JWT+SpringBoot+SpringMVC参数解析器

简介

大家以前都使用过session存储信息,有的交给容器创建,有的存储到mysql或者redis,这次项目用到了JWT,我们把用户的信息和登录的过期时间都封装到一个token字符串里,客户端每次请求只需要在头信息里携带token即可,话不多说,下面是目录结构.

一.annonation注解

package com.demo.annotation;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface IgnoreLogin {
} 

该注解主要作用是过滤掉请求拦截器,使用该注解就不会对该请求进行拦截(权限校验),具体使用下面讲.

package com.demo.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 登录用户信息
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser {} 

该注解作用是SpringMVC参数解析器,类似于RequestBody注解(希望大家了解springmvc的参数解析机制),和我们后面的resolver相关联.

二.bean实体类

package com.demo.bean;

public class User {private long userId;private String userName;private String password;忽略get/set
} 

我们的用户信息

package com.demo.bean;

public class Business {private String str;private int num;忽略get/set
} 

我们的业务参数

三.config配置信息

package com.demo.config;

import com.demo.interceptor.AuthorizationInterceptor;
import com.demo.resolver.UserArgumentResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

/**
 * MVC配置
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {@Autowiredprivate AuthorizationInterceptor authorizationInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(authorizationInterceptor).addPathPatterns("/**");//注入我们自定义的拦截器,拦截所有请求}@Overridepublic void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {resolvers.add(new UserArgumentResolver());//注入我们的用户参数解析器}
} 

四.controller

package com.demo.controller;

import com.demo.annotation.IgnoreLogin;
import com.demo.annotation.LoginUser;
import com.demo.bean.Business;
import com.demo.bean.User;
import com.demo.util.JwtUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
public class UserController {private Logger logger = LoggerFactory.getLogger(getClass());@Autowiredprivate JwtUtils jwtUtils;@PostMapping(value = "/login")@IgnoreLoginpublic String login() {//在此 我们不做登录检验 假设检验成功User user = new User();user.setUserId(9527);user.setUserName("小星星");return jwtUtils.generateToken(user);//这里只是为了测试只返回token,(请求不含IgnoreLogin注解时需要将token放在头信息里)}@PostMapping("/business")public User business(@RequestBody Business business, @LoginUser User user) {//在业务逻辑可以使用注解将我们的user注入进来logger.info("用户信息参数id:{},姓名:{}", user.getUserId(), user.getUserName());logger.info("我们的业务参数:{},{}", business.getStr(), business.getNum());return user;}
} 

可以看到当我们登陆成功后我们可以生成一个token字符串返回给客户端,这个字符串包含了用户信息和时间信息(jwt机制),同时我们做了一个模仿业务的请求,business是我们的业务参数,user是我们根据客户端上发的token解析出来的,下面会讲到如何解析.可以看到只要我们需要user的参数,我们就可以直接使用LoginUser注解和User就可以直接得到,非常方便,客户端并不需要将我们的用户信息参杂到我们的业务参数中.相对安全。

五.exception

package com.demo.exception;

public class RRException extends RuntimeException {private static final long serialVersionUID = 1L;private String msg;private int code = 500;

} 

这里我就不解析了,根据需求可以和客户端协商相应的错误码

六.interceptor拦截器

package com.demo.interceptor;

import com.demo.annotation.IgnoreLogin;
import com.demo.exception.RRException;
import com.demo.util.JwtUtils;
import io.jsonwebtoken.Claims;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 权限(Token)验证
 */
@Component
public class AuthorizationInterceptor extends HandlerInterceptorAdapter {@Autowiredprivate JwtUtils jwtUtils;public static final String USER_KEY = "user";private Logger logger = LoggerFactory.getLogger(getClass());@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (!(handler instanceof HandlerMethod) || ((HandlerMethod) handler).getMethodAnnotation(IgnoreLogin.class) != null) {//如果不是HandlerMethod或者忽略登录logger.info("无需token校验,handler:{}", handler);return true;}//获取用户凭证String token = request.getHeader(jwtUtils.getHeader());if (StringUtils.isBlank(token)) {token = request.getParameter(jwtUtils.getHeader());}//凭证为空if (StringUtils.isBlank(token)) {throw new RRException(jwtUtils.getHeader() + "不能为空", HttpStatus.UNAUTHORIZED.value());}Claims claims = jwtUtils.getClaimByToken(token);if (claims == null || jwtUtils.isTokenExpired(claims.getExpiration())) {throw new RRException(jwtUtils.getHeader() + "失效,请重新登录", HttpStatus.UNAUTHORIZED.value());}//设置userId到request里,后续根据userId,获取用户信息request.setAttribute(USER_KEY, jwtUtils.getUser(claims));return true;}
} 

我们会过滤掉不是HandlerMethod的请求和带有IgnoreLogin的注解(并不是所有方法都需要校验,例如登录请求,支付回调请求),我们会取出客户端发出的token,解析出来并判断是否过期,没有token或者已过期我们可以需要返回一个错误码给客户端然后重新登录,当我们校验成功后我们会取出用户信息放入到request里(后面会在参数解析器里解析出来),这也是这个拦截器的精髓,既能校验又能获取用户的信息.

七.resolver参数解析器

package com.demo.resolver;

import com.demo.annotation.LoginUser;
import com.demo.bean.User;
import com.demo.interceptor.AuthorizationInterceptor;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import javax.servlet.http.HttpServletRequest;

/**
 * 用户参数解析器
 */
public class UserArgumentResolver implements HandlerMethodArgumentResolver {@Overridepublic boolean supportsParameter(MethodParameter parameter) {return parameter.hasParameterAnnotation(LoginUser.class);}@Overridepublic Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);User user = (User) request.getAttribute(AuthorizationInterceptor.USER_KEY);return user;}
} 

springmvc的参数解析器,需要继承HandlerMethodArgumentResolver,有两个方法,第一个就是支持什么类型的参数,可以看到我们支持拥有LoginUser注解的参数,第二个方法是从request里取出我们在拦截器中放入的user并返回,这样就实现了user对象的注入.

八.JwtUtils

package com.demo.util;

import com.alibaba.fastjson.JSONObject;
import com.demo.bean.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * jwt工具类
 */
@Component
@ConfigurationProperties(prefix = "jwt")
public class JwtUtils {private Logger logger = LoggerFactory.getLogger(getClass());private long expire;private String secret;private String header;/** * 生成jwt token */public String generateToken(User user) {Date nowDate = new Date();//过期时间Date expireDate = new Date(nowDate.getTime() + expire * 1000);return Jwts.builder().setHeaderParam("typ", "JWT").setSubject(JSONObject.toJSONString(user)).setIssuedAt(nowDate).setExpiration(expireDate).signWith(SignatureAlgorithm.HS512, secret).compact();}/** * 解析出来claim * @param token * @return */public Claims getClaimByToken(String token) {try {return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();} catch (Exception e) {logger.debug("validate is token error ", e);return null;}}/** * 得到user * @param claims * @return */public User getUser(Claims claims) {return JSONObject.parseObject(claims.getSubject(), User.class);}/** * token是否过期 * @return true:过期 */public boolean isTokenExpired(Date expiration) {return expiration.before(new Date());}public String getSecret() {return secret;}public void setSecret(String secret) {this.secret = secret;}public long getExpire() {return expire;}public void setExpire(long expire) {this.expire = expire;}public String getHeader() {return header;}public void setHeader(String header) {this.header = header;}
} 

expire过期时间,secret密钥,header头信息名称 这些数据在application.yml里,这里我们会根据User对象生成一个token字符串,根据token取出claims对象,这里就包含了我们的过期时间和之前我们所存的user信息.

九.springboot启动和yml参数

package com.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootStart {public static void main(String[] agrs) {SpringApplication.run(SpringBootStart.class, agrs);}
}

application.yml
jwt:#加密秘钥secret: f4e2e5203fg45sf45g4de581c0f9eb5#token,单位秒expire: 6000header: token 

十.总结

代码随少,五脏俱全,在这里我们梳理一下流程.

1:用户上发登录请求,我们会返回一个token(包含校验信息和用户信息).
2:客户端上发请求需要携带token到头信息里服务器需要验证.
3:服务器拦截请求取出token并进行解析,把user信息存入到request中.
4:在springmvc我们增加了参数解析器,将user从request取出并返回,这时候就完成了参数的解析和注入.
5:可以在controller逻辑代码使用我们的user对象了.

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值