springboot 集成jwt 如何优雅的获取登录信息
2024-01-03 14:20:30
springboot 集成jwt 如何优雅的获取登录信息
一、前言
通常springboot项目在实战应用中,需要处理用户权限体系,而jwt目前也是业界里主要的实现方案,本表主要为大家讲解一下,在项目如何优雅的集成jwt后并做到,优雅获取登录信息。
二、源码资料
源码资料已为大家准备好: 源码
三、实现思路
1、pom.xml集成jwt相关依赖
2、编写登录信息获取的注解 @LoginUser
3、编写非登录鉴权的注解@Login
3、实现HandlerInterceptor接口的preHandle方法判断请求的接口参数是否添加注解
4、实现HandlerMethodArgumentResolver类的resolveArgument方法
5、将实现类添加至WebMvcConfigurer的addArgumentResolvers方法
6、添加jwt工具类
7、controller层方法对入参添加注解 @Login @LoginUser
8、添加servlet拦截器AuthInterceptor
四、代码案例
1、pom.xml集成jwt相关依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
2、编写登录信息获取的注解 @LoginUser
/**
* Copyright (c) 2018 人人开源 All rights reserved.
*
* https://www.softworld.vip
*
* 版权所有,侵权必究!
*/
package com.jiuzhou.common.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* github地址 http://www.github.com/wanyushu
* gitee地址 http://www.gitee.com/wanyushu
* @author yushu
* @email 921784721@qq.com
**/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser {
}
3、编写非登录鉴权的注解@Login
/**
* Copyright (c) 2018 人人开源 All rights reserved.
*
* https://www.softworld.vip
*
* 版权所有,侵权必究!
*/
package com.jiuzhou.common.annotation;
import java.lang.annotation.*;
/**
* 登录效验
* @author Mark sunlightcs@gmail.com
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Login {
}
4、实现HandlerMethodArgumentResolver类的resolveArgument方法,并判断是否需要登录信息
/**
* Copyright (c) 2016-2019 人人开源 All rights reserved.
*
* https://www.renren.io
*
* 版权所有,侵权必究!
*/
package com.jiuzhou.common.resolver;
import com.jiuzhou.common.annotation.LoginUser;
import com.jiuzhou.common.constants.CommonConstants;
import com.jiuzhou.entity.UserInfo;
import com.jiuzhou.service.UserInfoService;
import org.apache.catalina.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* 有@LoginUser注解的方法参数,注入当前登录用户
*/
@Component
public class LoginUserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Autowired
private UserInfoService userInfoService;
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().isAssignableFrom(UserInfo.class) && parameter.hasParameterAnnotation(LoginUser.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer container,
NativeWebRequest request, WebDataBinderFactory factory) {
//获取用户ID
Object object = request.getAttribute(CommonConstants.USER_KEY, RequestAttributes.SCOPE_REQUEST);
if(object == null){
return null;
}
//获取用户信息
UserInfo userInfo = userInfoService.getById((Long)object);
return userInfo;
}
}
5、将实现类添加至WebMvcConfigurer的addArgumentResolvers方法
package com.jiuzhou.common.configure;
import com.jiuzhou.common.resolver.LoginUserHandlerMethodArgumentResolver;
import com.jiuzhou.intercepter.AuthInterceptor;
import com.jiuzhou.intercepter.RepeatInterceptor;
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;
/**
* github地址 http://www.github.com/wanyushu
* gitee地址 http://www.gitee.com/wanyushu
* @author yushu
* @email 921784721@qq.com
**/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private RepeatInterceptor repeatInterceptor;
@Autowired
private AuthInterceptor authInterceptor;
@Autowired
private LoginUserHandlerMethodArgumentResolver loginUserHandlerMethodArgumentResolver;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(repeatInterceptor)
.addPathPatterns("/questionGroup/**");
registry.addInterceptor(authInterceptor).addPathPatterns("/user/**");
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(loginUserHandlerMethodArgumentResolver);
}
}
6、添加jwt工具类
package com.jiuzhou.common.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* token过期配置
*
* @author Chopper
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "system.auth")
public class JwtProperties {
/**
* token默认过期时间
*/
private long tokenExpireTime = 2;
/**
* 密钥
*/
private String jwtKey;
}
package com.jiuzhou.common.token;
import com.alibaba.fastjson.JSON;
import com.jiuzhou.common.Enum.SecurityEnum;
import com.jiuzhou.common.Enum.UserEnums;
import com.jiuzhou.common.properties.JwtProperties;
import io.jsonwebtoken.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
*/
@Component
public class TokenUtil {
private Logger logger = LoggerFactory.getLogger(TokenUtil.class);
@Autowired
private JwtProperties tokenProperties;
@Autowired
private RedisTemplate redisTemplate;
/**
* 构建token
*
* @param username 主体
* @param claim 私有声明
* @param longTerm 长时间特殊token 如:移动端,微信小程序等
* @param userEnums 用户枚举
* @return TOKEN
*/
public Token createToken(String username, Object claim, boolean longTerm, UserEnums userEnums) {
Token token = new Token();
//访问token
String accessToken = createToken(username, claim, tokenProperties.getTokenExpireTime());
redisTemplate.opsForValue().set(CachePrefix.ACCESS_TOKEN.getPrefix(userEnums) + accessToken, 1,
tokenProperties.getTokenExpireTime(), TimeUnit.MINUTES);
//刷新token生成策略:如果是长时间有效的token(用于app),则默认15天有效期刷新token。如果是普通用户登录,则刷新token为普通token2倍数
Long expireTime = longTerm ? 15 * 24 * 60L : tokenProperties.getTokenExpireTime() * 4; //设置8小时
String refreshToken = createToken(username, claim, expireTime);
redisTemplate.opsForValue().set(CachePrefix.REFRESH_TOKEN.getPrefix(userEnums) + refreshToken, 1, expireTime, TimeUnit.MINUTES);
token.setAccessToken(accessToken);
token.setRefreshToken(refreshToken);
return token;
}
/**
* 获取token信息
* @param token
* @return
*/
public AuthUser getTokenInfo(String token){
Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(SecretKeyUtil.generalKeyByDecoders())
.parseClaimsJws(token).getBody();
} catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | IllegalArgumentException e) {
// throw new ServiceException(ResultCode.USER_AUTH_EXPIRED.code()+"",ResultCode.USER_AUTH_EXPIRED.message());
}
if(null==claims){
return null;
}
return JSON.toJavaObject(JSON.parseObject(claims.get(SecurityEnum.USER_CONTEXT.getValue()).toString()), AuthUser.class);
}
/**
* 刷新token
*
* @param oldRefreshToken 刷新token
* @param userEnums 用户枚举
* @return token
*/
public Token refreshToken(String oldRefreshToken, UserEnums userEnums) {
Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(SecretKeyUtil.generalKeyByDecoders())
.parseClaimsJws(oldRefreshToken).getBody();
} catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | IllegalArgumentException e) {
//token 过期 认证失败等
logger.debug("用户登录信息失效!");
// throw new ServerException(ResultCode.USER_AUTH_EXPIRED.message());
}
//获取存储在claims中的用户信息
String json = claims.get(SecurityEnum.USER_CONTEXT.getValue()).toString();
AuthUser authUser = JSON.toJavaObject(JSON.parseObject(json), AuthUser.class);
String username = authUser.getUserName();
//获取是否长期有效的token
boolean longTerm = authUser.getLongTerm();
//如果缓存中有刷新token &&
if ( null!=redisTemplate.opsForValue().get(CachePrefix.REFRESH_TOKEN + ":" + UserEnums.MEMBER+":" + oldRefreshToken)) {
Token token = new Token();
//访问token
String accessToken = createToken(username, authUser, tokenProperties.getTokenExpireTime());
redisTemplate.opsForValue().set(CachePrefix.ACCESS_TOKEN.getPrefix(userEnums) + accessToken, 1, tokenProperties.getTokenExpireTime(), TimeUnit.MINUTES);
//如果是信任登录设备,则刷新token长度继续延长
Long expirationTime = tokenProperties.getTokenExpireTime() * 2;
if (longTerm) {
expirationTime = 60 * 24 * 15L;
}
//刷新token生成策略:如果是长时间有效的token(用于app),则默认15天有效期刷新token。如果是普通用户登录,则刷新token为普通token2倍数
String refreshToken = createToken(username, authUser, expirationTime);
redisTemplate.opsForValue().set(CachePrefix.REFRESH_TOKEN.getPrefix(userEnums) + refreshToken, 1, expirationTime, TimeUnit.MINUTES);
token.setAccessToken(accessToken);
token.setRefreshToken(refreshToken);
redisTemplate.delete(CachePrefix.REFRESH_TOKEN.getPrefix(userEnums) + oldRefreshToken);
return token;
} else {
throw new RuntimeException(ResultCode.USER_CONNECT_LOGIN_ERROR.message());
}
}
/**
* 生成token
*
* @param username 主体
* @param claim 私有神明内容
* @param expirationTime 过期时间(分钟)
* @return token字符串
*/
private String createToken(String username, Object claim, Long expirationTime) {
//JWT 生成
return Jwts.builder()
//jwt 私有声明
.claim(SecurityEnum.USER_CONTEXT.getValue(), JSON.toJSONString(claim))
//JWT的主体
.setSubject(username)
//失效时间 当前时间+过期分钟
.setExpiration(new Date(System.currentTimeMillis() + expirationTime * 60 * 1000))
//签名算法和密钥
.signWith(SecretKeyUtil.generalKey())
.compact();
}
}
7、controller层方法对入参添加注解 @Login @LoginUser
package com.jiuzhou.controller;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.jiuzhou.common.Enum.UserEnums;
import com.jiuzhou.common.annotation.Login;
import com.jiuzhou.common.annotation.LoginUser;
import com.jiuzhou.common.token.AuthUser;
import com.jiuzhou.common.token.Token;
import com.jiuzhou.common.token.TokenUtil;
import com.jiuzhou.common.vo.UserVo;
import com.jiuzhou.entity.UserInfo;
import com.jiuzhou.service.UserAccountService;
import com.jiuzhou.service.UserInfoService;
import com.jiuzhou.utils.RestResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* github地址 http://www.github.com/wanyushu
* gitee地址 http://www.gitee.com/wanyushu
* @author yushu
* @email 921784721@qq.com
**/
@Api(tags = "【用户】信息表controller",value= "【用户】信息表相关接口")
@RestController
@RequestMapping("user")
public class UserInfoController {
private Logger logger = LoggerFactory.getLogger(UserInfoController.class);
@Autowired
UserAccountService userAccountService;
@Autowired
private UserInfoService userInfoService;
@Autowired
private TokenUtil tokenUtil;
/**
* @return
*/
@Login
@ApiOperation(value = "用户登录接口")
@PostMapping("login")
public RestResult login(@RequestBody UserVo userVo) {
logger.info("用户登录:{}",userVo);
UserInfo userInfo = userInfoService.getOne(Wrappers.<UserInfo>lambdaQuery()
.eq(UserInfo::getEmail, userVo.getAccount()));
AuthUser authUser = new AuthUser(null,userInfo.getUserId(),userInfo.getUserName(),userInfo.getAvatar());
Token token = tokenUtil.createToken(userInfo.getUserName(), authUser, false, UserEnums.MEMBER);
return RestResult.ok(token);
}
/**
* @return
*/
@ApiOperation(value = "查询用户信息")
@GetMapping("info")
public RestResult login(@LoginUser UserInfo userInfo) {
logger.info("用户获取登录信息:{}",userInfo);
return RestResult.ok(userInfo);
}
}
7
/**
* Copyright (c) 2016-2019 人人开源 All rights reserved.
*
* https://www.renren.io
*
* 版权所有,侵权必究!
*/
package com.jiuzhou.intercepter;
import com.jiuzhou.common.annotation.Login;
import com.jiuzhou.common.constants.CommonConstants;
import com.jiuzhou.common.token.AuthUser;
import com.jiuzhou.common.token.TokenUtil;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
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;
/**
* github地址 http://www.github.com/wanyushu
* gitee地址 http://www.gitee.com/wanyushu
* @author yushu
* @email 921784721@qq.com
**/
@Component
public class AuthInterceptor extends HandlerInterceptorAdapter {
@Autowired
private TokenUtil tokenUtil;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
Login annotation;
if(handler instanceof HandlerMethod) {
annotation = ((HandlerMethod) handler).getMethodAnnotation(Login.class);
}else{
return true;
}
//从header中获取token
String token = request.getHeader("token");
//如果header中不存在token,则从参数中获取token
if(StringUtils.isBlank(token)){
token = request.getParameter("token");
}
if(null==token){
return false;
}
AuthUser authUser = tokenUtil.getTokenInfo(token);
//设置userId到request里,后续根据userId,获取用户信息
request.setAttribute(CommonConstants.USER_KEY, authUser.getId());
return true;
}
}
8、添加servlet拦截器AuthInterceptor
/**
* Copyright (c) 2016-2019 人人开源 All rights reserved.
*
* https://www.renren.io
*
* 版权所有,侵权必究!
*/
package com.jiuzhou.intercepter;
import com.jiuzhou.common.annotation.Login;
import com.jiuzhou.common.constants.CommonConstants;
import com.jiuzhou.common.token.AuthUser;
import com.jiuzhou.common.token.TokenUtil;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
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;
/**
* github地址 http://www.github.com/wanyushu
* gitee地址 http://www.gitee.com/wanyushu
* @author yushu
* @email 921784721@qq.com
**/
@Component
public class AuthInterceptor extends HandlerInterceptorAdapter {
@Autowired
private TokenUtil tokenUtil;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
Login annotation;
if(handler instanceof HandlerMethod) {
annotation = ((HandlerMethod) handler).getMethodAnnotation(Login.class);
}else{
return true;
}
//从header中获取token
String token = request.getHeader("token");
//如果header中不存在token,则从参数中获取token
if(StringUtils.isBlank(token)){
token = request.getParameter("token");
}
if(null==token){
return false;
}
AuthUser authUser = tokenUtil.getTokenInfo(token);
//设置userId到request里,后续根据userId,获取用户信息
request.setAttribute(CommonConstants.USER_KEY, authUser.getId());
return true;
}
}
五、案例测试
1、postman发送请求用户登录接口
http://127.0.0.1:8081/user/login
post application/json
{"account":"921784721@qq.com","password":"123456"}
2、获取登录信息接口
http://127.0.0.1:8081/user/login
get
header:{"token":"eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyQ29udGV4dCI6IntcImlkXCI6MSxcImxvbmdUZXJtXCI6ZmFsc2UsXCJ0ZW5hbnRJZFwiOjAsXCJ1c2VyTmFtZVwiOlwi5Luj56CB5rGf5rmWXCJ9Iiwic3ViIjoi5Luj56CB5rGf5rmWIiwiZXhwIjoxNzAzOTY2NDE3fQ.02Cj4KtnXZOeZi530hO3IakFmqhV7hP-6H749mmBdXQ"}
{
"code": 0,
"msg": null,
"data": {
"userId": 1,
"userAccount": "admin",
"userName": "代码江湖",
"avatar": null,
"linkPhone": null,
"email": "921784721@qq.com",
"sex": 1,
"realName": "九州之子",
"status": 1,
"createDate": null,
"updateTime": null
}
}
六、总结
1、在jwt获取登录信息的过程中注解@Login主要用于屏蔽登录的接口
2、@LoginUser 注解用于获取用户的信息
3、注意登录信息的获取在项目实战中,可能仅仅只需要获取userId即可,可关注我后续总结
六、获取更多springboot使用方法请前往 springboot框架使用技巧
文章来源:https://blog.csdn.net/weixin_40951507/article/details/135306137
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!