JAVA代码优化:Token验证处理
简述:
Token验证处理是指在客户端和服务端之间进行身份验证和授权的过程。在这个过程中,客户端通常会提供一个令牌(Token),用于证明其合法性和权限。服务端接收到该令牌后,需要对其进行验证,以确定该请求是否来自合法的客户端。
JWT是一种常见的Token验证处理方式。
JWT简述:
JWT(JSON Web Token)由三部分组成,它们分别是头部(Header)、载荷(Payload)和签名(Signature)。每个部分都使用Base64编码进行序列化,并使用点号(.)作为分隔符。
- 头部(Header):头部包含了关于JWT的元数据信息,以及指定所使用的算法的声明。常见的算法有HMAC、RSA和ECDSA等。头部通常是一个JSON对象,例如:
{ //"alg"表示所使用的算法(此处为HMAC SHA-256) "alg": "HS256", //"typ"表示令牌的类型(此处为JWT) "typ": "JWT" }
- 载荷(Payload):载荷包含了一些声明(claims),这些声明是关于实体(如用户)和其他数据的陈述。载荷可以包含预定义的声明,如"sub"(主题,表示主体的唯一标识)、"exp"(过期时间,表示令牌的有效期)、"iat"(发布时间,表示令牌的发行时间)等,也可以包含自定义的声明。载荷通常也是一个JSON对象,例如:
{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }
- 签名(Signature):签名是使用指定的算法(如HMAC、RSA等)对头部和载荷进行签名生成的一串字符串。签名用于验证令牌的完整性和真实性,以防止被篡改。签名的生成需要使用密钥(秘钥),服务端在验证令牌时也需要使用相同的密钥进行签名验证。
- JWT的三部分是通过点号(.)连接起来形成一个完整的令牌,例如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJzdWIiOiAiMTIzNDU2Nzg5MCIsIm5hbWUiOiAiSm9obiBEb2UiLCAiaWF0IjogMTUxNjIzOTAyMn0 . SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
token验证处理(TokenService)
-
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<String, Object> 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<String, Object> 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; } }
附加登录用户身份权限(LoginUser)
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<String> permissions; /** * 用户信息 */ private SysUser user; public String getToken() { return token; } public void setToken(String token) { this.token = token; } public LoginUser() { } public LoginUser(SysUser user, Set<String> 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<String> getPermissions() { return permissions; } public void setPermissions(Set<String> 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; } }