通过归纳梳理,在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执行顺序如下图所示
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。