一、参考流程
jwt解析出来的可以放在请求头也可以放在ThreadLocal中,根据项目而定(本文以ThreadLocal为例)
二、详细解决
1、引入依赖
<!--JWT-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
2、封装一些常用的工具类
特别注意:jwt生成工具中,有jwt的key这些。可以理解成加密工具中的盐和key主要
/**
* jwt claims key
*/
public final static String JWT_CLAIMS_KEY = "jwt_claims_key";
/**
* jwt secret key
*/
public static final String JWT_SECRET_KEY = "create_site";
- 1、JWT生成和解析工具
/***
* JWT生成工具
* @author xusj
* <br>CreateDate 2022/8/17 15:44
*/
@Slf4j
public class JwtTokenUtils {
/**
* 生成jwttoken
*/
public static String generateJwtToken(Long userId, String userName, String userAccount, Long siteId) {
AccToken accToken = new AccToken(userId, userName, userAccount, siteId);
//超时时间设置为7天
accToken.setExpireDateTime(new Date((new Date()).getTime() + 3600L * 24 * 300 * 1000));
ObjectMapper mapper = new ObjectMapper();
String ac;
try {
//转化为一个json
ac = mapper.writeValueAsString(accToken);
} catch (JsonProcessingException e) {
e.printStackTrace();
throw new ServiceException(LoginEnum.LOGIN_JSON_FAIL);
}
Map<String, Object> claims = new HashMap<>();
claims.put(LoginEnum.JWT_CLAIMS_KEY, ac);
//调用jwt中的方法,生成一个JwtBuilder(jwt创建者)
JwtBuilder builder = Jwts.builder();
//使用对称加密生成jwt token
builder.setClaims(claims).signWith(SignatureAlgorithm.HS512, LoginEnum.JWT_SECRET_KEY);
return builder.compact();
}
/**
* 解析jwtToken
*/
public static AccToken parseToken(String jwtToken) {
AccToken accToken = null;
try {
JwtParser parser = Jwts.parser();
//如果解析失败,会抛出异常
Jws<Claims> claimsJws = parser.setSigningKey(LoginEnum.JWT_SECRET_KEY).parseClaimsJws(jwtToken);
Claims claims = claimsJws.getBody();
String acJson = (String) claims.get(LoginEnum.JWT_CLAIMS_KEY);
ObjectMapper mapper = new ObjectMapper();
//TODO XUSJ 还原json
accToken = mapper.readValue(acJson, AccToken.class);
} catch (Exception e) {
log.info("==========token解析失败===========");
e.printStackTrace();
}
//如果acctoken==null,说明token是无效的
return accToken;
}
}
- 2、token参数封装工具实体类
package com.xiangzheng.website.utils.token;
import java.util.Date;
/***
* token
*
* @author xusj
* <br>CreateDate 2022/8/17 15:48
*/
public class AccToken {
private Long userId;
private String userName;
private String userAccount;
//jwttoken的超时时间
private Date expireDateTime;
private Long siteId;
public Long getSiteId() {
return siteId;
}
public void setSiteId(Long siteId) {
this.siteId = siteId;
}
public AccToken() {
}
public AccToken(Long userId, String userName, String userAccount, Long siteId) {
this.userId = userId;
this.userName = userName;
this.userAccount = userAccount;
this.siteId = siteId;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserAccount() {
return userAccount;
}
public void setUserAccount(String userAccount) {
this.userAccount = userAccount;
}
public Date getExpireDateTime() {
return expireDateTime;
}
public void setExpireDateTime(Date expireDateTime) {
this.expireDateTime = expireDateTime;
}
}
- 3、ThreadLocal使用存放用户信息(非必须的,按照项目需求一些参数可以放在请求头中【通过网关】,让后面的都在请求头中去参数也行)
package com.xiangzheng.website.utils.threadLocalUtils;
import com.xiangzheng.website.utils.token.AccToken;
import org.springframework.stereotype.Component;
/**
* 持有用户信息,用于代替session对象
* 使用ThreadLocal实现
*
* @author xusj
* <br>CreateDate 2022/8/18 13:27
*/
@Component
public class HostHolder {
/**
* ThreadLocal本质是以线程为key存储元素
*/
private final ThreadLocal<AccToken> users = new ThreadLocal<>();
public void setUser(AccToken user) {
users.set(user);
}
public AccToken getUser() {
return users.get();
}
public void clear() {
users.remove();
}
}
3、编写登录接口逻辑
- 1、这里是伪代码,主要模拟登录验证之后,生成JWT的过程
–前面还有登录验证的逻辑,登录成功才会生成JWTToken,返回给前端,并且设置缓存
后端生成JWT注意点如下:
- 1.1:放到缓存中
- 1.2:token返给前端,前端放在请求头中
// 前面还有登录验证的逻辑,登录成功才会生成JWTToken
// 生成JWT
String jwtToken = JwtTokenUtils.generateJwtToken(user.getId(), user.getUserName(), user.getUserAccount(), loginDTO.getSiteId());
// 封装返回登录成功返回
UserInfoVO userInfoVO = new UserInfoVO();
userInfoVO.setUserImg(user.getUserPhoto());
userInfoVO.setUserName(user.getUserName());
userInfoVO.setSiteId(loginDTO.getSiteId());
userInfoVO.setUserAccount(user.getUserAccount());
userInfoVO.setToken(jwtToken);
try {
redisCache.setCacheObject(key, jwtToken, 7, TimeUnit.DAYS);
} catch (Exception e) {
throw new ServiceException("缓存异常");
}
return userInfoVo;
4、编写拦截器
拦截器的目的:
1、拦截请求头中的参数,解析并校验
2、这里是将解析出来的参数放到ThreadLocal中;也可以通过网关放在请求头中
3、如果使用ThreadLocal一定要请求结束之后清除
4、之后要使用直接通过ThreadLoacl中使用就行了
5、记得校验缓存中的token是否过期
/**
* 拦截器
*
* @author xusj
* <br>CreateDate 2022/6/6 15:18
*/
@Slf4j
public class AuthInterceptor implements HandlerInterceptor {
@Resource
private RedisCache redisService;
@Resource
private HostHolder hostHolder;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 校验请求头
log.info("请求拦截====>{}", request.getRequestURI());
String accessToken = request.getHeader("Authorization");
if (StringUtils.isEmpty(accessToken)) {
throw new ServiceException(LoginEnum.AUTH_FAIL);
}
// 解析JWT,解析失败异常。获取请求头中的信息
AccToken accToken = JwtTokenUtils.parseToken(accessToken);
// 判断是否过期
String accessTokenRedis = (String) redisService.getCacheObject(RedisConstants.USER_TOKEN_INFORMATION_AUTH + accToken.getUserId() + accToken.getUserAccount() + accToken.getSiteId());
if (StringUtils.isEmpty(accessTokenRedis)) {
throw new ServiceException(LoginEnum.LOGIN_EXPIRED);
}
// TODO XUSJ 放userId和siteId到请求头中,便于后面使用
// 将token解码后的数据存入当前线程的ThreadLocalMap中
hostHolder.setUser(accToken);
log.info("=========当前threadLocal中的参数{}=========", JSON.toJSONString(accToken));
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 请求结束,将保存的信息清除
hostHolder.clear();
}
}
三、总结
1、这图比较官方,上面是我自己的图比较糙
2、ThreadLocal和请求头的选择按照项目觉得即可
3、贴一下我在上面使用到的自己封装的Redis工具
package com.xiangzheng.website.utils.redis;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @author xusj
* <br>CreateDate 2022/6/6 18:59
*/
@Component
@SuppressWarnings("all")
public class RedisCache {
@Resource
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout) {
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key) {
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key) {
return redisTemplate.delete(key);
}
/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public long deleteObject(final Collection collection) {
return redisTemplate.delete(collection);
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList) {
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key) {
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext()) {
setOperation.add(it.next());
}
return setOperation;
}
/**
* 获得缓存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key) {
return redisTemplate.opsForSet().members(key);
}
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey) {
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern) {
return redisTemplate.keys(pattern);
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/96198.html