应届生工作日记-SpringSecurity框架的对外接口鉴权

1.背景

近期在工作中负责的项目需要对外提供接口,我们的项目是内部服务,只允许内网访问,首先要讲接口对外暴露出去,让外网可以访问到,这一部分是直接找了公司的运维去完成了。关于接口肯定是需要做鉴权的,普遍我们会选择进行签名,这样就可以保证接口的安全性。

2.思路

一般平台是采用appId+appSecret的方式去对外部服务做授权,双方约定好加密方式,通过验签的方式进行加密
认证。本案例是采用了MD5加密,对 对请求方式+url+请求body+密钥以及时间戳+随机字符串进行加密。
加密方式如下:
请求方式:POST
请求地址:/webApi/v1/test
请求体:根据具体业务填,一般是json格式
秘钥:appSecret,由提供接口方 提供给调用方,用于认证
时间戳:timestamp,调用接口的当前时间,防止攻击
随机字符串:nonce,调用方提供,提高接口安全性
调用方式:
可以把appId,timestamp,nouce,signature放入到请求头中
请求体只放入和业务相关的参数
接口调用方按照这个方式加密,传入一个signature;接口提供方按照这个加密方式也加密一次,然后相比较即可。

1.本项目采用的是springSecurity框架,首先在框架的WebSecurityConfigurerAdapter里面做配置对该接口进行放行
2.然后再编写一个SignInterceptor去实现HandlerInterceptor拦截器
3.实现WebMvcConfigurer中的addInterceptors方法把SignInterceptor注入进去

3.实现

(1)签名方法
 public static String getSignature(String method, String url, String body, String timestamp, String nonce, String appSecret){
 		//加密顺序以及方式自己规定即可
        return DigestUtils.md5Hex(method + url + body + appSecret + timestamp + nonce);
    }
(2)自定义拦截器
WebConfig是对外开放的一个实体,包含appid,appSecret等字段
WebEnum是对外统一提供的状态码
以上两个自行编写,不再提供

public class SignInterceptor implements HandlerInterceptor {
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private WebConfigMapper webConfigMapper;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //appId验证
        String appId = request.getHeader("appId");
        if (StringUtils.isEmpty(appId)) {
            returnMsg(response,WebEnum.APPID_EMPTY.getCode(), WebEnum.APPID_EMPTY.getMessage());
            return false;
        }
        WebConfig config = WebConfigMapper.selectConfigByAppId(appId);
        String appSecret = config.getAppSecret();
        if (StringUtils.isEmpty(appSecret)) {
            returnMsg(response, WebEnum.APPID_INVALID.getCode(),WebEnum.APPID_INVALID.getMessage());
            return false;
        }
        //时间戳验证
        String timestamp = request.getHeader("timestamp");
        if (StringUtils.isEmpty(timestamp)) {
            returnMsg(response,WebEnum.TIMESTAMP_EMPTY.getCode(), WebEnum.TIMESTAMP_EMPTY.getMessage());
            return false;
        }
        //大于5分钟,非法请求
        long diff = System.currentTimeMillis() - Long.parseLong(timestamp);
        if (Math.abs(diff) > 1000 * 60 * 5) {
            returnMsg(response,WebEnum.TIMESTAMP_EXPIRED.getCode(),WebEnum.TIMESTAMP_EXPIRED.getMessage());
            return false;
        }
        //随机字符串,防止重复提交
        String nonce = request.getHeader("nonce");
        if (StringUtils.isEmpty(nonce)) {
            returnMsg(response,WebEnum.NONCE_EMPTY.getCode(), WebEnum.NONCE_EMPTY.getMessage());
            return false;
        }
        //验证签名
        String signature = request.getHeader("signature");
        if (StringUtils.isEmpty(nonce)) {
            returnMsg(response,WebEnum.SIGNATURE_EMPTY.getCode(), WebEnum.SIGNATURE_EMPTY.getMessage());
            return false;
        }
        String method = request.getMethod();
        String url = request.getRequestURI();
        String body = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8);
        String signResult = SignUtil.getSignature(method, url, body, timestamp, nonce, appSecret);
//        System.out.println("signResult = " + signResult);
        if (!signature.equals(signResult)) {
            returnMsg(response,WebEnum.SIGNATURE_AUTHENTICATION_FAILED.getCode(), WebEnum.SIGNATURE_AUTHENTICATION_FAILED.getMessage());
            return false;
        }
        //检查是否重复请求
        String key = appId + "_" + timestamp + "_" + nonce;
        if (redisUtil.hasKey(key)) {
            returnMsg(response,WebEnum.REQUEST_DUPLICATED.getCode(), WebEnum.REQUEST_DUPLICATED.getMessage());
            return false;
        }
        //设置5分钟
        redisUtil.set(key, signResult, 5 * 60);
        request.setAttribute("redisKey", key);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //请求处理完毕之后,移除缓存
        String value = (String) request.getAttribute("redisKey");
        if (!StringUtils.isEmpty(value)) {
            redisUtil.del("value");
        }
    }

    public void returnMsg(HttpServletResponse response, Integer code, String message) throws IOException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        //返回的数据
        JSONObject res = new JSONObject();
        res.put("code", code);
        res.put("message", message);
        PrintWriter out = null;
        out = response.getWriter();
        out.write(res.toString());
        out.flush();
        out.close();
    }

(3)拦截器注入到WebMvcConfigurer中
@Configuration
public class WebAppConfigurer implements WebMvcConfigurer {
    @Bean
    public SignInterceptor signInterceptor(){
        return new SignInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    	//可以在这里添加多个接口拦截
        registry.addInterceptor(signInterceptor()).addPathPatterns("/webApi/v1/test");
    }
}

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值