【JAVA】Springboot整合JWT

134 阅读6分钟

文章轉載 => baobao555.tech/archives/40

背叛我的人我都送他100万,熱愛學習的人将来那就是几个亿!

图片.png

1. JWT詳解

回顾一下利用token进行用户身份验证的流程:

  • 客户端使用用户名和密码请求登录
  • 服务端收到请求,验证用户名和密码
  • 验证成功后,服务端会签发一个token,再把这个token返回给客户端
  • 客户端收到token后可以把它存储起来,比如放到cookie中
  • 客户端每次向服务端请求资源时需要携带服务端签发的token,可以在cookie或者header中携带
  • 服务端收到请求,然后去验证客户端请求里面带着的token,如果验证成功,就向客户端返回请求数据

而JWT就是上述流程当中token的一种具体实现方式,其全称是JSON Web Token,官网地址:jwt.io/

2. java-jwt

2.1 Dependency

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.10.3</version>
</dependency>

2.2 對稱簽名

package com.example.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.Calendar;
import java.util.Map;

public class JWTUtils {

        private static final String SECRET = "!DAR$";
        
        /**
         * 生成token
         * @param payload token携带的信息
         * @return token字符串
         */
        public static String getToken(Map<String,String> payload){
            // 指定token过期时间为7天
            Calendar calendar = Calendar.getInstance();
            calendar.add(Calendar.DATE, 7);

            JWTCreator.Builder builder = JWT.create();
            // 构建payload
            payload.forEach((k,v) -> builder.withClaim(k,v));
            // 指定过期时间和签名算法
            String token = builder.withExpiresAt(calendar.getTime()).sign(Algorithm.HMAC256(SECRET));
            return token;
        }


        /**
         * 解析token
         * @param token token字符串
         * @return 解析后的token
         */
        public static DecodedJWT decode(String token){
            JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
            DecodedJWT decodedJWT = jwtVerifier.verify(token);
            return decodedJWT;
        }
}
@RestController
@RequestMapping(value = "/hello")
public class HelloController {

    @RequestMapping(value = "/token")
    public String getToken() {

        Map<String, String> map = new HashMap<>();
        map.put("userName", "nolan");
        map.put("userId", "1");

        String token = JWTUtils.getToken(map);
        return token;
    }

    @RequestMapping(value = "parse")
    public void parseToken() {
        String token = getToken();
        DecodedJWT decode = JWTUtils.decode(token);

        // 获取解析后的token中的payload信息
        Claim userId = decode.getClaim("userId");
        Claim userName = decode.getClaim("userName");
        System.out.println(userId.asString());
        System.out.println(userName.asString());
    }
}

2.3 非對稱簽名

和下面那個非對稱簽名一樣,都有報錯,沒解決,參考最下面Error

import cn.hutool.crypto.asymmetric.RSA;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Calendar;
import java.util.Map;

public class JWTUtils {

    private static final String RSA_PRIVATE_KEY = "...";
    private static final String RSA_PUBLIC_KEY = "...";

    /**
     * 生成token
     * @param payload token携带的信息
     * @return token字符串
     */
    public static String getTokenRsa(Map<String,String> payload){

        // 指定token过期时间为7天
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DATE, 7);

        JWTCreator.Builder builder = JWT.create();
        // 构建payload
        payload.forEach((k,v) -> builder.withClaim(k,v));

        // 利用hutool创建RSA
        RSA rsa = new RSA(RSA_PRIVATE_KEY, null);
        // 获取私钥
        RSAPrivateKey privateKey = (RSAPrivateKey) rsa.getPrivateKey();
        // 签名时传入私钥
        String token = builder.withExpiresAt(calendar.getTime()).sign(Algorithm.RSA256(null, privateKey));
        return token;
    }

    /**
     * 解析token
     * @param token token字符串
     * @return 解析后的token
     */
    public static DecodedJWT decodeRsa(String token){
        // 利用hutool创建RSA
        RSA rsa = new RSA(null, RSA_PUBLIC_KEY);
        // 获取RSA公钥
        RSAPublicKey publicKey = (RSAPublicKey) rsa.getPublicKey();
        // 验签时传入公钥
        JWTVerifier jwtVerifier = JWT.require(Algorithm.RSA256(publicKey, null)).build();
        DecodedJWT decodedJWT = jwtVerifier.verify(token);
        return decodedJWT;
    }
}

稍微改了一下,可以創建token,但是無法校驗

The Token's Signature resulted invalid when verified using the Algorithm: SHA256withRSA

public class JWTUtils02 {

    public static PrivateKey getPrivateKey(){
        //生成公私钥对
        KeyPair pair = SecureUtil.generateKeyPair("RSA");
        PrivateKey privateKey1 = pair.getPrivate();
        PublicKey publicKey1 = pair.getPublic();
        return  privateKey1;
    }
    public static PublicKey getPublicKey(){
        //生成公私钥对
        KeyPair pair = SecureUtil.generateKeyPair("RSA");
        PrivateKey privateKey1 = pair.getPrivate();
        PublicKey publicKey1 = pair.getPublic();
        return  publicKey1;
    }

    /**
     * 生成token
     * @param payload token携带的信息
     * @return token字符串
     */
    public static String getTokenRsa(Map<String,String> payload){

        // 指定token过期时间为7天
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DATE, 7);

        JWTCreator.Builder builder = JWT.create();
        // 构建payload
        payload.forEach((k,v) -> builder.withClaim(k,v));

        // 利用hutool创建RSA
        RSA rsa = new RSA(getPrivateKey(), null);
        // 获取私钥
        RSAPrivateKey privateKey = (RSAPrivateKey) rsa.getPrivateKey();
        // 签名时传入私钥
        String token = builder.withExpiresAt(calendar.getTime()).sign(Algorithm.RSA256(null, privateKey));
        return token;
    }

    /**
     * 解析token
     * @param token token字符串
     * @return 解析后的token
     */
    public static DecodedJWT decodeRsa(String token){

        RSA rsa = new RSA(null, getPublicKey());
        // 获取RSA公钥
        RSAPublicKey publicKey = (RSAPublicKey) rsa.getPublicKey();
        // 验签时传入公钥
        JWTVerifier jwtVerifier = JWT.require(Algorithm.RSA256(publicKey,null)).build();
        DecodedJWT decodedJWT = jwtVerifier.verify(token);
        return decodedJWT;
    }
}

3. JJWT

3.1 Dependency

0.9.x版本

<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

0.11.x版本

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.2</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>

3.2 對稱簽名

0.9.x版本

public class JWTUtils {

    // token时效:24小时
    public static final long EXPIRE = 1000 * 60 * 60 * 24;
    // 签名哈希的密钥,对于不同的加密算法来说含义不同
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";

    /**
     * 根据用户id和昵称生成token
     * @return JWT规则生成的token
     */
    public static String getJwtToken(String id, String username) {
        String JwtToken = Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")
                .setSubject("Demo")
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                .claim("userId", id)
                .claim("userName", username)
                // HS256算法实际上就是MD5加盐值,此时APP_SECRET就代表盐值
                .signWith(SignatureAlgorithm.HS256, APP_SECRET)
                .compact();

        return JwtToken;
    }

    /**
     * 判断token是否存在与有效
     * @param jwtToken token字符串
     * @return 如果token有效返回true,否则返回false
     */
    public static boolean checkToken(String jwtToken) {
        if (StringUtils.isEmpty(jwtToken)) return false;
        try {
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 判断token是否存在与有效
     * @param request Http请求对象
     * @return 如果token有效返回true,否则返回false
     */
    public static boolean checkToken(HttpServletRequest request) {
        try {
            // 从http请求头中获取token字符串
            String jwtToken = request.getHeader("Authorization");
            if (StringUtils.isEmpty(jwtToken)) return false;
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 根据token获取会员id
     *
     * @param request Http请求对象
     * @return 解析token后获得的用户id
     */
    public static String getMemberIdByJwtToken(HttpServletRequest request) {
        String jwtToken = request.getHeader("Authorization");
        if (StringUtils.isEmpty(jwtToken)) return "";
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        Claims claims = claimsJws.getBody();
        return (String) claims.get("id");
    }
}
@RestController
@RequestMapping(value = "/demo")
public class DemoController {

    @RequestMapping(value = "/token")
    public String createToken(){

        String nolan = JWTUtils.getJwtToken("1", "Nolan");
        return nolan;
    }

    @RequestMapping(value = "/parse")
    public void parseToken(){
        String token = createToken();
        boolean b = JWTUtils.checkToken(token);
        System.out.println(b);

    }
}

0.11.x版本

public class JwtUtils {
    // token时效:24小时
    public static final long EXPIRE = 1000 * 60 * 60 * 24;
    // 签名哈希的密钥,对于不同的加密算法来说含义不同
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHOsdadasdasfdssfeweee";

    /**
     * 根据用户id和昵称生成token
     * @return JWT规则生成的token
     */
    public static String getJwtToken(String id, String nickname){
        String JwtToken = Jwts.builder()
                .setSubject("demo")
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                .claim("userId", id)
                .claim("userName", username)
                // 传入Key对象
                .signWith(Keys.hmacShaKeyFor(APP_SECRET.getBytes(StandardCharsets.UTF_8)), SignatureAlgorithm.HS256)
                .compact();
        return JwtToken;
    }

    /**
     * 判断token是否存在与有效
     * @param jwtToken token字符串
     * @return 如果token有效返回true,否则返回false
     */
    public static Jws<Claims> decode(String jwtToken) {
        // 传入Key对象
        Jws<Claims> claimsJws = Jwts.parserBuilder().setSigningKey(Keys.hmacShaKeyFor(APP_SECRET.getBytes(StandardCharsets.UTF_8))).build().parseClaimsJws(jwtToken);
        return claimsJws;
    }
}

3.3 非對稱簽名

同上,參考2.3,使用靜態方法創建key,就可以創建token了,校驗還是問題,有時間再研究吧

public class JwtUtils {

    private static final String RSA_PRIVATE_KEY = "...";
    private static final String RSA_PUBLIC_KEY = "...";

    /**
         * 根据用户id和昵称生成token
         * @param id  用户id
         * @param nickname 用户昵称
         * @return JWT规则生成的token
         */
    public static String getJwtTokenRsa(String id, String nickname){
        // 利用hutool创建RSA
        RSA rsa = new RSA(RSA_PRIVATE_KEY, null);
        RSAPrivateKey privateKey = (RSAPrivateKey) rsa.getPrivateKey();
        String JwtToken = Jwts.builder()
            .setSubject("baobao-user")
            .setIssuedAt(new Date())
            .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
            .claim("id", id)
            .claim("nickname", nickname)
            // 签名指定私钥
            .signWith(privateKey, SignatureAlgorithm.RS256)
            .compact();
        return JwtToken;
    }

    /**
         * 判断token是否存在与有效
         * @param jwtToken token字符串
         * @return 如果token有效返回true,否则返回false
         */
    public static Jws<Claims> decodeRsa(String jwtToken) {
        RSA rsa = new RSA(null, RSA_PUBLIC_KEY);
        RSAPublicKey publicKey = (RSAPublicKey) rsa.getPublicKey();
        // 验签指定公钥
        Jws<Claims> claimsJws = Jwts.parserBuilder().setSigningKey(publicKey).build().parseClaimsJws(jwtToken);
        return claimsJws;
    }

}

4.實際開發

  • 在登录验证通过后,给用户生成一个对应的随机token(注意这个token不是指jwt,可以用uuid等算法生成),然后将这个token作为key的一部分,用户信息作为value存入Redis,并设置过期时间,这个过期时间就是登录失效的时间
  • 将第1步中生成的随机token作为JWT的payload生成JWT字符串返回给前端
  • 前端之后每次请求都在请求头中的Authorization字段中携带JWT字符串
  • 后端定义一个拦截器,每次收到前端请求时,都先从请求头中的Authorization字段中取出JWT字符串并进行验证,验证通过后解析出payload中的随机token,然后再用这个随机token得到key,从Redis中获取用户信息,如果能获取到就说明用户已经登录
public class JWTInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String JWT = request.getHeader("Authorization");
        try {
            // 1.校验JWT字符串
            DecodedJWT decodedJWT = JWTUtils.decode(JWT);
            // 2.取出JWT字符串载荷中的随机token,从Redis中获取用户信息
            ...
            return true;
        }catch (SignatureVerificationException e){
            System.out.println("无效签名");
            e.printStackTrace();
        }catch (TokenExpiredException e){
            System.out.println("token已经过期");
            e.printStackTrace();
        }catch (AlgorithmMismatchException e){
            System.out.println("算法不一致");
            e.printStackTrace();
        }catch (Exception e){
            System.out.println("token无效");
            e.printStackTrace();
        }
        return false;
    }
}

5. Error

不出意外的話,基本上意外馬上就會到來

1.InvalidKeySpecException: java.security.InvalidKeyException: IOException: Short read of DERl

blog.csdn.net/XikYu/artic…

裡面說是因為進口管制限制,需要修改策略文件 www.oracle.com/java/techno… ,我下載以後覆蓋掉還是不行,沒解決問題....

JDK:将两个jar文件放到%JDK_HOME%\jre\lib\security下  
JRE:将两个jar文件放到%JRE_HOME%\lib\security下

6. Help


以上,有解決的,請打在評論區,不勝感謝。