springboot自定义注解+拦截器完成Token+签名校验

一:为什么需要使用校验

请求校验可以保证数据通信时的安全性:

  • 防止参数被篡改
  • 判断登录用户是否合法(虽然你是你,但是你还需要门票(token)才能进入)
  • 请求的唯一性(防止恶意重复提交–爬虫、网络攻击等)
二:工作流程
  • 不加校验:
    获取一个列表:http://api.XXX.com/getproductList?id=value1
    这种方式,只需要一个url调用者就可以获取到数据列表,导致数据泄露,也会造成数据库压力激增。
  • 加入校验:
    1. token校验:用户在登录时,通过一定逻辑生成一份token返回给客户端保存,同时在服务器端保存一份,每次用户调用接口时,会携带这个token与服务器存放的token进行校验,如果一致则校验通过。(和uuid的区别:uuid相当于身份证,token相当于门票,token是会不断改变的。就算别人拿到了你的uuid也没用)
    2. 签名校验:请求api时,会将发送的请求url的参数按ASCII字段排序,然后对这个字符串进行按照一定规则进行加密,放入到参数中一起传给服务器,服务器根据请求参数按照相同规则进行加密,如果加密后的数据和传参一致,校验通过。(可以通过加入时间戳,来控制时间)
    3. 例:微信支付接口
      在这里插入图片描述
      在这里插入图片描述
三:代码示例
  1. 自定义注解:
@Inherited
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PublicUrl {

    /**
     * 是否校验token合法性
     *
     * @return
     */
    boolean tokenValidate() default false;

    /**
     * 是否校验签名合法性
     *
     * @return
     */
    boolean signValidate() default false;
}
  1. 自定义拦截器,实现WebMvcConfigurer接口
    此处在我的2个项目中出现了不同的情况,一个@Configuration注解完全不起作用,需要加上@EnableWebMvc注解才生效,一个只需要@Configuration就ok了,原因解释见:@EnableWebMvc注解
@EnableWebMvc
@Configuration
public class MyAppConfigurer implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(getHandlerInterceptor());
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedHeaders("Content-Type", "x-requested-with", "X-Custom-Header")
                .allowedMethods("PUT", "POST", "GET", "DELETE", "OPTIONS")
                .allowedOrigins("*")
                .allowCredentials(true);
    }

    @Bean
    public HandlerInterceptor getHandlerInterceptor() {
        return new SafeHandlerInterceptor();
    }
}
  1. 签名及token校验的具体实现
public class SafeHandlerInterceptor implements HandlerInterceptor {
    @Autowired
    private MyProperties properties;
    @Autowired
    private UserDao userDao;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {     
        if (properties.getTokenDisable()) {
            return true;
        }
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            if (handlerMethod.getBean() instanceof BasicErrorController) {//exclude frame BasicErrorController
                return true;
            }
            ValidateResponse validateResponse = new ValidateResponse(true, null);
            //validate controller method annotation to judge token or params whether or not validate
            PublicUrl publicUrl = handlerMethod.getMethodAnnotation(PublicUrl.class);
            if (null != publicUrl) {
                if (publicUrl.signValidate() == true) {
                    validateResponse = paramSignValidate(request, response);
                }
                if (validateResponse.isValidate() && publicUrl.tokenValidate() == true) {
                    validateResponse = tokenValidate(request, response);
                }
            } else {
                //validate controller class annotation to judge token or params whether or not validate
                publicUrl = handlerMethod.getBean().getClass().getAnnotation(PublicUrl.class);
                if (null != publicUrl) {
                    if (publicUrl.signValidate() == true) {
                        validateResponse = paramSignValidate(request, response);
                    }
                    if (validateResponse.isValidate() && publicUrl.tokenValidate() == true) {
                        validateResponse = tokenValidate(request, response);
                    }
                } else {
                    validateResponse = validate(request, response);
                }
            }
            if (!validateResponse.isValidate()) {
                throw validateResponse.getException();
            }
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {    }

    /**
     * 全局校验
     *
     * @param request
     * @param response
     * @return
     */
    private ValidateResponse validate(HttpServletRequest request, HttpServletResponse response) {
        ValidateResponse validateResponse = paramSignValidate(request, response);
        if (validateResponse.isValidate()) {
            validateResponse = tokenValidate(request, response);
        }
        return validateResponse;
    }

    /**
     * 签名校验
     *
     * @param request
     * @param response
     * @return
     */
    private ValidateResponse paramSignValidate(HttpServletRequest request, HttpServletResponse response) {
        String sign = request.getParameter("sign");
        Map<String, String[]> map = request.getParameterMap();
        // 将参数按照一定规则获取到sign和前端传过来的sign进行比较,规则自定义需要和前端一致
        String sign1 = SignUtil.signMap(request.getServletPath(), map);
        if (ObjectUtil.isEmpty(sign) || !sign.equals(sign1)) {
            return new ValidateResponse(false, new ApiException(ResultEnums.ILLEGAL_REQUEST));
        }
        return new ValidateResponse(true, null);
    }

    /**
     * token校验
     *
     * @param request
     * @param response
     * @return
     */
    private ValidateResponse tokenValidate(HttpServletRequest request, HttpServletResponse response) {
        String token = request.getParameter(“token”);
        if (ObjectUtil.isEmpty(token)) {
            return new ValidateResponse(false, new ApiException(ResultEnums.MISS_TOKEN));
        }
        String userUuid = request.getParameter(USER_UUID);
        // 此处将token保存在了db,也可以放在redis
        User user = userDao.selectByUuid(userUuid);
        if (ObjectUtil.isEmpty(user)) {
            return new ValidateResponse(false, new ApiException(ResultEnums.ILLEGAL_USER));
        }
        // 将前台传来的token和服务器的进行比较,前端的token可以是用户登录时由一定规则生成的一个密钥,随返回值一起传给前端存储
        if (!token.equals(user.getToken())) {
            return new ValidateResponse(false, new ApiException(ResultEnums.ERROR_TOKEN));
        }
        return new ValidateResponse(true, null);
    }

    /**
     * 校验返回对象
     */
    private static class ValidateResponse {
        private boolean validate;
        private YkApiException exception;

        public ValidateResponse(boolean validate, YkApiException exception) {
            this.validate = validate;
            this.exception = exception;
        }

        public boolean isValidate() {
            return validate;
        }

        public Exception getException() {
            return exception;
        }
    }
}
  1. 将传参进行md5加密
public class SignUtil {
    private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};

    private final static String randomBaseStr = "abcdefghijklmnopqrstuvwxyz0123456789";
    private static String SIGN_KEY = "123456"; // 自定义
    private static String CHARSET_NAME = "UTF-8";
    private static final String sign = "sign";

    public static String signMap(String path, Map<String, String[]> map) {
        StringBuilder param = new StringBuilder(path);
        Iterator<Map.Entry<String, String[]>> entries = map.entrySet().iterator();
        Map<String, String[]> params = new TreeMap<>();
        while (entries.hasNext()) {
            Map.Entry<String, String[]> entry = entries.next();
            String key = entry.getKey();
            String[] values = entry.getValue();
            if (values == null) {
                entries.remove();
            }
            if (sign.equals(key)) {
                continue;
            }
            params.put(key, values);
        }

        Iterator<Map.Entry<String, String[]>> entries2 = params.entrySet().iterator();
        while (entries2.hasNext()) {
            Map.Entry<String, String[]> entry = entries2.next();
            String key = entry.getKey();
            String[] values = entry.getValue();
            if (values.length == 1) {
                param.append("&").append(key).append("=").append(values[0]);
            } else {
                param.append("&").append(key).append("=").append(JsonUtil.toJson(values));
            }

        }
        System.out.println(param);
        return MD5Encode(param.toString() + SIGN_KEY, CHARSET_NAME);
    }

    private static String byteArrayToHexString(byte b[]) {
        StringBuffer resultSb = new StringBuffer();
        for (int i = 0; i < b.length; i++)
            resultSb.append(byteToHexString(b[i]));

        return resultSb.toString();
    }

    private static String byteToHexString(byte b) {
        int n = b;
        if (n < 0)
            n += 256;
        int d1 = n / 16;
        int d2 = n % 16;
        return hexDigits[d1] + hexDigits[d2];
    }

    public static String MD5Encode(String origin, String charsetname) {
        String resultString = null;
        try {
            resultString = new String(origin);
            MessageDigest md = MessageDigest.getInstance("MD5");
            if (charsetname == null || "".equals(charsetname))
                resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
            else
                resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));
        } catch (Exception exception) {
        }
        return resultString;
    }
 }
  1. controller层
@PostMapping(value = "/testToken")
@PublicUrl(tokenValidate = true)
public Result<Boolean> testToken(String token) {
    return ResultUtil.success(。。。);
}

最后的最后,博主也是个java新手,记录下工作中的好东西,有什么不对或者还可以提高的地方,欢迎大家留言指正!!!

  • 0
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 可以通过实现 Filter 接口来创建一个过滤器,然后在 Spring Boot 中使用 @Bean 注解将其注册到应用程序中。在过滤器中,可以通过 HttpServletRequest 对象获取请求头中的 token,然后进行验证或者其他操作。具体实现可以参考 Spring Boot 官方文档或者相关教程。 ### 回答2: 在使用Spring Boot中,可以通过创建一个过滤器来拦截token。下面是一个简单的示例,说明如何实现。 首先,需要创建一个自定义的过滤器类,实现javax.servlet.Filter接口。在实现过滤器时,我们可以在doFilter方法中进行token验证的逻辑处理。以下是示例代码: ```java import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class TokenFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 将请求和响应对象转换成HttpServletRequest和HttpServletResponse对象 HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; // 从请求头中获取token String token = httpRequest.getHeader("Authorization"); // 进行token验证的逻辑处理,例如验证token是否过期、是否有效等 // ... // 如果验证通过,将请求继续传递给下一个过滤器或目标资源处理 chain.doFilter(request, response); } // 其他方法,例如init和destroy方法,可以留空不做处理 } ``` 接下来,需要将自定义的过滤器添加到Spring Boot应用程序中。可以使用@Configuration注解将过滤器添加为一个Bean,并使用@Order注解指定过滤器的执行顺序。例如: ```java import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; @Configuration public class FilterConfig { @Bean public FilterRegistrationBean<TokenFilter> tokenFilter() { FilterRegistrationBean<TokenFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new TokenFilter()); // 设置过滤器的URL映射规则,例如/*表示拦截所有请求 registrationBean.addUrlPatterns("/*"); // 设置过滤器的执行顺序 registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); return registrationBean; } } ``` 最后,重新启动Spring Boot应用程序,自定义的过滤器就会拦截所有请求,并对token进行验证处理。 请注意,上述示例只是一个简单的演示,实际应用中可能需要根据具体的需求进行修改和扩展。例如,可以从数据库或缓存中获取token,进行更加复杂的验证逻辑,并在验证失败时返回相应的错误信息。 ### 回答3: 使用Spring Boot实现过滤器拦截Token的步骤如下: 1. 创建一个自定义的过滤器类,实现javax.servlet.Filter接口,并重写doFilter方法。在doFilter方法中,可以通过HttpServletRequest对象获取请求头中的Token信息,并进行相应的验证或处理。 2. 在Spring Boot应用的启动类中,通过添加注解@EnableWebSecurity开启Web安全配置,并通过继承WebSecurityConfigurerAdapter类重写configure方法。 3. 在configure方法中,使用http对象的addFilterBefore方法将自定义的过滤器添加到过滤器链中,指定过滤器在哪个过滤器之前进行拦截。 4. 在过滤器中,可以进行Token验证和处理逻辑。例如,可以使用JWT来生成和验证Token,或将Token存储在Redis中,根据请求的Token进行校验等。 5. 如果Token验证不通过,可以返回相应的错误信息或重定向到登录页面。如果验证通过,可以进行其他的业务逻辑处理。 总之,通过自定义过滤器并将其添加到Spring Boot的过滤器链中,可以在请求到达Controller之前进行Token验证和拦截操作,以实现对请求的安全控制。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值