需求
有个简单的需求,对于第三方接口我们需要做个简单的鉴权机制,这边使用的是非对称性加密的机制。我们提供三方公钥,他们通过公钥对接口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 {
@Autowired
private ThridApiInfoConfig thridApiInfoConfig;
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return methodParameter.getMethod().isAnnotationPresent(AuthenticationThird.class);
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
String encoding = "UTF-8";
InputStream inputStream = null;
try {
//step1 获取http请求中原始的body
String body = IOUtils.toString(inputMessage.getBody(), encoding);
//step2 将对应字符串转为bean
BaseEncryptReq 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() {
@Override
public InputStream getBody() throws IOException {
return finalInputStream;
}
@Override
public 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)
@ResponseBody
public 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工具类对报文通过公钥来加密
控制台输出
修改下加密报文
至此就完成了一个简单通用的接口鉴权