JWT & SpringBoot & 授权

2022-01-17 15:14:45 浏览数 (1)

JWT 官网:https://jwt.io/introduction/ JWT 在线校验:https://jwt.io/#debugger-io 推荐的测试代码:#Test

什么是 JWT?

JSON WEB TOKEN,它定义了一种紧凑且自包含的方式,用于将信息作为 JSON 对象安全地在各方之间传输信息。此信息可以验证和信任,因为它是数字签名。JWT 可以使用密钥(使用HMAC算法)或使用 RSA 或 ECDSA 进行公钥/私钥对进行签名

它有什么作用呢?(抄自JWT官网)

  • 授权:这是使用 JWT 的最常见方案。用户登录后,每个后续请求都将包括 JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销小,并且能够轻松地跨不同的域使用。
  • 信息交换:JSON 网络令牌是各方之间安全传输信息的一种好方式。由于可以对JWT进行签名(例如,使用公钥/私钥对)可以确定发件人就是他们说的。此外,由于使用标头和有效负载计算签名,您还可以验证内容是否未被篡改。

我们一般只去API使用 授权校验

为什么使用 JWT

  • 解决Session的内存占用问题 (存储于客户端)
  • 解决各个服务端 Session共享问题

JWT 认证流程

生成Token

  1. 客户端携带自己的信息 请求后台认证端口
  2. 后台核对客户端提交的信息,将用户信息作为JWT 令牌的负载(Payload)与头部进行Base64 编码的拼接,形成一个Token。格式例如Head.Payload.Singurater 以 “.” 进行拼接,这里要看仔细
  3. Token生成后,将其作为字符串以登录成功的返回结果返回给前端。
  4. 前端将后台生成的结果 存储在 loacalStorage 或者 sessionStorage上 (如果,用户退出登录,可以选择删除浏览器的响应信息即可)

使用 Token

  1. 后续用户请求,前端需要将JWT 放入http请求的 Header 中的 Authorization 位 (可以解决 XSS 和 XSRF 问题)
  2. 后台每次接受到请求,都要检查 JWT 是否存在,并验证有效性(是否有效、是是否过期等等),通过验证,就可以正常访问了。

JWT 的优势

  • 简洁 :可以通过http请求中的head 放入jwt ,其数据量小,传输速度块
  • 自包含:负载中,可以存储一部分信息,可以减少数据库的查询次数
  • 跨语言:字符串格式,任何web形式 都支持
  • 使用与微服务,不需要考虑共享问题

JWT 的结构组成 (部分抄自官网)

  • 头部(Header)
  • 负载(Payload)
  • 签名(Signature)

标头通常由两部分组成:令牌的类型(即 JWT)和正在使用的签名算法,如 HMAC SHA256 或 RSA。

例如:

代码语言:javascript复制
{
  "alg": "HS256",
  "typ": "JWT"
}

然后,此 JSON编码为 Base64Url,以形成 JWT 的第一部分。

负载

令牌的第二部分是有效负载,其中包含声明。声明是关于实体(通常为用户)和其他数据的语句。有三种类型的索赔:已登记、公共私人索赔。

  • 已注册声明:这些是一组预定义声明,不是强制性的,但建议提供一组有用的、可互操作的索赔。其中一些是:iss(发行人)、exp(到期时间)、(主题)、aud(访问者)和其他。请注意,声明名称只有三个字符,只要 JWT 是紧凑的。
  • 公共声明:这些可以由使用JWT的人可以当即定义。但是,为了避免冲突,应在IANA JSON Web 令牌注册表中定义它们,或定义为包含抗冲突命名空间的 URI。
  • 私人声明:这些是为在同意使用它们的各方之间共享信息而创建的自定义声明,它们既不是已注册的,也不是公开声明。

示例有效负载可能是:

代码语言:javascript复制
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

然后对有效负载进行 Base64Url编码,以形成 JSON Web 令牌的第二部分。

请注意,对于已签名的令牌,此信息虽然可防止篡改,但任何人都可以阅读。除非对 JWT 进行加密,否则不要将机密信息放在 JWT 的有效负载或标头元素中。

签名

要创建签名部分,您必须使用编码标头、编码有效负载、机密、标头中指定的算法,并签名。

例如,如果要使用 HMAC SHA256 算法,将采用以下方式创建签名:

代码语言:javascript复制
HMACSHA256(
  base64UrlEncode(header)   "."  
  base64UrlEncode(payload),
  secret)

签名用于验证邮件未随之更改,对于使用私钥签名的令牌,它还可以验证 JWT 的发件人是否为它所说的发件人。

放在一起

输出是三个 Base64-URL 字符串,由点分隔,这些点可以在 HTML 和 HTTP 环境中轻松传递,但与基于 XML 的标准(如 SAML)相比,更紧凑。

下面显示了一个 JWT,它具有以前的标头和有效负载编码,并且它使用机密进行签名。

如果要使用 JWT 并付诸实践,可以使用 jwt.io器解码、验证和生成 JWT。

JAVA 使用 JWT

生成令牌

导包

代码语言:javascript复制
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.13.0</version>
        </dependency>

调用 JWT.create() 方后面的链式调用,添加相关参数

代码语言:javascript复制
    // 生成Token
    @Test
    public void createJwt(){
        HashMap<String,Object> hashMap = new HashMap<>();
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.SECOND,100);              //  设置超时时间 100s
        String token = JWT.create()
                .withHeader(hashMap)                            //  Header
                .withClaim("username", "张三")       //  Payload
                .withClaim("age", 20)               //  Payload
                .withExpiresAt(instance.getTime())          //  设置超时时间
                .sign(Algorithm.HMAC256("!Govbuy2021JWT~ZLK")); //  自己生成的签名,足够复杂就行,不要泄露就行

        System.out.println("Token is in next line:");
        System.out.println(token);

    }

结果 如图

额外知识:

如果 相同的负载 字段名,例如 “username” 一个叫张三 、一个叫李四,那么就会默认使用代码顺序下面的”username”,即Claims 中 只会存在一个 “username”

验证令牌

调用 JWT 验证对象 JWTVerifier 的 verify() 方法 解析

代码语言:javascript复制
    @Test
    public void ValidateJwt(){
        JWTVerifier build = JWT.require(Algorithm.HMAC256("!Govbuy2021JWT~ZLK")).build();
        DecodedJWT verify = build.verify(""  
                "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTM2MTM3MjksImFnZSI6MjAsInVzZXJuYW1lIjoi5byg5LiJIn0.G2tQ5564EDx1V7yF4GT6tTCn5lLZHSSdOfpm3ae73w4");
        // 获取令牌内 包含的信息
        String username = verify.getClaim("username").asString();
        int age = verify.getClaim("age").asInt();
        System.out.println(username);
        System.out.println(age);
    }

如果令牌 超过设置的过期时间 ,就会报 令牌超时异常

代码语言:javascript复制
com.auth0.jwt.exceptions.TokenExpiredException: The Token has expired on Thu Feb 18 09:38:12 CST 2021.

整合SpringBoot

创建 自己的工具类

代码语言:javascript复制
import cn.hutool.core.date.DateUtil;
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.HashMap;
import java.util.Map;

/**
 * @author : zanglikun
 * @date : 2021/2/18 9:58
 * @Version: 1.0
 * @Desc : JWT生成工具。具体JWT文章请访问:
 */
public class JWTUtils {

    // 定义的加密密钥
    private static final String MYSign = "!HEFEI2021JWT~ZLK";

    /**
     * 生成 令牌
     * @param map 你创建的参数集合
     * @return
     */
    public static String getToken(Map<String, String> map) {

        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.DATE,7);  //  设置超时时间 7 天
        System.out.println(instance.getTime());
        // 创建 JWT Builder
        JWTCreator.Builder builder = JWT.create();
        // 设置 Payload
        map.forEach((k, v) -> {
            builder.withClaim(k, v);
        });

        // 超时时间 与 Sign
        String sign = builder.withExpiresAt(instance.getTime())
                .sign(Algorithm.HMAC256(MYSign));


        return sign;
    }

    /**
     * 验证 Token 的合法性 只要不抛异常,就算令牌 验证成功
     * @param token 你传递的token
     */
    public static void verifyToken(String token) {
        JWTVerifier build = JWT.require(Algorithm.HMAC256(MYSign)).build();
        DecodedJWT verify = build.verify(token);
        //System.out.println("签名验证成功,会打印此话");
    }

    /**
     * 获取 Token中 信息,以后 直接 通过 DecodedJWT 进行 .getClaim.as数据类型() 获取;
     * @param token  你传递的token
     * @return DecodedJWT 可用于获取数据
     */
    public static DecodedJWT getInfo(String token) {
        return JWT.require(Algorithm.HMAC256(MYSign)).build().verify(token);
    }

    public static void main(String[] args) {
        HashMap hashMap = new HashMap();
        hashMap.put("Abc你好123","Abc你好123");
        String token = JWTUtils.getToken(hashMap);
        /* JWT 常用方法
        JWTUtils.getInfo(token).getHeader();
        JWTUtils.getInfo(token).getPayload();
        JWTUtils.getInfo(token).getSignature();
        */
        System.out.println(JWTUtils.getInfo(token).getClaims());

        Long exp = JWTUtils.getInfo(token).getClaim("exp").asLong();
        //这里使用了Hutool的工具 打印时间
        System.out.println("过期时间是:"   DateUtil.date(exp*1000));
    }
}

未来在单体架构,就放在 拦截器 放行登录接口就行,减少验签等代码的冗余,分布式架构,就放在网关里面即可。

代码语言:javascript复制
        <!--Hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.15</version>
        </dependency>


// 创建JWT
    @Test
    public void JWT() {
        Map payload = new HashMap<>();
        payload.put("账号","123");
        payload.put("姓名","汪顺");
        // Token 有效期是15天 可不设置
        payload.put("expire_time",System.currentTimeMillis()   1000 * 60 * 60 * 24 * 15);
        // 生成Token并执行加密方式:hs256 密码是123456
        String token = JWTUtil.createToken(payload, JWTSignerUtil.hs256("123456".getBytes()));
        System.out.println(token);
    }





// 验证JWT
    @Test
    public void UnJWT() {
        // 这是我们生成的Token
        String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyLotKblj7ciOiIxMjMiLCLlp5PlkI0iOiLmsarpoboiLCJleHBpcmVfdGltZSI6MTYzNzU4MTIyMzQ4NH0.SWZd0KmcNkrRyDn9uTp1O3CkFaiCmSXlrf3Gyw-GYck";
        try {
            // 这是验证Token完整性 如果有异常,就说明Token不完整
            JWTValidator.of(token).validateAlgorithm(JWTSignerUtil.hs256("123456".getBytes()));
            // 判断Token是否超时
            JWTPayload payload = JWTUtil.parseToken(token).getPayload();
            Long nowtime = new Date().getTime();
            if (Long.valueOf((Long) payload.getClaim("expire_time")) < nowtime){
                System.out.println("Token超时");
               int i = 1/0;
            }
            // 打印负载的内容
            System.out.println(payload.getClaim("账号"));
            System.out.println(payload.getClaim("姓名"));
            System.out.println(DateUtil.date((Long) payload.getClaim("expire_time")));
        }catch (Exception e){
            System.out.println("Token校验不通过");
        }

    }

特殊说明: 解决问题的光鲜,藏着磕Bug的痛苦。 万物皆入轮回,谁也躲不掉! 以上文章,均是我实际操作,写出来的笔记资料,不会出现全文盗用别人文章!烦请各位,请勿直接盗用!

0 人点赞