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


因为新增的自定义拦截器需要进行自定义拦截器配置

@Configuration
public 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

@Controller
public class TestController {

    @Auth //自定义注解
    @GetMapping("/auth" )
    private String myTest(){
        
        return "Hello.....";
    }
}

postman测试结果

@Value("${auth.need:false}") 当为false的情况下,接口可以直接访问。
Spring boot对外接口加密访问_第1张图片

@Value("${auth.need:true}") 当为true的情况下,我们直接访问,发现接口被拦截。
Spring boot对外接口加密访问_第2张图片
当我们在请求头中加上时间戳和签名后访问,就可以访问成功!
Spring boot对外接口加密访问_第3张图片
如果时间戳过期时间有要求的话,可以设置一个时间戳的最大过期时间,判断一下。

总结

刚刚接触,学习笔记,如有错误,欢迎指正。

你可能感兴趣的:(笔记,java,spring,boot,算法)