简单描述:最近在处理鉴权这一块的东西,需求就是用户登录需要获取token,然后携带token访问接口,token认证成功接口才能返回正确的数据,如果访问接口时候token过期,就采用刷新token刷新令牌(得到新的token和refresh_token),然后在访问接口返回数据,如果刷新token也过期了,就提示用户重新登录。废话不多说,直接上代码。源码在github上

使用 springboot + thymeleaf + mybatis 搭建的 

//核心依赖
<!-- spring security + OAuth2 + JWT    start  -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.9.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- spring security + OAuth2 + JWT end -->

 

//核心配置 application.yml文件
#toekn相关配置
token:
config:
#客户端标识,类比为token的用户名,我写的是项目名
clientId: SOJ_DEMO
#客户端安全码,类比为token的密码,我写的是假邮箱
secret: soj@123
#表示授权模式: password(密码模式),authorization_code(授权码模式)
grantTypes: password
#表示权限范围,该属性为可选项
scopes: all
#令牌的有效时长,此处为180s/60,时长为2分钟 设置短一点是为了测试刷新token
accessTokenValidity: 120
#刷新令牌的有效时长
refreshTokenValidity: 36000
#资源ID号
resourceId: SOJ_DEMO
#token签名的key,用于token对称加解密
signingKey: SOJ_SYMMETRY
#设置刷新令牌机制.true(重复使用:更新access_token时长后,refresh_toke时长不更新)。false(与true相反)
isRefreshToken: false

 

利用keytool工具生成密钥对(非对称秘钥 公钥私钥) 来执行签名过程  keytool工具使用帮助文档 这里得好好看看 最好一步步来 繁琐的过程,我的妈呀 真的要吐了 太恶心了

//在cmd命令提示符中执行此命令  绿色部分是变量 你可以修改
keytool -genkeypair -alias jwt -keyalg RSA -keypass xc1234 -keystore jwt.jks -storepass xc1234 

 执行完之后会在C:\Users\Administrator目录下生成一个jwt.jks文件, .jks文件包含了我们的秘钥 公钥 私钥,后续会在代码中读取此文件中的公钥私钥。

 把生成的jwt.jks文件放到resources目录下的certificate文件夹中,这里给一下目录结构

SpringSecurity+Oauth2+Jwt实现toekn认证和刷新token

 

 然后在POM文件中加入对此文件的引入

//pom文件

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>

<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<excludes>
<exclude>certificate/*.jks</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<includes>
<include>certificate/*.jks</include>
</includes>
</resource>
</resources>
</build>

 

 下边开始代码

JwtToken主要负责token解析和加解密 和上边的jwt.jks文件打交道 读取公钥私钥

package com.xc.soj_demo.jwt;

import io.jsonwebtoken.ClaimJwtException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Configuration;

import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Date;

/**
 * token加解密
 * 
 * 1.对称加解密
 * 2.非对称加解密(RSA)
 * 
 */
@Configuration
public class JwtToken {
    
    /**
     * 加载jwt.jks文件
     */
    private static InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("certificate/jwt.jks");
    private static PrivateKey privateKey = null;
    private static PublicKey publicKey = null;

    static {
        try {
            KeyStore keyStore = KeyStore.getInstance("JKS");
            keyStore.load(inputStream, "xc1234".toCharArray());
            privateKey = (PrivateKey) keyStore.getKey("jwt", "xc1234".toCharArray());
            publicKey = keyStore.getCertificate("jwt").getPublicKey();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 生成jwt token(非对称加密模式 公钥私钥)
     */
    public static String generateTokenRSA(String subject, int expirationSeconds) {
        Date nowDate = new Date();
        Date expireDate = new Date(nowDate.getTime() + expirationSeconds * 1000);
        return Jwts.builder()
                .setClaims(null)
                .setHeaderParam("typ", "JWT")
                .setSubject(subject)
                .setIssuedAt(nowDate)
                .setExpiration(expireDate)
                .signWith(SignatureAlgorithm.RS256, privateKey) 
                .compact();
    }

    /**
     * 解析jwt token(非对称加密模式 公钥私钥)
     */
    public static Claims parseTokenRSA(String token) {
        if (StringUtils.isEmpty(token)) {
            return null;
        }
  
        try {
            return Jwts.parser()
                    .setSigningKey(publicKey)
                    .parseClaimsJws(token)
                    .getBody();
        }catch (Exception e){
            System.out.println("try-catch:validate is token error ");
            return null;
        }
    }
    
    /**
     * token是否过期
     * @return  true:过期
     */
    public static boolean isTokenExpired(Date expiration) {
        boolean before = expiration.before(new Date());
        return before;
    }
    
    /**
     * 生成jwt token(对称加密模式)
     */
    public static String generateToken(String subject, int expirationSeconds,
            String signingKey) {
        Date nowDate = new Date();
        Date expireDate = new Date(nowDate.getTime() + expirationSeconds * 1000);
        return Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setSubject(subject)
                .setIssuedAt(nowDate)
                .setExpiration(expireDate)
                .signWith(SignatureAlgorithm.HS512, signingKey)
                .compact();
    }
    
    /**
     * 解析jwt token(对称加密模式)
     */
    public static String parseToken(String token,String signingKey) {
        if (StringUtils.isEmpty(token)) {
            return null;
        }
        token = StringUtils.substringAfter(token, "bearer");//定义token令牌的类型为bearer
        Claims claims;
        try {
            claims = Jwts.parser().setSigningKey(signingKey.getBytes("UTF-8")).parseClaimsJws(token).getBody();
        } catch (ClaimJwtException e) {
            //源码DefaultJwtParser.Class中的处理过程是 从token中取出载荷payload部分,解析出claim(claim中存在用户信息),然后在解析是否过期,最后才抛出的异常
            //所以是可以从 ClaimJwtException e中取出需要的部分 并且源码ClaimJwtException.Class类中有header和claim两个私有属性并提供了get方法
            claims = e.getClaims();
        } catch (UnsupportedEncodingException e) {
            return null;
        }
        String localUser = (String) claims.get("userinfo");// 拿到当前用户
        return localUser;
    }
    
    
}
JwtToken.java

相关文章: