spring-security和jwt如何生成token

3,072 阅读5分钟

背景:在开始一个项目时,目前生成token的方法主要有两种,一种是spring-security+jwt组合,另一种则是shiro+jwt组合,本文主要阐述spring-security+jwt组合生成token。对这两种框架不熟悉的同学,有兴趣可以点击去官网学习下,不熟悉的也没关系,在生成token的过程中涉及到的知识点,我会一一解答,不熟悉的也不影响阅读本文(建议独自去学习下)。

开始
废话不多,大家都知道,要使用框架,那必不可少的,首先就是要引入相关依赖啊

//pom.xml
<dependencies>
    ...
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
      <version>2.5.3</version>
    </dependency>
    <dependency>
      <groupId>io.jsonwebtoken</groupId>
      <artifactId>jjwt</artifactId>
      <version>0.9.1</version>
    </dependency>
</dependencies>

除此之外,我们还需要在application.yml文件中,给jwt配置一下信息:

jwt:
  #  JWT存储请求头
  tokenHeader: Authorization
  #  JWT加密使用的密钥
  secret: juejin-secret
  #  JWT超期限时间(60*60*24expiration: 604800
  #  JWT负载中拿到开头
  tokenHead: Bearer

不管你是老司机,还是正在进阶的同学,我还是在这里啰嗦解析一下其中的字段。tokenHeader是存储的一个请求头,在前端后端接口数据交互中一般都会有,以及tokenHead一般都会存在于请求信息里,secret是jwt加密和解密要使用到的一个密钥,expiration是token的一个失效时间设定,我这里设置是24小时失效。

接下来我们可以去实现一个spring-security+jwt组合生成token的一个工具类JwtTokenUtil,这个类一般都放在包的config下,下面先把代码展示出来:

/java/com.hhk,server.config.security(包路径)

package com.hhk.server.config.security;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Component
public class JwtTokenUtil {
    private static final String CLAIM_KEY_USERNAME="sub";
    private static final String CLAIM_KEY_CREATED="created";
    @Value("${jwt.secret}")
    private String secret;
    @Value("${jwt.expiration}")
    private Long expiration;

    /**
     * 根据用户信息生成token
     */
    public String generateToken(UserDetails userDetails){
        Map<String,Object> claims=new HashMap<>();
        claims.put(CLAIM_KEY_USERNAME,userDetails.getUsername());
        claims.put(CLAIM_KEY_CREATED,new Date());
        return generateToken(claims);
    }

//    根据荷载JWT生成token
    public String generateToken(Map<String,Object> claims){
//        Jwts去生成token
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(generateExpirationDate())
                .signWith(SignatureAlgorithm.HS256,secret)
                .compact();
    }

//    生成token失效时间
    private Date generateExpirationDate() {
        return new Date(System.currentTimeMillis()+expiration*1000);
    }

//    从token中获取登录名
    public String getUserNameByToken(String token){
        String userName;
        try{
            Claims claims=getClaimsFromToken(token);
            userName=claims.getSubject();
        }catch (Exception e){
            userName=null;
        }
        return userName;
    }

//    从token中获取荷载
    private Claims getClaimsFromToken(String token) {
        Claims claims=null;
        try {
            claims=Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        }catch (Exception e){
            e.printStackTrace();
        }
        return claims;
    }
//    验证token是否有效
    public boolean validateToken(String token,UserDetails userDetails){
        String username=getUserNameByToken(token);
        return username.equals(userDetails.getUsername())&&!isTokenExpired(token);
    }

//    验证token是否失效
    private boolean isTokenExpired(String token) {
        Date expireDate=getExpiredDateFromToken(token);
        return  expireDate.before(new Date());
    }

//    从token中获取失效时间
    private Date getExpiredDateFromToken(String token) {
        Claims claims=getClaimsFromToken(token);
        return claims.getExpiration();
    }
//    判断token是否可以被刷新
    public boolean canRefresh(String token){
        return !isTokenExpired(token);
    }

//    刷新token
    public String refreshToken(String token){
        Claims claims=getClaimsFromToken(token);
        claims.put(CLAIM_KEY_CREATED,new Date());
        return generateToken(claims);
    }
}

下面主要是解析一下,上面整个JwtTokenUtil的源码,自己能阅读的话,不用看下面的源码,下面主要是以方法为单位进行拆分讲解:

//在类的开始,我们需要定义两个静态字符串,以及获取我们前面在Application.yml中定义好的jwt参数,这里是通过@Value(${""})的方式获取Application.yml文件中的配置信息了
private static final String CLAIM_KEY_USERNAME="sub"; //定义静态用户名(key)CLAIM_KEY_USERNAME
private static final String CLAIM_KEY_CREATED="created";//定义静态生成token的时间
@Value("${jwt.secret}") //获取密钥
private String secret;
@Value("${jwt.expiration}") //获取设定的失效时间
private Long expiration;

到这里在工具类JwtTokenUtil需要使用的静态资源就准备好了,下面我们要开始创建生成token的方法了:

/**
 * 根据用户信息生成token
 */
public String generateToken(UserDetails userDetails){
    Map<String,Object> claims=new HashMap<>(); //定义一个荷载,用于存储用户ing以及token的生成时间
    claims.put(CLAIM_KEY_USERNAME,userDetails.getUsername()); //通过。put方法存储CLAIM_KEY_USERNAME和CLAIM_KEY_CREATED的值
    claims.put(CLAIM_KEY_CREATED,new Date());
    return generateToken(claims); //generateToken的方法在下面会编写其源码,主要是根据荷载生成token的
}

承上启下,下面是generateToken(claims):

//    根据荷载JWT生成token
    public String generateToken(Map<String,Object> claims){
        return Jwts.builder()  //Jwts去生成token
                .setClaims(claims) //存储带有用户信息的荷载
                .setExpiration(generateExpirationDate()) //存储失效时间
                .signWith(SignatureAlgorithm.HS256,secret) //使用算法,和密钥
                .compact();
    }

generateToken(claims)中使用的generateExpirationDate(),贴一下代码:

//    生成token失效时间
    private Date generateExpirationDate() {
        //System.currentTimeMillis()+expiration*1000===当前时间+失效时间
        return new Date(System.currentTimeMillis()+expiration*1000);
    }

到这里其实已经实现了生成token了,但是,既然作为工具类,那在项目中,有时候要校验token,就是通过token中的用户名去跟数据库中的用户进行对比,这时就要获取token中的用户名了,也就是源码中的getUserNameByToken(String token)

从token中获取登录名
    public String getUserNameByToken(String token){
        String userName;
        try{ //有时候会存在用户名不存在的情况,所以要对这种异常进行捕获
            Claims claims=getClaimsFromToken(token); //因为生成token时,用户名是存储在token中的荷载,所以getClaimsFromToken(token)获取荷载
            userName=claims.getSubject(); //claims的getSubject()获取用户名
        }catch (Exception e){
            userName=null;
        }
        return userName;
    }

getUserNameByToken(String token)中使用的getClaimsFromToken(token)是要从token中获取荷载:

//    从token中获取荷载
    private Claims getClaimsFromToken(String token) {
        Claims claims=null;
        try {
            //通过密钥和token从jwts中获取荷载,因为荷载有可能为空,所以需要捕获一下异常,防止程序崩溃
            claims=Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        }catch (Exception e){
            e.printStackTrace();
        }
        return claims;
    }

在日常开发中,或者访问网页中时,我们经常会出现需要重新登录的情况,这是,就需要重新登录,以获取新的token继续访问网页,这个过程,需要验证token是否有效、是否可以被刷新。具体方法如下:

//    验证token是否有效UserDetails是spring-security框架里的一个类
    public boolean validateToken(String token,UserDetails userDetails){
        String username=getUserNameByToken(token); //从token中获取用户名
        return username.equals(userDetails.getUsername())&&!isTokenExpired(token);
    }

//    验证token是否失效
    private boolean isTokenExpired(String token) {
        Date expireDate=getExpiredDateFromToken(token); //从token中获取失效时间
        return  expireDate.before(new Date());
    }

//    从token中获取失效时间
    private Date getExpiredDateFromToken(String token) {
        Claims claims=getClaimsFromToken(token); //从token中获取荷载,荷载中存储了失效时间
        return claims.getExpiration();
    }
//    判断token是否可以被刷新
    public boolean canRefresh(String token){
        return !isTokenExpired(token);
    }

//    刷新token
    public String refreshToken(String token){
        Claims claims=getClaimsFromToken(token);
        claims.put(CLAIM_KEY_CREATED,new Date());
        return generateToken(claims);
    }

到这里,spring-security+jwt生成token,以及获取token中的信息的工具类就写完了,感谢阅读!❤️❤️❤️