JWT+Springboot

概念

JWT(JSON Web Tokens):JSON Web令牌

是一种紧凑的,UL安全的方法,用于表示要在两方之间转移的声明(凭证)。
JWT定义了一种紧凑而独立的方法,用于在各方之间安全地将信息作为JSON对象传输。

  1. 使用json对象在系统之间传递信任的数据,例如{“name”:“lisi”,“id”:1001,“role”,“admin”}
  2. 是URL安全的,浏览器中作为参数传递JWT
  3. JWT数据可以使用秘钥(使用HMAC算法)或使用RSA或ECDSA的公用/专用密钥对对JWT进行签名。JWT信息可以使用数字签名,因此可以被验证和信任。

JWT规范,允许我们使用JWT在两个组织之间传递安全可靠的信息。
在这里插入图片描述

JWT是一个凭证

用户访问系统:先获取凭据(token),使用凭据(token)访问其他资源

JWT工作原理

用户先到服务器认证身份,认证后服务器返回一个json,就像这个样子

"id":1001,
"创建时间""2021年5月15日10点21分10秒"
“角色":"经理”,
"过期时间""2021年5月16日0点0分0秒"

以后用户再发起请求,就是带着这个json数据,服务器拿这个json对象确定用户身份,判断用户能执行操作,获取数据。为了防止用户篡改数据,服务器在生成这个json对象的时候,会加上签名。
服务器就不保存任何session数据了,也无需使用redis存储,服务器变成无状态了,从而比较容易实现扩展。

什么时候使用JWT

  1. 授权Authorization:这是使用JWT的最常见方案。用户登录后,每个后续请求都将包含JWT,从而允许用户访问该令牌允许的路由,服务和资源。单点登录是当今广泛使用JWT的一项功能,因为它的开销很小并且可以在不同的域中轻松使用。
  2. 信息交换Information Exchange:JSON Web令牌是在各方之间安全地传输信息的一种好方法。因为可以对JWT进行签名(例如,使用公钥/私钥对),所以您可以确保发件人是他们所说的人。此外,由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否未被篡改。
  3. 客户端会话(无Session)

认证流程

  1. 前端通过web表单将自己的用户和密码发送到后端接口(一般是http-post请求,建议使用SSL加密传输(https协议),以免敏感信息被嗅探)
  2. 后端核对用户名和密码成功后,将用户的Id等其他信息作为JWT Payload(负载),将其与头部分别进行BASE64编码拼接后签名,形成一个JWT(Token),形成的JWT就是一个字符串(head.payload.singueater)
  3. 后端将JWT字符串作为登录成功的结果返回给前端。前端结果保存在localStorage或sessionStorage上或者是redis中,退出登录时前端删除保存的JWT即可。
  4. 前端在每次请求时将JWT放入http header中的Authorization位(解决XSS和XSRF问题)
  5. 后端检查是否存在,如存在验证JWT的有效性,例如:检查签名是否正确,检查token是否过期,检查token的接收方是否是自己(可选)。
  6. 验证通过后后端使用jwt中包含的用户信息进行其他逻辑操作,返回相应结果。

为什么需要JWT

cookie和session的缺点:

  • session信息都存储在服务端内存
  • 集群环境中需要额外处理
  • csrf:Cross-site request forgery,cookie被截获后可能发生跨站点请求伪造
  • cookie的跨域(前后端分离)读写不方便

jwt优势:

  • 输出是三个由.分割的Base64-URL字符串,可以在HTML和HTTP环境中轻松传递这些字符串,与基于XML的标准(如SAML)相比,更紧凑。
  • 简洁:可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快。
    自包含:负载中包含了所有用户所需的信息,避免了多次查询数据库。
  • 因为token是以JSON加密的新三国hi保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持。
  • 不需要再服务端保存会话信息,特别适用于分布式微服务。

JWT组成

JWT规范中,定义组成结构,长这个样子

eyJhbGci0iJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY30DkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWFOIjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

这是一个很长的字符串,中间用点(.)分隔成三个部分。JWT内部是没有换行的,这里只是为了便于展示,将它写成了几行。

JWT的三个部分依次如下:

  • Header(头部)

  • Payload(负载)

  • Signature(签名)

以上三个部分是"."作为分隔,是一行显示:xxxx.yyyy.zzz

在这里插入图片描述
在这里插入图片描述

Header:是一个json对象,存储元数据

{
	"a1g":"Hs256",
	"typ":"JWT"
}

a1g:是签名的算法名称(algorithm),默认是HMAC SHA256(写成HS256)

typ属性表示这个令牌(token)的类型(type),JWT令牌统一写为JWT

元数据的json对象使用base64URL编码,翻译为字符串

Payload:负载,是一个json对象。是存放传递的数据,数据分为Public的和Private的。

Public是JWT中规定的一些字段,可以自己选择使用

  1. iss(issuer):签发人
  2. exp(expiration time):过期时间
  3. sub(subject):该JWT所面向的用户
  4. aud(audience):受众,接收该JwT的一方
  5. nbf(Not Before):生效时间
  6. iat(Issued At):签发时间
  7. jti(JWT ID):编号

Private是自己定义的字段

{
	"role":"经理",
	"name":"张凡",
	"id":2345
}

Playload默认是没有加密的,以上数据都明文传输的,所以不要放敏感数据。此部分数据也是json对象使用base64URL编码,翻译为字符串。

Signature:签名。签名是对Header和Payload两部分的签名,目的是防止数据被篡改

HMACSHA256(
base64UrlEncode (header)+"."+
base64Ur1Encode (payload),
secret)
# 签名算法:先指定一个secret秘钥,把base64URL的header,base64URL的payload和secret秘钥使用
# HMAC SHA256生成签名字符串

最后把base64URL的Header、base64URL的payload、Signature签名的值三个部分拼成一字符串,每个部分之间用”点”(.)分隔,就可以返回给用户。
例如:
eyJhbGciOiJICJ9.eyJzdmFtZSOIjoxNTE2Ij15DIyfQ.Sf1KxwRJ6POk6yJV.adQssw5c

jwt的实现形式

方式一 :jwt(java-jwt)

  1. 导入依赖
<dependency>
   <groupId>com.auth0</groupId>
   <artifactId>java-jwt</artifactId>
   <version>3.11.0</version>
</dependency>
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

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

public class JavaJWTUtil {
	//这个secret我们一般写在配置文件或者存放常量的类中,这里为了方便直接写在这儿	
    private static String secret="WHY2023.11.24";
   
    /**
     * 生成token   header.payload.signature
     * @param map :payload中需要存放的信息,以map方式传入
     * @param day :过期时间,以秒为单位
     * @return
     */
    public static String getToken(Map<String,String> map,Integer day){
        Calendar instance=Calendar.getInstance();
        instance.add(Calendar.SECOND,day);
        //创建jwt  builder
        JWTCreator.Builder builder=JWT.create();
        //payload,这里采用lambda表达式设置
        map.forEach((k,v)->{
            builder.withClaim(k,v);
        });
        String token=builder.withExpiresAt(instance.getTime())//指定令牌过期时间
                .sign(Algorithm.HMAC256(secret));
        return token;
    }

    /**
     * 验证token合法性,不合法会抛出异常信息
     * @param token : 前端传来的token
     * @return
     */
    public static DecodedJWT verify(String token){
        //如果有任何验证异常,此处都会抛出异常,因此我们可以捕获这些异常来反馈信息回前端
        DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC256(secret)).build().verify(token);
        return decodedJWT;
    }
}

常见异常信息

  1. SignatureVerificationException 签名不一致
  2. TokenExpiredException 令牌过期
  3. AlgorithmMismatchException 签名算法不匹配
  4. InvalidClaimException payload不可用

方式二 : jjwt(推荐)

github地址:https:/github.com/jwtk/jjwt

  1. 导入坐标
		<dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.6.0</version>
        </dependency>
  1. 编写工具类
package com.why.utils;


import cn.hutool.core.lang.UUID;
import io.jsonwebtoken.*;

import java.util.*;

public class JJwtUtils {

    /**
     * 秘钥,不能公开
     */
    private static final String SECRET = "why2023.11.24";


    /**
     * 第一个版本
     *
     * @param map    map里面是用户信息
     * @param expire expire是以毫秒为单位的过期时间
     * @return 返回token字符串
     */
    public static String CreateToken(Map map, long expire) {
        //指定签名的时候使用的算法,也就是header那部分,jjwt已经将这部分内容封装好了。
        SignatureAlgorithm hs256 = SignatureAlgorithm.HS256;
        //生成jwt构造器
        JwtBuilder jwtBuilder = Jwts.builder();
        //生成签名的时候使用的秘钥secret,一般可以从本地配置文件中读取,切记这个秘钥不能外露哦。
        //它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
        //一定要放在前面,否则会使后面的设置无效
        jwtBuilder.setClaims(map)
                //jwt的唯一标识
                .setId(UUID.randomUUID().toString())
                //jwt的签发人
                .setIssuer("why")
                //iat: jwt的签发时间
                .setIssuedAt(new Date())
                //sub(Subject):代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,
                // 可以存放什么userid,username之类的,作为用户的唯一标志。
                .setSubject("LoginCheck")
                //设置有效期,当前时间+有效时间
                .setExpiration(new Date(System.currentTimeMillis() + expire))
                //设置签名使用的签名算法和签名使用的秘钥
                .signWith(hs256, SECRET);

        return jwtBuilder.compact();
    }

    /**
     * 第二个版本
     *
     * @param expire
     * @return
     */
    public static String CreateToken(long expire) {
        //指定签名的时候使用的算法,也就是header那部分,jjwt已经将这部分内容封装好了。
        SignatureAlgorithm hs256 = SignatureAlgorithm.HS256;
        //生成jwt构造器
        JwtBuilder jwtBuilder = Jwts.builder();
        //生成签名的时候使用的秘钥secret,一般可以从本地配置文件中读取,切记这个秘钥不能外露哦。
        //它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
        //一定要放在前面,否则会使后面的设置无效
        jwtBuilder.setId(UUID.randomUUID().toString())//jwt的唯一标识
                //jwt的签发人
                .setIssuer("why")
                //iat: jwt的签发时间
                .setIssuedAt(new Date())
                //sub(Subject):代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,
                // 可以存放什么userid,username之类的,作为用户的唯一标志。
                .setSubject("LoginCheck")
                //设置有效期,当前时间+有效时间
                .setExpiration(new Date(System.currentTimeMillis() + expire))
                //设置签名使用的签名算法和签名使用的秘钥
                .signWith(hs256, SECRET);

        return jwtBuilder.compact();
    }

    /**
     * 解析token获取用户信息
     *
     * @param token
     * @return
     */
    public static Claims parseJWT(String token) {
        //通过秘钥去解析token
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
        //返回解析后的内容
        return claimsJws.getBody();
    }


}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值