SpringBoot+JWT实现登录状态管理
介绍
本文实现登录状态管理采用如下技术,全局异常中心、全局获得用户信息、自定义返回类。
项目地址:https://gitee.com/jiang_ming_hong/spring-boot–jwt.git
实现过程
1.需要使用的依赖包
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--JWT的依赖-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
2.编写自定义返回类
public class R {
private static HashMap map = new HashMap();
public static Map succeed(String msg) {
map.put("msg", msg);
map.put("code", 200);
return map;
}
public static Map succeed(String msg, Object data) {
map.put("code", 200);
map.put("msg", msg);
map.put("data", data);
return map;
}
public static Map succeed(int code, String msg, Object data) {
map.put("msg", msg);
map.put("data", data);
map.put("code", code);
return map;
}
}
3.全局异常类
全局异常中心就是把异常集中在一个类中,方便管理异常。
@Configuration
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
public class GlobalException {
@ExceptionHandler(SignatureVerificationException.class)
public Map testException(SignatureVerificationException signatureVerificationException) {
return R.succeed("签名异常");
}
@ExceptionHandler(TokenExpiredException.class)
public Map testException(TokenExpiredException tokenExpiredException) {
return R.succeed("token过期");
}
@ExceptionHandler(AlgorithmMismatchException.class)
public Map testException(AlgorithmMismatchException AlgorithmMismatchException) {
return R.succeed("加密算法不匹配");
}
@ExceptionHandler(Exception.class)
public Map testException(Exception exception) {
return R.succeed("出错了");
}
}
4.全局用户信息类
根据token值取出用户的信息,当需要用户这些信息时直接取就行,不用从数据库中取。
public class BaseUserInfo {
private static final ThreadLocal<Map<String, String>> THREAD_LOCAL = new ThreadLocal<>();
//判断线程map是否为空,为空就添加一个map
public static Map<String, String> getLocalMap() {
Map<String, String> map = THREAD_LOCAL.get();
if (map == null) {
map = new HashMap<>(10);
THREAD_LOCAL.set(map);
}
return map;
}
//把用户信息添加到线程map中
public static void set(String key, String name) {
Map<String, String> map = getLocalMap();
map.put(key, name);
}
//获得线程map中的数据
public static String get(String key) {
Map<String, String> map = getLocalMap();
return map.get(key);
}
}
5.JWT封装类
@Slf4j
public class JWTUtils {
/**
* 盐值
*/
private static final String SING="123456";
/**
* 生成令牌
* @param map payload载荷声明参数
* @return
*/
public static String getToken(Map<String,String> map){
//获取日历对象
Calendar calendar=Calendar.getInstance();
//默认7天过期
calendar.add(Calendar.DATE, 7);
//新建一个JWT的Builder对象
JWTCreator.Builder builder = JWT.create();
//将map集合中的数据设置进payload
map.forEach((k,v)->{
builder.withClaim(k, v);
});
//设置过期时间和签名
log.info("时间={}"+calendar.getTime());
String sign = builder.withExpiresAt(calendar.getTime()).sign(Algorithm.HMAC256(SING));
return sign;
}
/**
* 验签并返回DecodedJWT
* @param token 令牌
*/
public static DecodedJWT getTokenInfo(String token){
return JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
}
}
6.用户枚举
@Getter
public enum UserInfoEnu {
user("name"),
pass("pass");
private String code;
UserInfoEnu(String code) {
this.code = code;
}
}
7.配置拦截器
控制那些请求被拦截或者那些请求不被拦截
@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {
/**
* 自定义拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyHandler())
//拦截全部
.addPathPatterns("/**")
//不需拦截的请求
.excludePathPatterns(this.exclud());
}
/**
* 不需拦截的请求
*/
public String[] exclud(){
String[] urls= {
"/login"
};
return urls;
}
}
8.编写自定义拦截器
验证Token信息是否正确,不正确提示相应的错误
public class MyHandler implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
//获得请求头里的TOKEN数据
String token = request.getHeader("token");
//根据token解析数据,因为配置了全局异常处理中心,如果有异常会在全局异常中心处理异常
DecodedJWT tokenInfo = JWTUtils.getTokenInfo(token);
//获得用户信息
String name = tokenInfo.getClaim("name").asString();
String pass = tokenInfo.getClaim("pass").asString();
//存储用户信息到ThreadLocal中
BaseUserInfo.set("name",name);
BaseUserInfo.set("pass",pass);
return true;
}
}
}
9.请求类
getBaseUserInfo()方法中BaseUserInfo.get()可以获得用户信息。
@RestController
public class testController {
/**
* 获得用户信息
*
* @return
*/
@PostMapping("test")
public String getBaseUserInfo() {
//获得用户信息
String name = BaseUserInfo.get(UserInfoEnu.user.getCode());
String pass = BaseUserInfo.get(UserInfoEnu.pass.getCode());
return "name:" + name + "-----pass:" + pass;
}
/**
* 模拟用户登录
*
* @param name
* @param pass
* @return
*/
@GetMapping("/login")
public Map login(String name, String pass) {
Map<String, String> map = new HashMap<>();
map.put("name", name);
map.put("pass", pass);
String token = JWTUtils.getToken(map);
return R.succeed(200, "登录成功", token);
}
}
测试效果图
模拟登录
登录后获得用户信息
在用户登录成功后,因该把token值存在前端的请求头里。这里只是在Postman中请求。
用户一信息
用户二信息
把token值故意写错
可以看到提示未登录