Thisisacommunitythatmaybeusefultoyou
One-to-onecommunication/interviewbrochure/resumeoptimization/jobsearchquestions,welcometojointhe”YudaoRapidDevelopmentPlatform“KnowledgePlanet.ThefollowingissomeinformationprovidedbyPlanet:
“ProjectPractice(Video)”:Learnfrombooks,“practice”frompastevents
“InternetHighFrequencyInterviewQuestions”:Studyingwithyourresume,springblossoms
“ArchitecturexSystemDesign”:Overcomingdifficultiesandmasteringhigh-frequencyinterviewscenarioquestions
“AdvancingJavaLearningGuide”:systematiclearning,themainstreamtechnologystackoftheInternet
“Must-readJavaSourceCodeColumn”:Knowwhatitisandwhyitisso
Thisisanopensourceprojectthatmaybeusefultoyou
DomesticStarisa100,000+opensourceproject.Thefront-endincludesmanagementbackend+WeChatapplet,andtheback-endsupportsmonomerandmicroservicearchitecture.
FunctionscoverRBACpermissions,SaaSmulti-tenancy,datapermissions,mall,payment,workflow,large-screenreports,WeChatpublicaccount,etc.:
Bootaddress:https://gitee.com/zhijiantianya/ruoyi-vue-pro
Cloudaddress:https://gitee.com/zhijiantianya/yudao-cloud
Videotutorial:https://doc.iocoder.cn
Source:blog.csdn.net/qq_43290318
/article/details/131516099
-
1concept
-
openinterface
-
Verification
-
-
2Interfacesignatureverificationcallingprocess
-
1.Agreedsignaturealgorithm
-
2.Issueanasymmetrickeypair
-
3.Generaterequestparametersignature
-
4.Requesttocallwithsignature
-
-
3codedesign
-
1.Signatureconfigurationclass
-
2.Signaturemanagementclass
-
3.Customizesignatureverificationannotations
-
4.AOPimplementssignatureverificationlogic
-
5.Solvetheproblemthattherequestbodycanonlybereadonce
-
6.Customtoolclass
-
1Concept
Openinterface
Openinterfacesrefertointerfacesthatareallowedtobecalledbythird-partysystemswithoutrequiringlogincredentials.Inordertopreventopeninterfacesfrombeingcalledmaliciously,openinterfacesgenerallyrequiresignatureverificationbeforetheycanbecalled.Systemsthatprovideopeninterfacesarecollectivelyreferredtoas”originalsystems”below.
Signatureverification
Signatureverificationmeansthatbeforecallingtheinterface,thethird-partysystemneedstogenerateasignature(string)basedonallrequestparametersaccordingtotherulesoftheoriginalsystem,andcarrythesignaturewhencallingtheinterface.Theoriginalsystemwillverifythevalidityofthesignature.Onlyifthesignatureverificationisvalidcantheinterfacebecallednormally,otherwisetherequestwillberejected.
Backendmanagementsystem+userappletimplementedbasedonSpringBoot+MyBatisPlus+Vue&Element,supportingRBACdynamicpermissions,multi-tenancy,datapermissions,workflow,three-partylogin,payment,SMS,mallandotherfunctions
Projectaddress:https://github.com/YunaiV/ruoyi-vue-pro
Videotutorial:https://doc.iocoder.cn/video/
2Interfacesignatureverificationcallingprocess
1.Agreedsignaturealgorithm
Asthecaller,thethird-partysystemneedstonegotiatethesignaturealgorithmwiththeoriginalsystem(theSHA256withRSA
signaturealgorithmisusedasanexamplebelow).Atthesametime,aname(callerID)isagreedupontouniquelyidentifythecallingsystemintheoriginalsystem.
2.Issuanceofasymmetrickeypair
Afterthesignaturealgorithmisagreedupon,theoriginalsystemwillgenerateanexclusiveasymmetrickeypair(RSAkeypair)foreachcallersystem.Theprivatekeyisissuedtothecallingsystem,andthepublickeyisheldbytheoriginalsystem.
Notethatthecallersystemneedstokeeptheprivatekey(storedinthebackendofthecallersystem).Becausefortheoriginalsystem,thecallersystemisthesenderofthemessage,andtheprivatekeyitholdsuniquelyidentifiesitasatrustedcalleroftheoriginalsystem.Oncetheprivatekeyofthecaller’ssystemisleaked,thecallerhasnotrustintheoriginalsystem.
3.Generaterequestparametersignature
Afterthesignaturealgorithmisagreedupon,theprincipleofgeneratingsignaturesisasfollows(activitydiagram).
Inordertoensurethattheprocessingdetailsofgeneratingsignaturesmatchthesignatureverificationlogicoftheoriginalsystem,theoriginalsystemgenerallyprovidesjarpackagesorcodesnippetstothecallertogeneratesignatures.Otherwise,thegeneratedsignaturesmaybeinvalidduetoinconsistenciesinsomeprocessingdetails..
4.Requesttocallwithsignature
PuttheagreedcallerIDinthepathparameter,andputthesignaturegeneratedbythecallerintherequestheader.
Backendmanagementsystem+userappletimplementedbasedonSpringCloudAlibaba+Gateway+Nacos+RocketMQ+Vue&Element,supportingRBACdynamicpermissions,multi-tenancy,datapermissions,workflow,three-partylogin,payment,SMS,mallandotherfunctions
Projectaddress:https://github.com/YunaiV/yudao-cloud
Videotutorial:https://doc.iocoder.cn/video/
3CodeDesign
1.Signatureconfigurationclass
Therelevantcustomymlconfigurationisasfollows.ThepublicandprivatekeysofRSAcanbegeneratedusingtheSecureUtiltoolclassofhugool.Notethatthepublicandprivatekeysarebase64-encodedstrings.
Defineaconfigurationclasstostoretheaboverelatedcustomymlconfiguration
importcn.hutool.crypto.asymmetric.SignAlgorithm; importlombok.Data; importorg.springframework.boot.autoconfigure.condition.ConditionalOnProperty; importorg.springframework.boot.context.properties.ConfigurationProperties; importorg.springframework.stereotype.Component; importjava.util.Map; /** *Signaturerelatedconfiguration */ @Data @ConditionalOnProperty(value="secure.signature.enable",havingValue="true")//Injectbeansbasedonconditions @Component @ConfigurationProperties("secure.signature") publicclassSignatureProps{ privateBooleanenable; privateMap<String,KeyPairProps>keyPair; @Data publicstaticclassKeyPairProps{ privateSignAlgorithmalgorithm; privateStringpublicKeyPath; privateStringpublicKey; privateStringprivateKeyPath; privateStringprivateKey; } }
2.Signaturemanagementclass
Defineamanagementclassthatholdstheaboveconfigurationandexposesmethodsforgeneratingsignaturesandverifyingsignatures.
Notethatthegeneratedsignatureisahexadecimal-encodedstringofbytearrays.Whenverifyingthesignature,youneedtohexadecimal-decodethesignaturestringintoabytearray.
importcn.hutool.core.io.IoUtil; importcn.hutool.core.io.resource.ResourceUtil; importcn.hutool.core.util.HexUtil; importcn.hutool.crypto.SecureUtil; importcn.hutool.crypto.asymmetric.Sign; importorg.springframework.boot.autoconfigure.condition.ConditionalOnBean; importorg.springframework.stereotype.Component; importorg.springframework.util.ObjectUtils; importtop.ysqorz.signature.model.SignatureProps; importjava.nio.charset.StandardCharsets; @ConditionalOnBean(SignatureProps.class) @Component publicclassSignatureManager{ privatefinalSignaturePropssignatureProps; publicSignatureManager(SignaturePropssignatureProps){ this.signatureProps=signatureProps; loadKeyPairByPath(); } /** *Verification.FailuretopassverificationmaythrowaruntimeexceptionCryptoException * *@paramcallerIDuniqueidentifierofthecaller *@paramrawDataoriginaldata *@paramsignaturesignaturetobeverified(hexstring) *@returnwhethertheverificationispassed */ publicbooleanverifySignature(StringcallerID,StringrawData,Stringsignature){ Signsign=getSignByCallerID(callerID); if(ObjectUtils.isEmpty(sign)){ returnfalse; } //Usepublickeytoverifysignature returnsign.verify(rawData.getBytes(StandardCharsets.UTF_8),HexUtil.decodeHex(signature)); } /** *Generatesignature * *@paramcallerIDuniqueidentifierofthecaller *@paramrawDataoriginaldata *@returnsignature(hexstring) */ publicStringsign(StringcallerID,StringrawData){ Signsign=getSignByCallerID(callerID); if(ObjectUtils.isEmpty(sign)){ returnnull; } returnsign.signHex(rawData); } publicSignaturePropsgetSignatureProps(){ returnsignatureProps; } publicSignatureProps.KeyPairPropsgetKeyPairPropsByCallerID(StringcallerID){ returnsignatureProps.getKeyPair().get(callerID); } privateSigngetSignByCallerID(StringcallerID){ SignatureProps.KeyPairPropskeyPairProps=signatureProps.getKeyPair().get(callerID); if(ObjectUtils.isEmpty(keyPairProps)){ returnnull;//Invalid,untrustedcaller } returnSecureUtil.sign(keyPairProps.getAlgorithm(),keyPairProps.getPrivateKey(),keyPairProps.getPublicKey()); } /** *Loadasymmetrickeypair */ privatevoidloadKeyPairByPath(){ //Supportclasspathconfiguration,intheform:classpath:secure/public.txt //Thepublickeyandprivatekeyarebothbase64encodedstrings signatureProps.getKeyPair() .forEach((key,keyPairProps)->{ //IfXxxKeyPathisconfigured,XxxKeyPathtakesprecedence keyPairProps.setPublicKey(loadKeyByPath(keyPairProps.getPublicKeyPath())); keyPairProps.setPrivateKey(loadKeyByPath(keyPairProps.getPrivateKeyPath())); if(ObjectUtils.isEmpty(keyPairProps.getPublicKey())|| ObjectUtils.isEmpty(keyPairProps.getPrivateKey())){ thrownewRuntimeException("Nopublicandprivatekeyfilesconfigured"); } }); } privateStringloadKeyByPath(Stringpath){ if(ObjectUtils.isEmpty(path)){ returnnull; } returnIoUtil.readUtf8(ResourceUtil.getStream(path)); } }
3.Customizedsignatureverificationannotations
Someinterfacesrequiresignatureverification,butsomeinterfacesdonot.Inordertoflexiblycontrolwhichinterfacesrequiresignatureverification,customizeasignatureverificationannotation.
importjava.lang.annotation.*; /** *ThisannotationismarkedonthemethodoftheControllerclass,indicatingthattheparametersoftherequestneedtobeverifiedforsignature */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD,ElementType.TYPE}) public@interfaceVerifySignature{ }
4.AOPimplementssignatureverificationlogic
Thesignatureverificationlogiccannotbeplacedintheinterceptor,becausetheinterceptorcannotdirectlyreadtheinputstreamofthebody,otherwisethesubsequent@RequestBody
parameterparserwillnotbeabletoreadthebody.
Sincethebodyinputstreamcanonlybereadonce,youneedtouseContentCachingRequestWrapper
towraptherequestandcachethebodycontent(seepoint5),butthecachetimingofthisclassisin@RequestBody
intheparameterparser.
Therefore,twoconditionsmustbemettoobtainthebodycacheinContentCachingRequestWrapper
:
-
Theinputparametersoftheinterfacemustexist
@RequestBody
-
Thetimingofreadingthebodycachemustbeaftertheparametersof
@RequestBody
areparsed,forexample,withinthelogicoftheAOPandControllerlayers.Notethatthetimingoftheinterceptorisbeforeparameterparsing
Tosumup,theinputparametersofthecontrollayermethodannotatedwith@VerifySignature
mustexistin@RequestBody
,sothatthebodycachecanbeobtainedduringsignatureverificationinAOP!
importcn.hutool.crypto.CryptoException; importlombok.extern.slf4j.Slf4j; importorg.aspectj.lang.annotation.Aspect; importorg.aspectj.lang.annotation.Before; importorg.aspectj.lang.annotation.Pointcut; importorg.springframework.boot.autoconfigure.condition.ConditionalOnBean; importorg.springframework.stereotype.Component; importorg.springframework.util.ObjectUtils; importorg.springframework.web.context.request.RequestAttributes; importorg.springframework.web.context.request.ServletWebRequest; importorg.springframework.web.servlet.HandlerMapping; importorg.springframework.web.util.ContentCachingRequestWrapper; importtop.ysqorz.common.constant.BaseConstant; importtop.ysqorz.config.SpringContextHolder; importtop.ysqorz.config.aspect.PointCutDef; importtop.ysqorz.exception.auth.AuthorizationException; importtop.ysqorz.exception.param.ParamInvalidException; importtop.ysqorz.signature.model.SignStatusCode; importtop.ysqorz.signature.model.SignatureProps; importtop.ysqorz.signature.util.CommonUtils; importjavax.annotation.Resource; importjavax.servlet.http.HttpServletRequest; importjava.nio.charset.StandardCharsets; importjava.util.Map; @ConditionalOnBean(SignatureProps.class) @Component @Slf4j @Aspect publicclassRequestSignatureAspectimplementsPointCutDef{ @Resource privateSignatureManagersignatureManager; @Pointcut("@annotation(top.ysqorz.signature.enumeration.VerifySignature)") publicvoidannotatedMethod(){ } @Pointcut("@within(top.ysqorz.signature.enumeration.VerifySignature)") publicvoidannotatedClass(){ } @Before("apiMethod()&&(annotatedMethod()||annotatedClass())") publicvoidverifySignature(){ HttpServletRequestrequest=SpringContextHolder.getRequest(); StringcallerID=request.getParameter(BaseConstant.PARAM_CALLER_ID); if(ObjectUtils.isEmpty(callerID)){ thrownewAuthorizationException(SignStatusCode.UNTRUSTED_CALLER);//Untrustedcaller } //Extractthesignaturefromtherequestheader,ifthereisnodirectrejection Stringsignature=request.getHeader(BaseConstant.X_REQUEST_SIGNATURE); if(ObjectUtils.isEmpty(signature)){ thrownewParamInvalidException(SignStatusCode.REQUEST_SIGNATURE_INVALID);//Invalidsignature } //Extractrequestparameters StringrequestParamsStr=extractRequestParams(request); //Verifysignature.Ifthesignatureverificationfails,abusinessexceptionwillbethrown. verifySignature(callerID,requestParamsStr,signature); } @SuppressWarnings("unchecked") publicStringextractRequestParams(HttpServletRequestrequest){ //@RequestBody Stringbody=null; //Thesignatureverificationlogiccannotbeplacedintheinterceptor,becausetheinterceptorcannotdirectlyreadtheinputstreamofthebody,otherwisethesubsequent@RequestBodyparameterparserwillnotbeabletoreadthebody. //Sincethebodyinputstreamcanonlybereadonce,ContentCachingRequestWrapperneedstobeusedtowraptherequestandcachethebodycontent.However,thecachetimingofthisclassisintheparameterparserof@RequestBody. //Therefore,twoconditionsmustbemettousethebodycacheinContentCachingRequestWrapper. //1.Theinputparameteroftheinterfacemustexist@RequestBody //2.Thetimingofreadingthebodycachemustbeaftertheparametersof@RequestBodyareparsed,forexample:withinthelogicoftheAOPandControllerlayers.Notethatthetimingoftheinterceptorisbeforeparameterparsing if(requestinstanceofContentCachingRequestWrapper){ ContentCachingRequestWrapperrequestWrapper=(ContentCachingRequestWrapper)request; body=newString(requestWrapper.getContentAsByteArray(),StandardCharsets.UTF_8); } //@RequestParam Map<String,String[]>paramMap=request.getParameterMap(); //@PathVariable ServletWebRequestwebRequest=newServletWebRequest(request,null); Map<String,String>uriTemplateVarNap=(Map<String,String>)webRequest.getAttribute( HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE,RequestAttributes.SCOPE_REQUEST); returnCommonUtils.extractRequestParams(body,paramMap,uriTemplateVarNap); } /** *Verifythesignatureofrequestparameters */ publicvoidverifySignature(StringcallerID,StringrequestParamsStr,Stringsignature){ try{ booleanverified=signatureManager.verifySignature(callerID,requestParamsStr,signature); if(!verified){ thrownewCryptoException("Thesignatureverificationresultisfalse."); } }catch(Exceptionex){ log.error("Failedtoverifysignature",ex); thrownewAuthorizationException(SignStatusCode.REQUEST_SIGNATURE_INVALID);//Converttobusinessexceptionandthrow } } } importorg.aspectj.lang.annotation.Pointcut; publicinterfacePointCutDef{ @Pointcut("execution(public*top.ysqorz..controller.*.*(..))") defaultvoidcontrollerMethod(){ } @Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)") defaultvoidpostMapping(){ } @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)") defaultvoidgetMapping(){ } @Pointcut("@annotation(org.springframework.web.bind.annotation.PutMapping)") defaultvoidputMapping(){ } @Pointcut("@annotation(org.springframework.web.bind.annotation.DeleteMapping)") defaultvoiddeleteMapping(){ } @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)") defaultvoidrequestMapping(){ } @Pointcut("controllerMethod()&&(requestMapping()||postMapping()||getMapping()||putMapping()||deleteMapping())") defaultvoidapiMethod(){ } }
5.Solvetheproblemthattherequestbodycanonlybereadonce
Thesolutionistowraptherequestandcachetherequestbody.SpringBootalsoprovidesContentCachingRequestWrapper
tosolvethisproblem.However,asalsodescribedindetailinpoint4,duetoitscachingtiming,itsusehasrestrictions.Youcanalsorefertoonlinesolutionsandimplementarequestwrapperclasstocachetherequestbody.
importlombok.NonNull; importorg.springframework.boot.autoconfigure.condition.ConditionalOnBean; importorg.springframework.stereotype.Component; importorg.springframework.web.filter.OncePerRequestFilter; importorg.springframework.web.util.ContentCachingRequestWrapper; importtop.ysqorz.signature.model.SignatureProps; importjavax.servlet.FilterChain; importjavax.servlet.ServletException; importjavax.servlet.http.HttpServletRequest; importjavax.servlet.http.HttpServletResponse; importjava.io.IOException; @ConditionalOnBean(SignatureProps.class) @Component publicclassRequestCachingFilterextendsOncePerRequestFilter{ /** *This{@codedoFilter}implementationstoresarequestattributefor *"alreadyfiltered",proceedingwithoutfilteringagainifthe *attributeisalreadythere. * *@paramrequestrequest *@paramresponseresponse *@paramfilterChainfilterChain *@see#getAlreadyFilteredAttributeName *@see#shouldNotFilter *@see#doFilterInternal */ @Override protectedvoiddoFilterInternal(@NonNullHttpServletRequestrequest,@NonNullHttpServletResponseresponse,@NonNullFilterChainfilterChain) throwsServletException,IOException{ booleanisFirstRequest=!isAsyncDispatch(request); HttpServletRequestrequestWrapper=request; if(isFirstRequest&&!(requestinstanceofContentCachingRequestWrapper)){ requestWrapper=newContentCachingRequestWrapper(request); } filterChain.doFilter(requestWrapper,response); } }
Registerfilter
importorg.springframework.boot.autoconfigure.condition.ConditionalOnBean; importorg.springframework.boot.web.servlet.FilterRegistrationBean; importorg.springframework.context.annotation.Bean; importorg.springframework.context.annotation.Configuration; importtop.ysqorz.signature.model.SignatureProps; @Configuration publicclassFilterConfig{ @ConditionalOnBean(SignatureProps.class) @Bean publicFilterRegistrationBean<RequestCachingFilter>requestCachingFilterRegistration( RequestCachingFilterrequestCachingFilter){ FilterRegistrationBean<RequestCachingFilter>bean=newFilterRegistrationBean<>(requestCachingFilter); bean.setOrder(1); returnbean; } }
6.Customtoolclass
importcn.hutool.core.util.StrUtil; importorg.springframework.lang.Nullable; importorg.springframework.util.ObjectUtils; importjava.util.Arrays; importjava.util.Map; importjava.util.stream.Collectors; publicclassCommonUtils{ /** *Extractallrequestparametersandconcatenatethemintoastringaccordingtofixedrules * *@parambodyTherequestbodyofthepostrequest *@paramparamMappathparameter(QueryString).Intheform:name=zhangsan&age=18&label=A&label=B *@paramuriTemplateVarNappathvariable(PathVariable).Intheform:/{name}/{age} *@returnAllrequestparametersaresplicedintoastringaccordingtofixedrules */ publicstaticStringextractRequestParams(@NullableStringbody,@NullableMap<String,String[]>paramMap, @NullableMap<String,String>uriTemplateVarNap){ //body:{userID:"xxx"} //pathparameters //name=zhangsan&age=18&label=A&label=B //=>["name=zhangsan","age=18","label=A,B"] //=>name=zhangsan&age=18&label=A,B StringparamStr=null; if(!ObjectUtils.isEmpty(paramMap)){ paramStr=paramMap.entrySet().stream() .sorted(Map.Entry.comparingByKey()) .map(entry->{ //Makeacopyandsorttheminascendinglexicographicorder String[]sortedValue=Arrays.stream(entry.getValue()).sorted().toArray(String[]::new); returnentry.getKey()+"="+joinStr(",",sortedValue); }) .collect(Collectors.joining("&")); } //pathvariable ///{name}/{age}=>/zhangsan/18=>zhangsan,18 StringuriVarStr=null; if(!ObjectUtils.isEmpty(uriTemplateVarNap)){ uriVarStr=joinStr(",",uriTemplateVarNap.values().stream().sorted().toArray(String[]::new)); } //{userID:"xxx"}#name=zhangsan&age=18&label=A,B#zhangsan,18 returnjoinStr("#",body,paramStr,uriVarStr); } /** *Usethespecifieddelimitertoconcatenatestrings * *@paramdelimiterdelimiter *@paramstrsMultiplestringstobespliced,canbenull *@returnthenewstringafterconcatenation */ publicstaticStringjoinStr(Stringdelimiter,@NullableString...strs){ if(ObjectUtils.isEmpty(strs)){ returnStrUtil.EMPTY; } StringBuildersbd=newStringBuilder(); for(inti=0;i<strs.length;i++){ if(ObjectUtils.isEmpty(strs[i])){ continue; } sbd.append(strs[i].trim()); if(!ObjectUtils.isEmpty(sbd)&&i<strs.length-1&&!ObjectUtils.isEmpty(strs[i+1])){ sbd.append(delimiter); } } returnsbd.toString(); } }
Codeofthisarticle
https://github.com/passerbyYSQ/DemoRepository
Welcometojoinmyknowledgeplanetandcomprehensivelyimproveyourtechnicalcapabilities.
Tojoin,“Longpress”or“Scan”theQRcodebelow:
Planet’scontentincludes:projectpractice,interviewsandrecruitment,sourcecodeanalysis,andlearningroutes.
Ifthearticleishelpful,pleasereaditandforwardit. Thankyouforyoursupport(*^__^*)
Theknowledgepointsofthearticlematchtheofficialknowledgefiles,andyoucanfurtherlearnrelatedknowledge.JavaSkillTreeHomepageOverview138474peoplearelearningthesystem