SpringMVC框架下JWT验证的几种方式

782 阅读2分钟

通过归纳梳理,在SpringMVC框架下,对JWT验证通常有3种方式

1.直接在controller方法中对jwt进行验证

@RestController
@RequestMapping(value = "/user")
public class UserController {
    @Autowired
    private JWTUtil jwtUtil;
    
    @RequestMapping("/index")
    public String index(@requestParam(value='jwt') String jwt){
    try{
        Claims claims=jwtUtil.parseJWT(jwt);
        String username=claims.get("name").toString();
        if(username.equals("wangying"))
            return true;
    }catch (SignatureException e){
          response.sendRedirect("/user/loginusername=wangying&password=123456");
        return false;
    }
}

将jwt参数通过request header 或者 body传递至后台接口,后台接口接收到jwt后,通过signature验证其有效性并解析的到payload,基于payload传递的参数进行权限认证。这种方式实现起来比较简单直观,但是需要为每一个认证的接口添加jwt参数,并且jwt参数验证代码间存在大量重复。

2.通过SpringMVC框架inteceptor功能实现

Inteceptor在SpringMVC框架中位于DispatchServlet与Controller之间,基于AOP编程思想,实现对请求的拦截。

SpringBoot中通过实现HandlerInteceptor接口定义拦截器,每个拦截器包含preHandle、postHandle和afterComplete三个方法,其中preHandle方法在处理请求前执行,postHandle在执行完Controller返回视图前执行,afterComplete在返回视图后执行。

public class JwtInterceptor implements HandlerInterceptor {
    @Autowired
    private JWTUtil jwtUtil;
    //在执行Controller之前
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String jwt=request.getParameter("jwt");
        try{
            Claims claims=jwtUtil.parseJWT(jwt);
            String username=claims.get("name").toString();
            if(username.equals("wangying"))
                return true;
        }catch (SignatureException e){
            response.sendRedirect("/user/login.html");
            return false;
        }
        return false;
    }
    //在执行完Controller以后,渲染视图以前
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {

    }
    //在完成视图渲染以后
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {

    }
}

在@Configuration配置类中通过实现WebMvcConfigurer接口实现Inteceptor的注册。

@Configuration
public class MyWebConfig implements WebMvcConfigurer {

    @Bean
    public JwtInterceptor jwtInterceptor(){
        return new JwtInterceptor();
    }
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtInterceptor()).addPathPatterns("/**").excludePathPatterns("/user/login");
    }
}

如果存在多个Inteceptor,比如A、B、C,其顺序可通过order参数指定,假设其顺序为ABC,则多个Inteceptor的方法间执行顺序为:A-preHandle、B-preHandle、C-preHandle、C-postHandle、B-postHandle、A-postHandle、C-afterComplete、B-afterComplete、A-afterComplete。

3.基于Servlet Filter 实现

Inteceptor是SpringMVC框架实现的功能,Filter是Servlet实现的功能。Filter与Inteceptor执行顺序如下图所示

35fb4df789c545d1846c11f116eea23c.png SpringBoot框架中,可以通过配置类注入FilterRegistionBean对象配置Filter

@Configuration
public class MyWebConfig implements WebMvcConfigurer {
    @Bean
    public JwtFilter jwtFilter(){
        return new JwtFilter();
    }
    @Bean
    public FilterRegistrationBean filterRegistrationBean(){
        FilterRegistrationBean register=new FilterRegistrationBean();
        register.setFilter(jwtFilter());
        Map<String,Object> params=new HashMap<>();
        params.put("noFilterUrl","/user/login");
        register.setInitParameters(params);
        List<String> urlPatterns=new ArrayList<>();
        urlPatterns.add("/*");
        register.setUrlPatterns(urlPatterns);
        return register;
    }
}

通过实现Filter接口定义Filter

public class JwtFilter implements Filter {
    @Autowired
    private JWTUtil jwtUtil;
    private List<String> noFilterUrls=new ArrayList<>();
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String noFilterUrlStr=filterConfig.getInitParameter("noFilterUrl");
        noFilterUrls=new ArrayList<>(Arrays.asList(noFilterUrlStr.split(",")));
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        String url=((HttpServletRequest)servletRequest).getRequestURI();
        boolean flag=false;
        for(String noFilterUrl:noFilterUrls){
            if(url.contains(noFilterUrl)){
                flag=true;
                break;
            }
        }
        if(!flag){
            String jwt=servletRequest.getParameter("jwt");
            try{
                Claims claims=jwtUtil.parseJWT(jwt);
                String username=claims.get("name").toString();
                if(username.equals("wangying"))
                    filterChain.doFilter(servletRequest,servletResponse);
            }catch (SignatureException e){
                servletResponse.setContentType("application/json");
                servletResponse.setCharacterEncoding("UTF-8");
                servletResponse.getWriter().write("请重新登录");
            }
        }else{
            filterChain.doFilter(servletRequest,servletResponse);
        }
    }

    @Override
    public void destroy() {

    }
}

Filter接口包含3个方法,其中init方法用户接受设置初始化参数,由于FilterRegisterBean没有直接设置ExcludeUrl的方法,因此通常通过init方法的FilterConfig参数传递需要Exclude的url,并在doFilter中通过自定义逻辑排除上述Url。