Token验证处理是指在客户端和服务端之间进行身份验证和授权的过程。在这个过程中,客户端通常会提供一个令牌(Token),用于证明其合法性和权限。服务端接收到该令牌后,需要对其进行验证,以确定该请求是否来自合法的客户端。
JWT是一种常见的Token验证处理方式。
JWT(JSON Web Token)由三部分组成,它们分别是头部(Header)、载荷(Payload)和签名(Signature)。每个部分都使用Base64编码进行序列化,并使用点号(.)作为分隔符。
{
//"alg"表示所使用的算法(此处为HMAC SHA-256)
"alg": "HS256",
//"typ"表示令牌的类型(此处为JWT)
"typ": "JWT"
}
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJzdWIiOiAiMTIzNDU2Nzg5MCIsIm5hbWUiOiAiSm9obiBEb2UiLCAiaWF0IjogMTUxNjIzOTAyMn0
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
package com.muyuan.framework.web.service;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import com.muyuan.common.constant.Constants;
import com.muyuan.common.core.domain.model.LoginUser;
import com.muyuan.common.core.redis.RedisCache;
import com.muyuan.common.utils.ServletUtils;
import com.muyuan.common.utils.StringUtils;
import com.muyuan.common.utils.ip.AddressUtils;
import com.muyuan.common.utils.ip.IpUtils;
import com.muyuan.common.utils.uuid.IdUtils;
import eu.bitwalker.useragentutils.UserAgent;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
/**
* token验证处理
*
*
*/
@Component
public class TokenService {
// 令牌自定义标识
@Value("${token.header}")
private String header;
// 令牌秘钥
@Value("${token.secret}")
private String secret;
// 令牌有效期(默认30分钟)
@Value("${token.expireTime}")
private int expireTime;
protected static final long MILLIS_SECOND = 1000;
protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;
@Autowired
private RedisCache redisCache;
/**
* 获取用户身份信息
*
* @return 用户信息
*/
public LoginUser getLoginUser(HttpServletRequest request) {
// 获取请求携带的令牌
String token = getToken(request);
//判断一个字符串是否为非空串(详见字符串工具类)
if (StringUtils.isNotEmpty(token)) {
//Claims对象,它包含了Payload部分的信息,也就是我们在生成Token时添加的各种自定义属性。
// 例如,如果我们在生成Token时添加了用户名、角色等信息,
// 那么在解析Token时就可以通过claims.get("username")、claims.get("role")等方法来获取这些信息。
Claims claims = parseToken(token);
// 解析对应的权限以及用户信息
//令牌前缀
//public static final String LOGIN_USER_KEY = "login_user_key";
String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
String userKey = getTokenKey(uuid);
//redisCache.getCacheObject获得缓存的基本对象(详见spring redis 工具类)
LoginUser user = redisCache.getCacheObject(userKey);
return user;
}
return null;
}
/**
* 设置用户身份信息
*/
public void setLoginUser(LoginUser loginUser)
{
//判断一个字符串是否为非空串(详见字符串工具类)
if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken()))
{
refreshToken(loginUser);
}
}
/**
* 删除用户身份信息
*/
public void delLoginUser(String token)
{
//判断一个字符串是否为非空串(详见字符串工具类)
if (StringUtils.isNotEmpty(token))
{
String userKey = getTokenKey(token);
//删除单个对象
redisCache.deleteObject(userKey);
}
}
/**
* 创建令牌
*
* @param loginUser 用户信息
* @return 令牌
*/
public String createToken(LoginUser loginUser) {
//IdUtils id快速生成器(详见文章ID生成工具)
String token = IdUtils.fastUUID();
//登录对象类的唯一标识token
loginUser.setToken(token);
//设置用户代理信息
setUserAgent(loginUser);
//刷新令牌有效期
refreshToken(loginUser);
//claims是用于存放Payload部分的信息的Map对象,它包含了我们需要在Token中添加的各种自定义属性,
// 例如用户ID、用户名、角色等
Map claims = new HashMap<>();
//常量令牌前缀public static final String LOGIN_USER_KEY = "login_user_key";
claims.put(Constants.LOGIN_USER_KEY, token);
//存放非敏感信息
claims.put("username",loginUser.getUsername());
claims.put("nickName",loginUser.getUser().getNickName());
claims.put("createTime",loginUser.getUser().getCreateTime());
return createToken(claims);
}
/**
* 验证令牌有效期,相差不足20分钟,自动刷新缓存
*
* @param loginUser
* @return 令牌
*/
public void verifyToken(LoginUser loginUser)
{
//过期时间
long expireTime = loginUser.getExpireTime();
//当前时间
long currentTime = System.currentTimeMillis();
if (expireTime - currentTime <= MILLIS_MINUTE_TEN)
{
refreshToken(loginUser);
}
}
/**
* 刷新令牌有效期
*
* @param loginUser 登录信息
*/
public void refreshToken(LoginUser loginUser)
{
//设置登录时间
loginUser.setLoginTime(System.currentTimeMillis());
//设置过期时间
loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
// 根据uuid将loginUser缓存
String userKey = getTokenKey(loginUser.getToken());
//储存redis详见文章(spring redis 工具类)
redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
}
/**
* 设置用户代理信息
*
* @param loginUser 登录信息
*/
public void setUserAgent(LoginUser loginUser)
{
//User-Agent是HTTP协议中的一个头部信息,通常用于标识发送HTTP请求的客户端软件或代理程序的详细信息。
// 它包含了客户端软件类型、版本号、操作系统类型、语言等信息。
// 在Web开发中,服务器可以通过User-Agent头部信息来识别客户端的浏览器和操作系统等信息。
UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
//获取ip详见文章(ip获取地址类)
String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
//存入以下数据
loginUser.setIpaddr(ip);
loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
loginUser.setBrowser(userAgent.getBrowser().getName());
loginUser.setOs(userAgent.getOperatingSystem().getName());
}
/**
* 从数据声明生成令牌
*
* @param claims 数据声明
* @return 令牌
*/
private String createToken(Map claims)
{
String token =
//Jwts.builder()方法创建一个JWT Builder对象,用于构建JWT Token。
Jwts.builder()
//调用setClaims(claims)方法设置JWT Token中的payload部分,即要传递的自定义信息。
//这里的claims参数是一个Map对象,其中包含了需要传递的键值对信息。
.setClaims(claims)
//signWith(SignatureAlgorithm.HS512, secret)方法对JWT Token进行签名,使用的算法是HS512,密钥是secret变量
.signWith(SignatureAlgorithm.HS512, secret)
//compact()方法将JWT Token生成为一个字符串,并将其作为方法的返回值。
.compact();
return token;
}
/**
* 从令牌中获取数据声明
*
* @param token 令牌
* @return 数据声明
*/
private Claims parseToken(String token)
{
//Jwts.parser()方法创建一个JWT Parser对象,用于解析JWT Token
return Jwts.parser()
//setSigningKey(secret)方法设置解析Token时所需的签名密钥,密钥是secret变量。
.setSigningKey(secret)
//parseClaimsJws(token)方法对传入的JWT Token进行解析。这里的token参数是要解析的JWT Token字符串。
.parseClaimsJws(token)
//getBody()方法获取解析后的Token内容,返回的是一个Claims对象,包含了Token中的payload部分的键值对信息。
.getBody();
}
/**
* 从令牌中获取用户名
*
* @param token 令牌
* @return 用户名
*/
public String getUsernameFromToken(String token)
{
//parseToken(token)方法解析传入的JWT Token
Claims claims = parseToken(token);
return claims.getSubject();
}
/**
* 获取请求token
*
* @param request
* @return token
*/
private String getToken(HttpServletRequest request)
{
//获取请求头名称,通常为Authorization。
String token = request.getHeader(header);
//判断一个字符串是否为非空串(详见字符串工具类)
//判断获取到的Token字符串是否非空,并且是否以预定义的Token前缀Constants.TOKEN_PREFIX开头
if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX))
{
//将Token前缀替换为空字符串,只保留真实的Token内容。
token = token.replace(Constants.TOKEN_PREFIX, "");
}
return token;
}
private String getTokenKey(String uuid)
{
//登录用户 redis key
//public static final String LOGIN_TOKEN_KEY = "login_tokens:";
return Constants.LOGIN_TOKEN_KEY + uuid;
}
}
package com.muyuan.common.core.domain.model;
import java.util.Collection;
import java.util.Set;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.muyuan.common.core.domain.entity.SysUser;
/**
* 登录用户身份权限
*
*
*/
public class LoginUser implements UserDetails
{
private static final long serialVersionUID = 1L;
/**
* 用户唯一标识
*/
private String token;
/**
* 登录时间
*/
private Long loginTime;
/**
* 过期时间
*/
private Long expireTime;
/**
* 登录IP地址
*/
private String ipaddr;
/**
* 登录地点
*/
private String loginLocation;
/**
* 浏览器类型
*/
private String browser;
/**
* 操作系统
*/
private String os;
/**
* 权限列表
*/
private Set permissions;
/**
* 用户信息
*/
private SysUser user;
public String getToken()
{
return token;
}
public void setToken(String token)
{
this.token = token;
}
public LoginUser()
{
}
public LoginUser(SysUser user, Set permissions)
{
this.user = user;
this.permissions = permissions;
}
@JsonIgnore
@Override
public String getPassword()
{
return user.getPassword();
}
@Override
public String getUsername()
{
return user.getUserName();
}
/**
* 账户是否未过期,过期无法验证
*/
@JsonIgnore
@Override
public boolean isAccountNonExpired()
{
return true;
}
/**
* 指定用户是否解锁,锁定的用户无法进行身份验证
*
* @return
*/
@JsonIgnore
@Override
public boolean isAccountNonLocked()
{
return true;
}
/**
* 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
*
* @return
*/
@JsonIgnore
@Override
public boolean isCredentialsNonExpired()
{
return true;
}
/**
* 是否可用 ,禁用的用户不能身份验证
*
* @return
*/
@JsonIgnore
@Override
public boolean isEnabled()
{
return true;
}
public Long getLoginTime()
{
return loginTime;
}
public void setLoginTime(Long loginTime)
{
this.loginTime = loginTime;
}
public String getIpaddr()
{
return ipaddr;
}
public void setIpaddr(String ipaddr)
{
this.ipaddr = ipaddr;
}
public String getLoginLocation()
{
return loginLocation;
}
public void setLoginLocation(String loginLocation)
{
this.loginLocation = loginLocation;
}
public String getBrowser()
{
return browser;
}
public void setBrowser(String browser)
{
this.browser = browser;
}
public String getOs()
{
return os;
}
public void setOs(String os)
{
this.os = os;
}
public Long getExpireTime()
{
return expireTime;
}
public void setExpireTime(Long expireTime)
{
this.expireTime = expireTime;
}
public Set getPermissions()
{
return permissions;
}
public void setPermissions(Set permissions)
{
this.permissions = permissions;
}
public SysUser getUser()
{
return user;
}
public void setUser(SysUser user)
{
this.user = user;
}
@Override
public Collection extends GrantedAuthority> getAuthorities()
{
return null;
}
}