Spring boot对外接口加密访问
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
Spring boot对外接口加密访问
- 说明
- 学习demo
- postman测试结果
- 总结
说明
由于公司业务需求,需要开放个别接口提供给第三方系统调用,为保证接口调用安全,请求放需要添加私钥请求校验,我方使用SHA256算法计算签名,然后进行Base64 encode,最后再进行urlEncode,来得到最终的签名,
学习demo
因为不是所有接口都需要加密访问,所以使用自定义注解来判断接口是否需要鉴权。
@Documented@Inherited@Target({ElementType.METHOD,ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface Auth { boolean validate() default true;//是否需要鉴权}
因需要对接口进行过滤拦截,所以创建拦截器,通过继承HandlerInterceptorAdaper实现过滤拦截:
public class AuthSercurityInterceptor extends HandlerInterceptorAdapter { @Value("${auth.secret.info:12345}") //双方约定秘钥 private String secret; @Value("${auth.need:true}") //true 表示拦截 private Boolean needAuth; //HEADER Authorization private static final String INFO_TIME = "timestamp"; private static final String INFO_SIGN = "sign"; private static final String AUTH_HEADER = "Authorization"; public String getSecret() { return secret; } public void setSecret(String secret) { this.secret = secret; } public Boolean getNeedAuth() { return needAuth; } public void setNeedAuth(Boolean needAuth) { this.needAuth = needAuth; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (needAuth==null||!needAuth){ return true; } if(!handler.getClass().isAssignableFrom(HandlerMethod.class)){ return true; } Auth auth = ((HandlerMethod)handler).getMethodAnnotation(Auth.class); if (auth==null||!auth.validate()){ return true; } String authorization = request.getHeader(AUTH_HEADER); System.out.println("authorization is :" + authorization); String[] info = authorization.trim().split(","); if (info==null||info.length<1){ throw new Exception("error ....."); } String timestamp = null; String sign = null; for (int i = 0; i < info.length; i++) { String str = info[i].trim(); if (StringUtils.isEmpty(str)){ continue; } String[] strSplit = str.split("="); if (strSplit==null||strSplit.length!=2){ continue; } String key = strSplit[0]; String value = strSplit[1]; if (INFO_TIME.equalsIgnoreCase(key)){ timestamp = value; System.out.println("timestamp is :" + timestamp); } if (INFO_SIGN.equalsIgnoreCase(key)){ sign = value; System.out.println("sign is :" + sign); } } if (StringUtils.isEmpty(timestamp)||StringUtils.isEmpty(sign)){ throw new Exception("error timestamp or sign is null"); } String sha256Str = SHAUtils.getSHA256Str(secret,timestamp); System.out.println("sha256str is :" + sha256Str); if (StringUtils.isEmpty(sha256Str)){ throw new Exception("sha256Str is null ..."); } if (!sha256Str.equals(sign)){ throw new Exception("sign error..."); } return super.preHandle(request,response,handler); }}
然后使用SHA256算法计算签名,然后进行Base64 encoder,最后在进行urlEncoder,来得到最终签名。
public class SHAUtils { public static final String ENCODE_TYPE_HMAC_SHA_256 ="HmacSHA256"; public static final String ENCODE_UTF_8_LOWER ="utf-8"; public static final String ENCODE_UTF_8_UPPER ="UTF-8"; public static String getSHA256Str(String secret,String message) throws Exception { if (StringUtils.isEmpty(secret)){ return null; } String encodeStr; try{ //HMAC_SHA256 加密 Mac HMAC_SHA256 = Mac.getInstance(ENCODE_TYPE_HMAC_SHA_256); SecretKeySpec secre_spec = new SecretKeySpec(secret.getBytes(ENCODE_UTF_8_UPPER),ENCODE_TYPE_HMAC_SHA_256); HMAC_SHA256.init(secre_spec); byte[] bytes = HMAC_SHA256.doFinal(message.getBytes(ENCODE_UTF_8_UPPER)); if (bytes==null&&bytes.length<1){ return null; } //字节转换为16进制字符串 String SHA256 =byteToHex(bytes); if (StringUtils.isEmpty(SHA256)){ return null; } //base64 String BASE64 = Base64.getEncoder().encodeToString(SHA256.getBytes(ENCODE_UTF_8_UPPER)); if (StringUtils.isEmpty(BASE64)){ return null; } //url encode encodeStr = URLEncoder.encode(BASE64,ENCODE_UTF_8_LOWER); }catch (Exception e){ throw new Exception("get 256 info error ...."); } return encodeStr; } private static String byteToHex(byte[] bytes){ if (bytes==null){ return null; } StringBuffer stringBuffer = new StringBuffer(); String temp=null; for (int i = 0; i <bytes.length ; i++) { temp = Integer.toHexString(bytes[i]&0xff); if (temp.length()==1){ stringBuffer.append("0"); } stringBuffer.append(temp); } return stringBuffer.toString(); }}
因为新增的自定义拦截器需要进行自定义拦截器配置
@Configurationpublic class AuthConfig extends WebMvcConfigurationSupport { @Bean //自定义的AuthSercurityInterceptor public AuthSercurityInterceptor authSercurityInterceptor(){ return new AuthSercurityInterceptor(); } @Override //进行注册添加AuthSercurityInterceptor protected void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authSercurityInterceptor()).addPathPatterns("/"); super.addInterceptors(registry); }}
创建controller
@Controllerpublic class TestController { @Auth //自定义注解 @GetMapping("/auth" ) private String myTest(){ return "Hello....."; }}
postman测试结果
@Value("${auth.need:false}") 当为false的情况下,接口可以直接访问。
@Value("${auth.need:true}") 当为true的情况下,我们直接访问,发现接口被拦截。
当我们在请求头中加上时间戳和签名后访问,就可以访问成功!
如果时间戳过期时间有要求的话,可以设置一个时间戳的最大过期时间,判断一下。
总结
刚刚接触,学习笔记,如有错误,欢迎指正。