JAVA代码优化:Token验证处理

简述:

Token验证处理是指在客户端和服务端之间进行身份验证和授权的过程。在这个过程中,客户端通常会提供一个令牌(Token),用于证明其合法性和权限。服务端接收到该令牌后,需要对其进行验证,以确定该请求是否来自合法的客户端。

JWT是一种常见的Token验证处理方式。

JWT简述:

JWT(JSON Web Token)由三部分组成,它们分别是头部(Header)、载荷(Payload)和签名(Signature)。每个部分都使用Base64编码进行序列化,并使用点号(.)作为分隔符。

  1. 头部(Header):头部包含了关于JWT的元数据信息,以及指定所使用的算法的声明。常见的算法有HMAC、RSA和ECDSA等。头部通常是一个JSON对象,例如:

    复制代码
    {
      //"alg"表示所使用的算法(此处为HMAC SHA-256)
      "alg": "HS256",
      //"typ"表示令牌的类型(此处为JWT)
      "typ": "JWT"
    }
  2. 载荷(Payload):载荷包含了一些声明(claims),这些声明是关于实体(如用户)和其他数据的陈述。载荷可以包含预定义的声明,如"sub"(主题,表示主体的唯一标识)、"exp"(过期时间,表示令牌的有效期)、"iat"(发布时间,表示令牌的发行时间)等,也可以包含自定义的声明。载荷通常也是一个JSON对象,例如:

    java 复制代码
    {
      "sub": "1234567890",
      "name": "John Doe",
      "iat": 1516239022
    }
  3. 签名(Signature):签名是使用指定的算法(如HMAC、RSA等)对头部和载荷进行签名生成的一串字符串。签名用于验证令牌的完整性和真实性,以防止被篡改。签名的生成需要使用密钥(秘钥),服务端在验证令牌时也需要使用相同的密钥进行签名验证。

  4. JWT的三部分是通过点号(.)连接起来形成一个完整的令牌,例如:

    java 复制代码
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
    .
    eyJzdWIiOiAiMTIzNDU2Nzg5MCIsIm5hbWUiOiAiSm9obiBEb2UiLCAiaWF0IjogMTUxNjIzOTAyMn0
    .
    SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

    token验证处理(TokenService)

java 复制代码
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)

java 复制代码
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;
    }
}
相关推荐
Fcy6482 小时前
Linux下 进程(一)(冯诺依曼体系、操作系统、进程基本概念与基本操作)
linux·运维·服务器·进程
袁袁袁袁满2 小时前
Linux怎么查看最新下载的文件
linux·运维·服务器
代码游侠2 小时前
学习笔记——设备树基础
linux·运维·开发语言·单片机·算法
主机哥哥2 小时前
阿里云OpenClaw部署全攻略,五种方案助你快速部署!
服务器·阿里云·负载均衡
Harvey9032 小时前
通过 Helm 部署 Nginx 应用的完整标准化步骤
linux·运维·nginx·k8s
珠海西格电力科技3 小时前
微电网能量平衡理论的实现条件在不同场景下有哪些差异?
运维·服务器·网络·人工智能·云计算·智慧城市
释怀不想释怀3 小时前
Linux环境变量
linux·运维·服务器
zzzsde4 小时前
【Linux】进程(4):进程优先级&&调度队列
linux·运维·服务器
qq_297574674 小时前
Linux 服务器 Java 开发环境搭建保姆级教程
java·linux·服务器
聆风吟º5 小时前
CANN开源项目实战指南:使用oam-tools构建自动化故障诊断与运维可观测性体系
运维·开源·自动化·cann