springboot2入门到实战 - JWT

JWT是什么?

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object。

This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

  • 基于JSON格式用于网络传输的令牌。
  • 紧凑的Claims声明格式。
  • Claim有索赔、声称、要求或者权利要求的含义。

在JWT之前

在JWT技术大规模应用之前,使用cookie+session技术做交互验证、存储由来已久。尤其在单体结构中更加突出(即使现在好多单体架构的项目依然使用cookie+session的经典组合)。

cookie+session技术之所以产生是由于HTTP协议在创建之初为加快服务器的响应,采用“无记忆性“来提高服务器的响应。但是会产生一系列的问题。例如:服务器如何确定客户端已经连接,已经登录了系统多长时间,是否允许该用户继续访问,该用户是否任然活跃,该用户如何找到其私有信息等问题等。
为此cookie+session技术很好的补充了http协议中“无记忆性“的问题。解决方案如图。

  • 服务器产生存储session对象,每个session对象有唯一的ID,有存活时间,可以记录数据。
  • 发起请求后,服务器将session的id写入客户端(浏览器),客户端通过cookie记录session的id
  • 客户端每一次发起请求需要携带session的id。
  • 服务器根据session的id查询指定session是否存在,然后交互。

image-20231026152201240

cookie-session模型很好的补充了http协议的无记忆性问题,但是如果在分布式环境下,如果每个服务器产生一个session或同步已有的session很困难,如果网络,服务器等由于延迟保证数据一致性比较困难。
为此我们需要一种轻量级,在分布式情况下依然能使用的技术尤为重要,JWT技术由此产生。

jwt的结构

image-20230515144726146

xxx.yyy.pppp

Header 部分是一个 JSON 对象,描述 JWT 的元数据。

image-20230515153047351

加密算法
  • HSA256

    SHA(Secure Hash Algorithm,安全散列算法)是一个密码散列函数家族,由美国国家安全局(NSA)设计,并由美国国家标准与技术研究院(NIST)发布,是美国的政府标准。

    • 无论输入多长,都输出64个字符,共32字节(byte),256位(bit)
    • 输出只包含数字0`9`和字母`A`F,大小写不敏感
  • RSA256

    由美国麻 省理工 学院三 位学者 Riv est、Sh amir 及Adleman 研 究发 展出 一套 可实 际使用 的公 开金 钥密码系 统,那 就是RSA(Rivest-Shamir-Adleman)密码系统。

    RS256(带有SHA-256的 RSA 签名)是一种非对称算法,它使用公钥/私钥对:身份提供者拥有用于生成签名的私钥(秘密)密钥,而 JWT 的消费者获得公钥验证签名。由于与私钥相反,公钥不需要保持安全,因此大多数身份提供者都可以让消费者轻松获取和使用(通常通过元数据 URL)。

Base64

Base64是一种基于64个可打印字符来表示二进制数据的表示方法。Base64不是加密算法,只是一种编码方式,可以用Base64来简单的“加密”来保护某些数据,所以每 6 个比特为一个单元,对应某个可打印字符。

image-20230515195043903

“hello” 编码

h–01101000 e --01100101

01101000

image-20230515201422170

 String str="hello";
        byte[] encode = Base64.getEncoder().encode(str.getBytes());
        String s = new String(encode);
        System.out.println("base64编码: "+s);

        byte[] decode = Base64.getDecoder().decode(s);
        String x = new String(decode);
        System.out.println("解析:" + x);

Payload

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。

Payload 也使用BASE64编码

Registered claims

这些是一组预先定义的声明,它们不是强制性的,但推荐使用,以提供一组有用的,可互操作的声明。 其中一些是:iss(发行者),exp(到期时间),sub(主题),aud(受众)等

序号名称解释
1iss (issuer)签发人
2exp (expiration time)过期时间
3sub (subject)主题
4aud (audience)受众(接收jwt的一方)
5nbf (Not Before)生效时间
6iat (Issued At)签发时间
7jti (JWT ID)jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
Public claims

这些可以由使用JWT的人员随意定义。

Private claims

这些是为了同意使用它们但是既没有登记,也没有公开声明的各方之间共享信息,而创建的定制声明。

Payload案例
{
  "sub": "456781234",
  "name": "Mr.zhang",
  "admin": true
}

Signature

The signature is used to verify the message wasn’t changed along the way, and, in the case of tokens signed with a private key, it can also verify that the sender of the JWT is who it says it is.

Signature 部分是对前两部分的签名,防止数据篡改。

https://jwt.io/

Signature部分由编码后的Header、Payload和自定义的秘钥使用Header中指定的算法(HSA256)进行加密签名;

image-20230516085001222

image-20230516142821824

JWT构成

image-20230516085933230

jwt使用

maven中引入token依赖

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

创建token字符串

//数据
User user = new User(1, "tom", "123", "tomcat");
Map<String, String> map = new HashMap<>();
map.put("username", user.getUsername());
map.put("name", user.getName());


String token = JWT.create()
        .withAudience(user.getUsername())
        .withExpiresAt(new Date(System.currentTimeMillis()+1000*30)) //设置过期时间为30ms
        .withClaim("data", map)
        .sign(Algorithm.HMAC256(user.getPassword()));

输出token结果:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJ0b20iLCJkYXRhIjp7ImFtZSI6InRvbWNhdCIsInVzZXJuYW1lIjoidG9tIn0sImV4cCI6MTY4NDIyNTk5NX0.oLSSNY9JeBRrKilOroUZ_xT5AS7AODotUxBXeOZ_D40

解析token

DecodedJWT decode = JWT.decode(token);

获取token的内容

System.out.println("Header: " + decode.getHeader());
System.out.println("Payload: " + decode.getPayload());
System.out.println("Audience: " + decode.getAudience());
System.out.println("Signature: " + decode.getSignature());

验证token

JWTVerifier build = JWT.require(Algorithm.HMAC256(user.getPassword())).build();

获取信息

System.out.println(build.verify(token).getClaim("data"));

{“name”:“tomcat”,“username”:“tom”}

token出现的问题

私钥salt被人篡改

  String salt ="guess";
    String token = JWT.create()
            .withClaim("name", "jack")
            .withClaim("password","123")
            .sign(Algorithm.HMAC256(salt));
    System.out.println(token);

    JWTVerifier build = JWT.require(Algorithm.HMAC256("abc")).build();
    build.verify(token);
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXNzd29yZCI6IjEyMyIsIm5hbWUiOiJqYWNrIn0.ghK3Y79d1VU66eaay9YknYyW9MhWCrsth93UTx75-fk
Exception in thread "main" com.auth0.jwt.exceptions.SignatureVerificationException: The Token's Signature resulted invalid when verified using the Algorithm: HmacSHA256
	at com.auth0.jwt.algorithms.HMACAlgorithm.verify(HMACAlgorithm.java:57)
	at com.auth0.jwt.JWTVerifier.verify(JWTVerifier.java:463)
	at com.auth0.jwt.JWTVerifier.verify(JWTVerifier.java:445)
	at com.wnhz.bms.preview.jwt.JwtDemo.main(JwtDemo.java:19)

篡改token中的信息

     String salt ="guess";
        String token = JWT.create()
                .withClaim("name", "jack")
                .withClaim("password","123")
                .sign(Algorithm.HMAC256(salt));
        System.out.println(token);

        token = token.replace("eyJwYXNzd29yZCI6IjEyMyIsIm5hbWUiOiJqYWNrIn0","eyJwYXNzd29yZCI7IjEyMyIsIm5hbWUiOiJqYWNrIn0");

        
      // token =  token.replace("K","W");//黑客

       // JWTVerifier build = JWT.require(Algorithm.HMAC256("abc")).build();
       JWTVerifier build = JWT.require(Algorithm.HMAC256(salt)).build();
        build.verify(token);
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXNzd29yZCI6IjEyMyIsIm5hbWUiOiJqYWNrIn0.ghK3Y79d1VU66eaay9YknYyW9MhWCrsth93UTx75-fk
Exception in thread "main" com.auth0.jwt.exceptions.JWTDecodeException: The string '{"password";"123","name":"jack"}' doesn't have a valid JSON format.
	at com.auth0.jwt.impl.JWTParser.decodeException(JWTParser.java:90)
	at com.auth0.jwt.impl.JWTParser.parsePayload(JWTParser.java:47)
	at com.auth0.jwt.JWTDecoder.<init>(JWTDecoder.java:49)
	at com.auth0.jwt.JWTVerifier.verify(JWTVerifier.java:444)
	at com.wnhz.bms.preview.jwt.JwtDemo.main(JwtDemo.java:26)

token过期

        String salt ="guess";
        String token = JWT.create()
                .withClaim("name", "jack")
                .withClaim("password","123")
                .withExpiresAt(new Date(System.currentTimeMillis()+10))
                .sign(Algorithm.HMAC256(salt));
        System.out.println(token);

        Thread.sleep(1000);

       JWTVerifier build = JWT.require(Algorithm.HMAC256(salt)).build();
        build.verify(token);
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXNzd29yZCI6IjEyMyIsIm5hbWUiOiJqYWNrIiwiZXhwIjoxNjk4MzA4MTA1fQ.KGA8e6HhO3CSxanvhBaO9X2EpCQY20iZCn2fkSqdlNo
Exception in thread "main" com.auth0.jwt.exceptions.TokenExpiredException: The Token has expired on 2023-10-26T08:15:05Z.
	at com.auth0.jwt.JWTVerifier$BaseVerification.assertValidInstantClaim(JWTVerifier.java:346)
	at com.auth0.jwt.JWTVerifier$BaseVerification.lambda$addMandatoryClaimChecks$17(JWTVerifier.java:308)
	at com.auth0.jwt.JWTVerifier$BaseVerification$1.verify(JWTVerifier.java:405)
	at com.auth0.jwt.JWTVerifier.verifyClaims(JWTVerifier.java:482)
	at com.auth0.jwt.JWTVerifier.verify(JWTVerifier.java:464)
	at com.auth0.jwt.JWTVerifier.verify(JWTVerifier.java:445)
	at com.wnhz.bms.preview.jwt.JwtDemo.main(JwtDemo.java:23)

使用拦截器统一拦截方式

@Component
public class AuthInterceptor  implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("bm_token");

        if (Objects.isNull(token) || "".equals(token.trim())) {
            throw new UserNoLoginException("用户名还没有登录异常");
        }

        return true;
    }
}

@Configuration
public class BmWebConfig implements WebMvcConfigurer {

    @Resource
    private AuthInterceptor authInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(authInterceptor)
                .excludePathPatterns("/api/login");

    }
}
  • 7
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值