需求
有個簡單的需求,對于第三方接口我們需要做個簡單的鑒權機制,這邊使用的是非對稱性加密的機制。我們提供三方公鑰,他們通過公鑰對接口json報文使用加密后的報文請求,我們通過對接收過來的請求某一個加密報文字段來進行RSA解密校驗
考慮到日后方便其他接口使用,我這邊使用了攔截自定義注解+RequestBodyAdvice機制來處理。話不多說,來實操一把
定義自定義注解類
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AuthenticationThird{}
RAS工具類
pom文件引入依賴
<dependency><groupId>com.github.shalousun</groupId><artifactId>common-util</artifactId><version>1.9.2</version></dependency>
import com.power.common.util.RSAUtil;import java.util.Map;public class RSATool {/*** 隨機生成一對公私鑰*/public static Map<String, String> generatorRsaPariKey() {return RSAUtil.createKeys(1024);}/*** 通過公鑰來加密獲取對應base64字符串** @param sourceData* @param publicKey* @return*/public static String encryptStr(String sourceData, String publicKey) {return RSAUtil.encryptString(sourceData, publicKey);}/*** 通過RAS 公鑰加密的字符串 使用私鑰來解密** @param encryptStr* @param privateKey* @return*/public static String decryptStr(String encryptStr, String privateKey) {return RSAUtil.decryptString(encryptStr, privateKey);}}
私鑰配置類
@Configuration
@Data
public class ThridApiInfoConfig {@Value("${api.auth.privateKey}")public String authenticationThirdPrivateKey;
}
?第三方請求加密后封裝報文類
@Data
public class BaseEncryptReq {private String encryptBoyStr;private String reqSource;}
自定義異常類
@Setter
@Getter
public class NoPassException extends RuntimeException {private String message;public NoPassException(String message) {this.message = message;}}
RequestBodyAdvice這里類能干什么
對@RequestBody進行增強處理,比如所有請求的數據都加密之后放在 body 中,在到達 controller 的方法之前,需要先進行解密,那么就可以通過 RequestBodyAdvice 來進行統一的解密處理,無需在 controller 方法中去做這些通用的操作。
自定義的類需要實現 RequestBodyAdvice 接口,但是這個接口有個默認的實現類 RequestBodyAdviceAdapter,相當于一個適配器,方法體都是空的,所以我們自定義的類可以直接繼承這個類,更方便一些
繼承RequestBodyAdviceAdapter類
@Slf4j
@ControllerAdvice
public class DecryptRequestBodyAdvice extends RequestBodyAdviceAdapter {@Autowiredprivate ThridApiInfoConfig thridApiInfoConfig;@Overridepublic boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {return methodParameter.getMethod().isAnnotationPresent(AuthenticationThird.class);}@Overridepublic HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {String encoding = "UTF-8";InputStream inputStream = null;try {//step1 獲取http請求中原始的bodyString body = IOUtils.toString(inputMessage.getBody(), encoding);//step2 將對應字符串轉為beanBaseEncryptReq baseEncryptReq = JSON.parseObject(body, BaseEncryptReq.class);if (!StringUtil.isNotBlank(baseEncryptReq.getEncryptBoyStr())) {throw new NoPassException("接口加密報文不能為空");}if (!StringUtil.isNotBlank(baseEncryptReq.getReqSource())) {throw new NoPassException("接口請求來源參數不能為空");}//todo 判斷來源if (StringUtil.isNotBlank(baseEncryptReq.getReqSource()) && !"xxx".equals(baseEncryptReq.getReqSource())) {throw new NoPassException("接口請求來源參數不合法");}//step3 解密baseEncryptReq.encryptBoyStr屬性,進行RSATool解密String decryptBody = RSATool.decryptStr(baseEncryptReq.getEncryptBoyStr(), thridApiInfoConfig.getAuthenticationThirdPrivateKey());//step 4 將解密之后的body數據重新封裝為HttpInputMessage作為當前方法的返回值inputStream = IOUtils.toInputStream(decryptBody, encoding);} catch (NoPassException ex) {log.error("DecryptRequestBodyAdvice 處理解密異常");throw new NoPassException(ex.getMessage());} catch (Exception ex) {log.error("DecryptRequestBodyAdvice 處理解密異常");throw new NoPassException("接口解密異常");}InputStream finalInputStream = inputStream;return new HttpInputMessage() {@Overridepublic InputStream getBody() throws IOException {return finalInputStream;}@Overridepublic HttpHeaders getHeaders() {return inputMessage.getHeaders();}};}}
?定義測試controller類
@Slf4j
@RestController
@RequestMapping("/testController")
public class TestController {@AuthenticationThird@PostMapping(value="/testAuth")public ResultDto testAuth(@RequestBody String param){log.info("解密后的報文:{}",param);return ResultDto.builder().success(true).message("測試鑒權").build();}
}
定義全局異常處理類
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {/*** 處理 NoPassException 異常** @param e* @return*/@ExceptionHandler(NoPassException.class)@ResponseBodypublic ResultDto handleNoPassException(NoPassException e) {log.error("自定義NoPassException異常:{},{}", e.getMessage(), e);return ResultDto.builder().success(false).message(e.getMessage()).build();}
接口響應實體類
@Data
public class ResultDto {private Boolean success;private String message;
}
測試效果
我們先用之前的RSATool工具類對報文通過公鑰來加密
?控制臺輸出
?
?修改下加密報文
?至此就完成了一個簡單通用的接口鑒權