色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

SpringBoot 接口簽名算法代碼設計

jf_ro2CN3Fa ? 來源:csdn ? 2023-11-07 15:11 ? 次閱讀

1概念

開放接口

開放接口是指不需要登錄憑證就允許被第三方系統調用的接口。為了防止開放接口被惡意調用,開放接口一般都需要驗簽才能被調用。提供開放接口的系統下面統一簡稱為"原系統"。

驗簽

驗簽是指第三方系統在調用接口之前,需要按照原系統的規則根據所有請求參數生成一個簽名(字符串),在調用接口時攜帶該簽名。原系統會驗證簽名的有效性,只有簽名驗證有效才能正常調用接口,否則請求會被駁回。

2接口驗簽調用流程

1. 約定簽名算法

第三方系統作為調用方,需要與原系統協商約定簽名算法(下面以SHA256withRSA簽名算法為例)。同時約定一個名稱(callerID),以便在原系統中來唯一標識調用方系統。

2. 頒發非對稱密鑰對

簽名算法約定后之后,原系統會為每一個調用方系統專門生成一個專屬的非對稱密鑰對(RSA密鑰對)。私鑰頒發給調用方系統,公鑰由原系統持有。

注意,調用方系統需要保管好私鑰(存到調用方系統的后端)。因為對于原系統而言,調用方系統是消息的發送方,其持有的私鑰唯一標識了它的身份是原系統受信任的調用方。調用方系統的私鑰一旦泄露,調用方對原系統毫無信任可言。

3. 生成請求參數簽名

簽名算法約定后之后,生成簽名的原理如下(活動圖)。

abc46c7e-7567-11ee-939d-92fbcf53809c.png

為了確保生成簽名的處理細節與原系統的驗簽邏輯是匹配的,原系統一般都提供jar包或者代碼片段給調用方來生成簽名,否則可能會因為一些處理細節不一致導致生成的簽名是無效的。

4. 請求攜帶簽名調用

路徑參數中放入約定好的callerID,請求頭中放入調用方自己生成的簽名

3代碼設計

1. 簽名配置類

相關的自定義yml配置如下。RSA的公鑰和私鑰可以使用hutool的SecureUtil工具類來生成,注意公鑰和私鑰是base64編碼后的字符串

abd6939a-7567-11ee-939d-92fbcf53809c.png

定義一個配置類來存儲上述相關的自定義yml配置

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;

/**
*簽名的相關配置
*/
@Data
@ConditionalOnProperty(value="secure.signature.enable",havingValue="true")//根據條件注入bean
@Component
@ConfigurationProperties("secure.signature")
publicclassSignatureProps{
privateBooleanenable;
privateMapkeyPair;

@Data
publicstaticclassKeyPairProps{
privateSignAlgorithmalgorithm;
privateStringpublicKeyPath;
privateStringpublicKey;
privateStringprivateKeyPath;
privateStringprivateKey;
}
}

2. 簽名管理類

定義一個管理類,持有上述配置,并暴露生成簽名和校驗簽名的方法。

注意,生成的簽名是將字節數組進行十六進制編碼后的字符串,驗簽時需要將簽名字符串進行十六進制解碼成字節數組

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();
}

/**
*驗簽。驗證不通過可能拋出運行時異常CryptoException
*
*@paramcallerID調用方的唯一標識
*@paramrawData原數據
*@paramsignature待驗證的簽名(十六進制字符串)
*@return驗證是否通過
*/
publicbooleanverifySignature(StringcallerID,StringrawData,Stringsignature){
Signsign=getSignByCallerID(callerID);
if(ObjectUtils.isEmpty(sign)){
returnfalse;
}

//使用公鑰驗簽
returnsign.verify(rawData.getBytes(StandardCharsets.UTF_8),HexUtil.decodeHex(signature));
}

/**
*生成簽名
*
*@paramcallerID調用方的唯一標識
*@paramrawData原數據
*@return簽名(十六進制字符串)
*/
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;//無效的、不受信任的調用方
}
returnSecureUtil.sign(keyPairProps.getAlgorithm(),keyPairProps.getPrivateKey(),keyPairProps.getPublicKey());
}

/**
*加載非對稱密鑰對
*/
privatevoidloadKeyPairByPath(){
//支持類路徑配置,形如:classpath:secure/public.txt
//公鑰和私鑰都是base64編碼后的字符串
signatureProps.getKeyPair()
.forEach((key,keyPairProps)->{
//如果配置了XxxKeyPath,則優先XxxKeyPath
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. 自定義驗簽注解

有些接口需要驗簽,但有些接口并不需要,為了靈活控制哪些接口需要驗簽,自定義一個驗簽注解

importjava.lang.annotation.*;


/**
*該注解標注于Controller類的方法上,表明該請求的參數需要校驗簽名
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public@interfaceVerifySignature{
}4. AOP實現驗簽邏輯

驗簽邏輯不能放在攔截器中,因為攔截器中不能直接讀取body的輸入流,否則會造成后續@RequestBody的參數解析器讀取不到body。

由于body輸入流只能讀取一次,因此需要使用ContentCachingRequestWrapper包裝請求,緩存body內容(見第5點),但是該類的緩存時機是在@RequestBody的參數解析器中。

因此,滿足2個條件才能獲取到ContentCachingRequestWrapper中的body緩存:

接口的入參必須存在@RequestBody

讀取body緩存的時機必須在@RequestBody的參數解析之后,比如說:AOP、Controller層的邏輯內。注意攔截器的時機是在參數解析之前的

綜上,標注了@VerifySignature注解的controlle層方法的入參必須存在@RequestBody,AOP中驗簽時才能獲取到body的緩存!

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);//不受信任的調用方
}

//從請求頭中提取簽名,不存在直接駁回
Stringsignature=request.getHeader(BaseConstant.X_REQUEST_SIGNATURE);
if(ObjectUtils.isEmpty(signature)){
thrownewParamInvalidException(SignStatusCode.REQUEST_SIGNATURE_INVALID);//無效簽名
}

//提取請求參數
StringrequestParamsStr=extractRequestParams(request);
//驗簽。驗簽不通過拋出業務異常
verifySignature(callerID,requestParamsStr,signature);
}

@SuppressWarnings("unchecked")
publicStringextractRequestParams(HttpServletRequestrequest){
//@RequestBody
Stringbody=null;
//驗簽邏輯不能放在攔截器中,因為攔截器中不能直接讀取body的輸入流,否則會造成后續@RequestBody的參數解析器讀取不到body
//由于body輸入流只能讀取一次,因此需要使用ContentCachingRequestWrapper包裝請求,緩存body內容,但是該類的緩存時機是在@RequestBody的參數解析器中
//因此滿足2個條件才能使用ContentCachingRequestWrapper中的body緩存
//1.接口的入參必須存在@RequestBody
//2.讀取body緩存的時機必須在@RequestBody的參數解析之后,比如說:AOP、Controller層的邏輯內。注意攔截器的時機是在參數解析之前的
if(requestinstanceofContentCachingRequestWrapper){
ContentCachingRequestWrapperrequestWrapper=(ContentCachingRequestWrapper)request;
body=newString(requestWrapper.getContentAsByteArray(),StandardCharsets.UTF_8);
}

//@RequestParam
MapparamMap=request.getParameterMap();

//@PathVariable
ServletWebRequestwebRequest=newServletWebRequest(request,null);
MapuriTemplateVarNap=(Map)webRequest.getAttribute(
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE,RequestAttributes.SCOPE_REQUEST);

returnCommonUtils.extractRequestParams(body,paramMap,uriTemplateVarNap);
}

/**
*驗證請求參數的簽名
*/
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);//轉換為業務異常拋出
}
}
}
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. 解決請求體只能讀取一次

解決方案就是包裝請求,緩存請求體。SpringBoot也提供了ContentCachingRequestWrapper來解決這個問題。但是第4點中也詳細描述了,由于它的緩存時機,所以它的使用有限制條件。也可以參考網上的方案,自己實現一個請求的包裝類來緩存請求體

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);
}
}

注冊過濾器

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
publicFilterRegistrationBeanrequestCachingFilterRegistration(
RequestCachingFilterrequestCachingFilter){
FilterRegistrationBeanbean=newFilterRegistrationBean<>(requestCachingFilter);
bean.setOrder(1);
returnbean;
}
}

6. 自定義工具類

importcn.hutool.core.util.StrUtil;
importorg.springframework.lang.Nullable;
importorg.springframework.util.ObjectUtils;

importjava.util.Arrays;
importjava.util.Map;
importjava.util.stream.Collectors;

publicclassCommonUtils{
/**
*提取所有的請求參數,按照固定規則拼接成一個字符串
*
*@parambodypost請求的請求體
*@paramparamMap路徑參數(QueryString)。形如:name=zhangsan&age=18&label=A&label=B
*@paramuriTemplateVarNap路徑變量(PathVariable)。形如:/{name}/{age}
*@return所有的請求參數按照固定規則拼接成的一個字符串
*/
publicstaticStringextractRequestParams(@NullableStringbody,@NullableMapparamMap,
@NullableMapuriTemplateVarNap){
//body:{userID:"xxx"}

//路徑參數
//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->{
//拷貝一份按字典序升序排序
String[]sortedValue=Arrays.stream(entry.getValue()).sorted().toArray(String[]::new);
returnentry.getKey()+"="+joinStr(",",sortedValue);
})
.collect(Collectors.joining("&"));
}

//路徑變量
///{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);
}

/**
*使用指定分隔符,拼接字符串
*
*@paramdelimiter分隔符
*@paramstrs需要拼接的多個字符串,可以為null
*@return拼接后的新字符串
*/
publicstaticStringjoinStr(Stringdelimiter,@NullableString...strs){
if(ObjectUtils.isEmpty(strs)){
returnStrUtil.EMPTY;
}
StringBuildersbd=newStringBuilder();
for(inti=0;i
                                        
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 接口
    +關注

    關注

    33

    文章

    8691

    瀏覽量

    151703
  • 算法
    +關注

    關注

    23

    文章

    4629

    瀏覽量

    93193
  • SpringBoot
    +關注

    關注

    0

    文章

    174

    瀏覽量

    193

原文標題:SpringBoot 接口簽名校驗實踐

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    基于橢圓曲線算法的數字簽名技術研究

    基于橢圓曲線算法的數字簽名技術的基本原理及其安全性,展望了公鑰密碼體制未來的發展方向。【關鍵詞】:橢圓曲線算法;;數字簽名;;網絡安全【DOI】:CNKI:SUN:GSKJ.0.201
    發表于 04-23 11:29

    SpringBoot知識總結

    SpringBoot干貨學習總結
    發表于 08-01 10:40

    怎樣去使用springboot

    怎樣去使用springboot呢?學習springboot需要懂得哪些?
    發表于 10-25 07:13

    在哪里可以找到用于導出AN10957上顯示的結果的確切CMAC簽名/mac代碼算法

    得到與 AN10957 不匹配的結果 (mac)。有誰知道我在哪里可以找到用于導出 AN10957 上顯示的結果的確切 CMAC 簽名/mac 代碼算法
    發表于 04-06 06:25

    什么是數字簽名算法(DSA)

    什么是數字簽名算法(DSA) DSA(Digital Signature Algorithm,數字簽名算法,用作數字簽名標準的一部分),它
    發表于 04-03 16:01 ?3544次閱讀

    一種錯誤簽名混合篩選算法

    針對分級身份密碼( HIBC)批驗簽過程中的錯誤簽名快速識別問題,設計實現了一種錯誤簽名混合篩選算法。針對HIBC簽名算法不完全聚合的特點,
    發表于 12-07 15:36 ?0次下載

    如何使用ECDSA算法生成數字簽名

    。 區塊鏈中的數字簽名 ECDSA算法 從A點到B點在橢圓曲線上的切線 根據wiki ECDSA為: 橢圓曲線密碼體制是一種基于有限域橢圓曲線代數結構的公鑰密碼體制。與非對稱密碼學相比
    發表于 12-27 14:12 ?9195次閱讀
    如何使用ECDSA<b class='flag-5'>算法</b>生成數字<b class='flag-5'>簽名</b>

    MuSig簽名方案可替代當前比特幣的ECDSA簽名算法

    當前,比特幣和其他區塊鏈普遍采用的是ECDSA簽名驗證算法。這顯然是中本聰在2008年根據當時廣泛使用和未授權的數字簽名系統所做出的技術決定。然而,ECDSA簽名存在一些嚴重的技術限制
    發表于 02-20 13:34 ?1603次閱讀

    Schnorr簽名和ECDSA簽名技術介紹

    Schnorr簽名是一個使BCH區塊鏈實現技術領先的強大功能,因為Schnorr簽名方案直接促進了BCH的隱私性和交易能力。Schnorr簽名算法是由著名的密碼學家Claus Schn
    發表于 05-16 10:32 ?2819次閱讀

    schnorr簽名算法相比ECDSA具有哪些優勢

    schnorr 簽名算法相比 ECDSA 來講,對于上述的優點,除了尚未標準化之外幾乎沒有缺點。而且由于兩種算法都基于同一個橢圓曲線,整個關于簽名的升級成本也是很低的。
    發表于 08-08 11:22 ?3459次閱讀

    基于ECDSA原理的FISCO BCOS交易簽名算法解析

    FISCO BCOS交易簽名算法基于ECDSA原理進行設計,ECDSA也是比特幣和以太坊采用的交易簽名算法
    發表于 02-19 16:46 ?1901次閱讀
    基于ECDSA原理的FISCO BCOS交易<b class='flag-5'>簽名</b><b class='flag-5'>算法</b>解析

    數據簽名的雙向簽名和重簽名的原理和資料簡介

    什么是數據簽名代碼簽名) 1.計算出需要校驗的數據HASH值 2.將校驗HASH值進行RSA加密 3.這部分利用RSA加密過后的HASH值,我們稱之為“數字簽名
    發表于 11-02 08:00 ?14次下載
    數據<b class='flag-5'>簽名</b>的雙向<b class='flag-5'>簽名</b>和重<b class='flag-5'>簽名</b>的原理和資料簡介

    基于ElGamal數字簽名算法的區塊鏈共識算法

    聯盟鏈是一種允許授權節點加入網絡的區塊鏈,當存在網絡狀況不理想等狀況時,會出現節點動態加入退出的問題。為此,在環簽名理論、 Elgamal數字簽名算法與PBFT算法的基礎上,提出一種
    發表于 05-19 11:51 ?10次下載

    SpringBoot如何實現啟動過程中執行代碼

    目前開發的SpringBoot項目在啟動的時候需要預加載一些資源。而如何實現啟動過程中執行代碼,或啟動成功后執行,是有很多種方式可以選擇,我們可以在static代碼塊中實現,也可以在構造方法里實現,也可以使用@PostConst
    的頭像 發表于 06-20 17:32 ?1480次閱讀

    什么是 SpringBoot

    本文從為什么要有 `SpringBoot`,以及 `SpringBoot` 到底方便在哪里開始入手,逐步分析了 `SpringBoot` 自動裝配的原理,最后手寫了一個簡單的 `start` 組件,通過實戰來體會了 `
    的頭像 發表于 04-07 11:28 ?1350次閱讀
    什么是 <b class='flag-5'>SpringBoot</b>?
    主站蜘蛛池模板: 一个人免费播放高清在线观看 | 国产精品久久久久久AV免费不卡 | 久久偷拍vs国产在线播放 | 老色69久久九九精品高潮 | 亚洲精品视频免费在线观看 | 99er久久国产精品在线 | 亚洲欧美综合乱码精品成人网 | 99爱在线精品视频网站 | 爱很烂qvod| 精油按摩日本 | 妖精视频在线观看高清 | 九九久久精品 | 全彩黄漫火影忍者纲手无遮挡 | 伊人不卡久久大香线蕉综合影院 | 亚洲综合视频 | 黄色网址在线免费观看 | 久久a级片 | 国产亚洲精品线观看不卡 | bl(高h)文 | 日韩精品久久久久久久电影 | 91久久偷偷做嫩草影院免费看 | 色欲人妻无码AV精品一区二区 | 理论片午午伦夜理片2021 | 日本午夜精品一区二区三区电影 | 一区二区三区国产亚洲网站 | 久久免费观看视频 | 国产亚洲AV无码成人网站 | 青青草原在线免费 | 热久久视久久精品2015 | 亚洲中文久久久久久国产精品 | 亚洲国产成人精品久久久久 | 怡春院国产精品视频 | 性欧美xxxxporn | 亚洲大码熟女在线 | 饥渴难耐的浪荡艳妇在线观看 | 色播成人影院 | 国产精人妻无码一区麻豆 | beeg日本老师按摩 | 欧美xxxx印度 | 亚洲午夜精品久久久久久抢 | 蜜芽一二三区 |