springboot集成JWT

JWT简介

是一种认证机制,主要用于用户登陆鉴权,生成token。

JWT结构

一个JWT实际上就是一个字符串,它由三部分组成:头部、载荷与签名,由“.”隔开。
header.payload.signature

eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE2OTUyNjczNTEsImNyZWF0ZVRpbWUiOjE2OTUxODA5NTE5NjAsImlkIjoxfQ.BNydrTifBXfxvFjV8-ofLZW1kodlNgsBqJeXMgesf2hLFR5bQ5vG0Ao0NKlUG9I2J9ksj-m3MFbefj7SVBxf0g
  • header

jwt的头部实际就是对元数据的描述,是一个 json对象,格式如下:

{
    "alg": "HS256",
    "typ": "JWT"
}

其中:
alg属性表示签名使用的算法,默认为 HMAC SHA256(写为HS256),
typ 属性表示令牌的类型,JWT 令牌统一写为JWT。

最后生成 t o k e n 的时候,就是将 j s o n 使用 B a s e 64 U R L 算法转码即可。 \color{red}{最后生成token的时候,就是将json使用 Base64 URL 算法转码即可。} 最后生成token的时候,就是将json使用Base64URL算法转码即可。

在实际的工作用,使用的是简化版,如下图:

{"alg":"HS512"}

base64转码就是eyJhbGciOiJIUzUxMiJ9

一但确定算法,请求头每次生成的结果是相同的 \color{blue}{一但确定算法,请求头每次生成的结果是相同的} 一但确定算法,请求头每次生成的结果是相同的

  • payload

载荷部分主要是传入一些非敏感的数据,也是json对象。

{"exp":1695197703,"createTime":1695111303930,"id":1}

除过需要传的一些数据外,还有七个默认的字段供选择:

iss (issuer):签发人/发行人
sub (subject):主题
aud (audience):用户
exp (expiration time):过期时间
nbf (Not Before):生效时间,在此之前是无效的
iat (Issued At):签发时间
jti (JWT ID):用于标识该 JWT

最后json进行base64编码,就生成token的第二部分内容

  • signature

签名主要是由header和payload两部分的base64编码,然后通过header中声明的加密算法 进行加盐secret组合加密,然后就得出一个签名哈希,也就是Signature,且无法反向解密。

集成JWT
  • 添加依赖
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

  • 创建jwt工具类
package com.test.commutill;

import io.jsonwebtoken.*;
import org.springframework.stereotype.Component;

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

/**
 * @Description
 * @Author yz
 * @Date 2023/9/20 12:27
 **/
@Component
public class JwtUtil {
private static final String secretKey="projectName";  //密钥一般为项目名称
private static final String subject="projectName";  //发行者一般为项目名称
private  static  final Long ttlMillis =7*24*60*60*1000L;//过期时间
    /**
     * 生成token
     * @param claims
     * @return
     */
    public static String createJWT(Map<String, Object> claims){
        JwtBuilder builder = Jwts.builder()
                .setId(UUID.randomUUID().toString()) //jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
                .setSubject(subject) // 发行者
                .setIssuedAt(new Date()) // 发行时间
                .signWith(SignatureAlgorithm.HS256, secretKey) // 签名类型 与 密钥
                .setClaims(claims)
                .setNotBefore(new Date()) //定义在什么时间之前,该jwt都是不可用的
                .setExpiration(new Date(System.currentTimeMillis() + ttlMillis))
                .compressWith(CompressionCodecs.DEFLATE);// 对载荷进行压缩

        return builder.compact();
    }

    /**
     * 从灵牌中获取用户声明的数据信息
     * @param token
     * @return
     */
    public static Claims parseJWT(String token) {
        return Jwts.parser().setSigningKey(secretKey)
                .parseClaimsJws(token)
                .getBody();
    }

    /**
     * 获取具体的用户id值
     * @param token
     * @return
     */
    public static String getInfoByToken(String token){
    Claims claims = parseJWT(token);
    String useId = claims.get("userId").toString();//获取userId属性值

    return useId;
    }

    /**
     * 判断令牌是否过期
     *
     * @param token 令牌
     * @return 是否过期
     */
    public static Boolean isTokenExpired(String token) {
        try {
            Claims claims = parseJWT(token);
            Date expiration = claims.getExpiration();
            return expiration.before(new Date());
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * token 校验
     * @param token
     * @return
     */
    public static Boolean validateToken(String token,String userId) {
        String id = getInfoByToken(token);
        return (userId.equals(id) && !isTokenExpired(token));
    }

    /**
     * 刷新令牌
     * @param token
     * @return
     */
    public static String refreshToken(String token) {
        String refreshedToken;
        try {
            Claims claims = parseJWT(token);
            refreshedToken = createJWT(claims);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }

    //测试数据
    public static String generateToken() {
        Map<String, Object> claims = new HashMap<>(4);
        claims.put("userId", 123);
        claims.put("phone", "13554345676");
        return createJWT(claims);
    }

    public static void main(String[] args) {
        String token="eyJhbGciOiJIUzI1NiIsInppcCI6IkRFRiJ9.eNqqVspLSlOyMjSzNDW0sDQxMNNRSq0ogAiYW5oYgQRKi1OLPFOAYkbGOkoFGfl5qUpWSobGpqYmxiamZuZmSjpK-QWpeSAlYEZmilItAAAA__8.xtprUrY-zvyYvAoI2zFXfEL2tjZvPlMl_sP_YRT-0yU";
        //String token1 = generateToken();
        String claims = getInfoByToken(token);
        System.out.println("用户声明的数据:"+claims);
    }
}


  • 配置拦截器
package com.test.intercepator;

import com.test.commutill.JwtUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName: Intercepator
 * @description: 拦截器实例
 * @author: yz
 * @create: 2021-12-10 14:28
 * @Version 1.0
 */
@Component
public class Intercepator implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("token");
        Map<String, Object> map = new HashMap<>();
        if (StringUtils.isBlank(token)) {
            throw new RuntimeException("无token值");
        }
        String userId = request.getParameter("userId");
        if (!JwtUtil.validateToken(token, userId)) {
            throw new RuntimeException("token验证失败");
        }
        return true;
    }


}
  • 在webConfig中注册拦截器
package com.test.config;

import com.test.intercepator.Intercepator;
import com.test.intercepator.LocaleChangeInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;

/**
 * @ClassName: WebConfig
 * @description:
 * @author: yz
 * @create: 2021-12-10 14:01
 * @Version 1.0
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private Intercepator intercepator;

    @Autowired
    private LocaleChangeInterceptor localeChangeInterceptor;

    //拦截器配置
       /*   addInterceptor:需要一个实现HandlerInterceptor接口的拦截器实例
        addPathPatterns:用于设置拦截器的过滤路径规则;addPathPatterns("/**")对所有请求都拦截
        excludePathPatterns:用于设置不需要拦截的过滤规则
        拦截器主要用途:进行用户登录状态的拦截,日志的拦截等。*/
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
       registry.addInterceptor(intercepator)
                .addPathPatterns("/**")
                .excludePathPatterns(
                        "/swagger-*/**",
                        "/doc.html",
                        "/v2/api-docs"
                );
    }

}
  • 测试环节
    可以写一个接口,进行postman测试。
	package com.test.controller;

	import io.swagger.annotations.Api;
	import org.springframework.web.bind.annotation.RequestMapping;
	import org.springframework.web.bind.annotation.RequestMethod;
	import org.springframework.web.bind.annotation.RestController;

	/**
	 * @ClassName: HelloWorldController
	 * @description: hello
	 * @author: yz
	 * @create: 2021-12-03 21:51
	 * @Version 1.0
	 */
	@RestController
	@RequestMapping("/test")
	@Api(tags = {"helloApi"})
	public class HelloWorldController {

		@RequestMapping(value = "/hello",method = RequestMethod.GET)
		public String hello() {
			return "Hello World!";
		}

	}

测试结果:
接口请求

方式二:

  • 添加依赖
   <!--jwt的依赖-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.8.3</version>
        </dependency>

  • 配置工具类
package com.test.commutill;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;

import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

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

/**
 * @Description
 * @Author yz
 * @Date 2023/9/20 16:41
 **/
public class JwtUtil2 {
    private static final String secretKey = "projectName";  //密钥一般为项目名称
    private static final String subject = "projectName";  //发行者一般为项目名称
    private static final Long ttlMillis = 7 * 24 * 60 * 60 * 1000L;//过期时间

    /**
     * 生成token
     *
     * @param map payload中需要放置的相关非敏感信息
     * @return 返回的生成的token信息
     */
    public static String getToken(Map<String, Object> map) {
        //设置一个时间,作为令牌的过期时间 ,设置过期时间为7天
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DATE, 7);

        //创建jwt builder
        JWTCreator.Builder builder = JWT.create();
        //遍历map集合,将信息放到payload中
        map.forEach((k, v) -> {
            builder.withClaim(k, v.toString());  //payload信息
        });
        //header信息可以省略,因为默认已经有算法和类型了,也可以在headerMap中设置算法
        HashMap<String, Object> headerMap = new HashMap<>();
        String token = builder.withHeader(headerMap)     //header信息
                .withExpiresAt(calendar.getTime()) //token过期时间
                .sign(Algorithm.HMAC256(secretKey));//签名
        return token;
    }

    /**
     * 验证token DecodedJWT 为解密之后的对象 可以获取payload中添加的数据
     */
    public static DecodedJWT verifyToken(String token) {
        //进行token的校验,注意使用同样的算法和同样的秘钥
        return JWT.require(Algorithm.HMAC256(secretKey)).build().verify(token);
    }

    /**
     * 获取token中payload中的添加的参数 演示
     *
     * @return
     */
    public static DecodedJWT getTokenPayloadParam(String token) {
        DecodedJWT verify = JWT.require(Algorithm.HMAC256(secretKey)).build().verify(token);
        //Map<String, Claim> claims = verify.getClaims(); //获取payload中所有的参数
        //verify.getClaim("userName").asString();  //通过key获取value,转成对应的类型
        return verify;
    }


    //测试数据
    public static String generateToken() {
        Map<String, Object> claims = new HashMap<>(4);
        claims.put("userId", 123);
        claims.put("phone", "13554345676");
        return getToken(claims);
    }

    public static void main(String[] args) throws JsonProcessingException {
        String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwaG9uZSI6IjEzNTU0MzQ1Njc2IiwiZXhwIjoxNjk1ODA0MzI0LCJ1c2VySWQiOiIxMjMifQ.BZeZ9iB7_o6C7i8DUhD-err5bEnYUCO63OQCqI64J2g";
        DecodedJWT decodedJWT = getTokenPayloadParam(token);

        System.out.println("header=" + decodedJWT.getHeader());
        System.out.println("payload=" + decodedJWT.getPayload());
        System.out.println("date=" + decodedJWT.getExpiresAt());//获取到过期时间
        System.out.println("Claims=" + decodedJWT.getClaims());
        System.out.println("userId=" + decodedJWT.getClaim("userId").asString());//获取属性值


    }
}

  • 配置拦截器
package com.test.intercepator;

import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.InvalidClaimException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.test.commutill.JwtUtil2;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName: Intercepator
 * @description: 拦截器实例
 * @author: yz
 * @create: 2021-12-10 14:28
 * @Version 1.0
 */
@Component
public class Intercepator implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//        String token = request.getHeader("token");
//        Map<String, Object> map = new HashMap<>();
//        if (StringUtils.isBlank(token)) {
//            throw new RuntimeException("无token值");
//        }
//        String userId = request.getParameter("userId");
//        if (!JwtUtil.validateToken(token, userId)) {
//            throw new RuntimeException("token验证失败");
//        }
//        return true;
        String token = request.getHeader("token");
        Map<String, String> map = new HashMap<>();
        try {
            JwtUtil2.verifyToken(token);//调用token解析的工具类进行解析
            return true;  //请求放行
        } catch (SignatureVerificationException e) {
            e.printStackTrace();
            map.put("msg", "签名不一致异常");
        } catch (TokenExpiredException e) {
            e.printStackTrace();
            map.put("msg", "令牌过期异常");
        } catch (AlgorithmMismatchException e) {
            e.printStackTrace();
            map.put("msg", "算法不匹配异常");
        } catch (InvalidClaimException e) {
            e.printStackTrace();
            map.put("msg", "失效的payload异常");
        } catch (Exception e) {
            e.printStackTrace();
            map.put("msg", "token无效");
        }
        //map异常的数据要返回给客户端需要转换成json格式  @ResponseBody 内置了jackson
        String resultJson = new ObjectMapper().writeValueAsString(map);
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().print(resultJson);
        return true;  //异常不放行
    }

}
  • 注册拦截器
package com.test.config;

import com.test.intercepator.Intercepator;
import com.test.intercepator.LocaleChangeInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;

/**
 * @ClassName: WebConfig
 * @description:
 * @author: yz
 * @create: 2021-12-10 14:01
 * @Version 1.0
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private Intercepator intercepator;

    @Autowired
    private LocaleChangeInterceptor localeChangeInterceptor;

    //拦截器配置
       /*   addInterceptor:需要一个实现HandlerInterceptor接口的拦截器实例
        addPathPatterns:用于设置拦截器的过滤路径规则;addPathPatterns("/**")对所有请求都拦截
        excludePathPatterns:用于设置不需要拦截的过滤规则
        拦截器主要用途:进行用户登录状态的拦截,日志的拦截等。*/
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(intercepator)
                .addPathPatterns("/**")
                .excludePathPatterns(
                        "/swagger-*/**",
                        "/doc.html",
                        "/v2/api-docs"
                );
    }
 
}
  • 验证
    修改token值,点击发送按钮。弹出异常信息。
    在这里插入图片描述
    遇到的问题:
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值