package com.gkhy.exam.framework.web.service;
|
|
import cn.hutool.crypto.digest.DigestUtil;
|
import com.gkhy.exam.common.constant.CacheConstant;
|
import com.gkhy.exam.common.domain.model.LoginUser;
|
import com.gkhy.exam.common.exception.ApiException;
|
import com.gkhy.exam.common.utils.RedisUtils;
|
import com.gkhy.exam.common.utils.StringUtils;
|
import io.jsonwebtoken.Claims;
|
import io.jsonwebtoken.Jwts;
|
import io.jsonwebtoken.SignatureAlgorithm;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.stereotype.Component;
|
|
import javax.servlet.http.HttpServletRequest;
|
import java.util.Date;
|
import java.util.HashMap;
|
import java.util.Map;
|
import java.util.concurrent.TimeUnit;
|
|
@Slf4j
|
@Component
|
public class TokenService {
|
|
//令牌自定义标识
|
public static final String HEADER="Authorization";
|
//令牌密钥
|
public static final String SECRET="gkhy123456@";
|
|
public static final String CLAIM_KEY_USERNAME = "sub";
|
//令牌有效期
|
public static final long EXPIRATION=12 * 60 * 60; //JWT的超期限时间(60*60*12)s
|
|
protected static final long MILLIS_SECOND = 1000L;
|
|
protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
|
|
private static final Long MILLIS_MINUTE_TEN = 20 * MILLIS_MINUTE;
|
|
@Autowired
|
private RedisUtils redisUtils;
|
|
|
public LoginUser getLoginUser(HttpServletRequest request){
|
String token=getToken(request);
|
if(!StringUtils.isBlank(token)){
|
try
|
{
|
// 解析对应的权限以及用户信息
|
String tagUsername = getUserNameFromToken(token);
|
if(StringUtils.isBlank(tagUsername)){
|
return null;
|
}
|
String userKey = getTokenKey(tagUsername.substring(0,tagUsername.lastIndexOf("_")));
|
LoginUser user = (LoginUser) redisUtils.get(userKey);
|
return user;
|
}
|
catch (Exception e)
|
{
|
log.error("获取用户信息异常'{}'", e.getMessage());
|
}
|
}
|
return null;
|
}
|
|
|
/**
|
* 删除用户身份信息
|
*/
|
public void delLoginUser(String username) {
|
if (StringUtils.isNotEmpty(username))
|
{
|
String userKey = getTokenKey(username);
|
redisUtils.del(userKey);
|
}
|
}
|
|
/**
|
* 创建令牌
|
*
|
* @param username 用户信息
|
* @return 令牌
|
*/
|
public String createToken(String username) {
|
Map<String, Object> claims = new HashMap<>();
|
claims.put(CLAIM_KEY_USERNAME, username);
|
return createToken(claims);
|
}
|
|
public String createToken(Map<String, Object> claims){
|
return Jwts.builder()
|
.setClaims(claims)
|
.setExpiration(generateExpirationDate())
|
.signWith(SignatureAlgorithm.HS512, SECRET)
|
.compact();
|
}
|
|
/**
|
* 从token中获取JWT中的负载
|
*/
|
private Claims getClaimsFromToken(String token) {
|
Claims claims = null;
|
try {
|
claims = Jwts.parser()
|
.setSigningKey(SECRET)
|
.parseClaimsJws(token)
|
.getBody();
|
} catch (Exception e) {
|
log.error("JWT格式验证失败:{}", token);
|
}
|
return claims;
|
}
|
|
/**
|
* 从token中获取登录用户名
|
*/
|
public String getUserNameFromToken(String token) {
|
String username;
|
try {
|
Claims claims = getClaimsFromToken(token);
|
username = claims.getSubject();
|
} catch (Exception e) {
|
username = null;
|
}
|
return username;
|
}
|
|
/**
|
* 从token中获取过期时间
|
*/
|
private Date getExpiredDateFromToken(String token) {
|
Claims claims = getClaimsFromToken(token);
|
return claims.getExpiration();
|
}
|
|
|
/**
|
* 验证token是否还有效
|
*
|
* @param token 客户端传入的token
|
* @param userDetails 从数据库中查询出来的用户信息
|
*/
|
public boolean validateToken(String token, UserDetails userDetails) {
|
String tagUsername = getUserNameFromToken(token);
|
String username=tagUsername.substring(0,tagUsername.lastIndexOf("_"));
|
if(StringUtils.isBlank(username)||!username.equals(userDetails.getUsername())){
|
return false;
|
}
|
String tokenKey=redisUtils.generateKey(CacheConstant.SYS_USER_TOKEN+md5Encode(token));
|
String userKey=redisUtils.generateKey(CacheConstant.SYS_USER_TOKEN+username);
|
String cacheToken= (String) redisUtils.get(tokenKey);
|
if(StringUtils.isBlank(cacheToken)||isTokenExpired(cacheToken)){
|
return false;
|
}
|
if(isNeedUpdate(cacheToken)){
|
String newToken=createToken(tagUsername);
|
// 设置超时时间
|
redisUtils.set(tokenKey,newToken,EXPIRATION,TimeUnit.SECONDS);
|
redisUtils.expire(userKey,(EXPIRATION)+2);
|
}
|
return true;
|
}
|
|
|
|
/**
|
* 判断token是否已经失效
|
*/
|
private boolean isTokenExpired(String token) {
|
Date expiredDate = getExpiredDateFromToken(token);
|
return expiredDate.before(new Date());
|
}
|
|
|
/**
|
* 生成token的过期时间
|
*/
|
private Date generateExpirationDate() {
|
return new Date(System.currentTimeMillis() + EXPIRATION * 1000);
|
}
|
|
|
public boolean isNeedUpdate(String token){
|
Date expertsAt =null;
|
try {
|
Claims claims = getClaimsFromToken(token);
|
expertsAt = claims.getExpiration();
|
}catch (Exception e){
|
throw new ApiException("token非法无效");
|
}
|
//如果剩余过期时间少于过期时常的一半时 需要更新
|
return (expertsAt.getTime()-System.currentTimeMillis()) < MILLIS_MINUTE_TEN;
|
}
|
|
|
/**
|
* 获取请求token
|
*
|
* @param request
|
* @return token
|
*/
|
private String getToken(HttpServletRequest request)
|
{
|
return request.getHeader(HEADER);
|
}
|
|
|
/**
|
* 获取redis key
|
* @param username
|
* @return
|
*/
|
private String getTokenKey(String username)
|
{
|
return redisUtils.generateKey(CacheConstant.LOGIN_TOKEN_KEY + username);
|
}
|
|
|
/**
|
* md5加密
|
* @param token
|
* @return
|
*/
|
public static String md5Encode(String token){
|
return DigestUtil.md5Hex(token);
|
}
|
|
|
/**
|
* 清空旧的登录信息,保证同时只有一个用户再登录,后登录用户会挤掉前一个用户
|
* @param username
|
*/
|
public void cacheUserToken(String username,String newToken){
|
String userKey = redisUtils.generateKey(CacheConstant.SYS_USER_TOKEN + username);
|
String oldToken = (String) redisUtils.get(userKey);
|
if (StringUtils.isNotBlank(oldToken)) {
|
String oldTokenkey = redisUtils.generateKey(CacheConstant.SYS_USER_TOKEN +md5Encode(oldToken));
|
redisUtils.del(oldTokenkey);
|
redisUtils.del(userKey);
|
}
|
redisUtils.set(userKey, newToken,(TokenService.EXPIRATION)+2, TimeUnit.SECONDS);
|
String tokenKey= redisUtils.generateKey(CacheConstant.SYS_USER_TOKEN+md5Encode(newToken));
|
// 设置超时时间
|
redisUtils.set(tokenKey,newToken,TokenService.EXPIRATION, TimeUnit.SECONDS);
|
}
|
|
|
|
}
|