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;
    }
}
相关推荐
学习3人组14 分钟前
集群服务器主机实现主机名与IP绑定
运维·服务器·tcp/ip
it技术分享just_free21 分钟前
基于 K8S kubernetes 搭建 安装 EFK日志收集平台
运维·docker·云原生·容器·kubernetes·k8s
2407-2 shw1 小时前
weblogic CVE-2018-2894 靶场攻略
java·运维·服务器·安全·weblogic
xiaobai12 31 小时前
集群聊天服务器项目【C++】(六)MySql数据库
服务器·数据库·c++
奇点 ♡1 小时前
【线程】线程的控制
linux·运维·c语言·开发语言·c++·visual studio code
向往风的男子1 小时前
【devops】devops-ansible之介绍和基础使用
运维·ansible·devops
安科瑞刘鸿鹏2 小时前
分布式光伏发电系统如何确保电能质量达到并网要求?
服务器·网络·分布式·嵌入式硬件·能源
xiaojiesec2 小时前
第159天:安全开发-Python-协议库爆破&FTP&SSH&Redis&SMTP&MYSQL等
运维·安全·ssh
技术不支持2 小时前
WSL2+Ubuntu 22.04搭建Qt开发环境+中文输入法
linux·运维·服务器·qt·ubuntu