JWT单点登录(源码学习)

你好我是辰兮,很高兴你能来阅读,本篇给是初学JWT单点登录后的困惑,现在源码学习后更加了解JWT的结构,小结下来,献给初学者,共同成长,一起进步。



一、JWT的结构

JWT的结构是什么样的?
在这里插入图片描述
token构成包含三个部分:

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

在这里插入图片描述

//格式如下
xxx.yyy.zzz

在这里插入图片描述
①JWT-header(头信息)

由两部分组成,令牌类型(即:JWT)散列算法(HMAC、RSASSA、RSASSA-PSS等)

JWT头部分是一个描述JWT元数据的JSON对象,通常如下所示。

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

然后,这个JSON被编码为Base64Url,形成JWT的第一部分。


②有效载荷payload

有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。

JWT的第二部分是payload,其中包含claims。claims是关于实体(常用的是用户信息)和其他数据的声明

claims有三种类型: registered, public, and private claims。

  • Registered claims: 这些是一组预定义的claims,非强制性的,但是推荐使用, iss(发行人), exp(到期时间),sub(主题), aud(观众)等;
  • Public claims: 自定义claims,注意不要和JWT注册表中属性冲突,这里可以查看JWT注册表;
  • Private claims:这些是自定义的claims,用于在同意使用这些claims的各方之间共享信息,它们既不是Registered claims,也不是Public claims;
{
  "sub": "1234567890",
  "name": "Tom",
  "admin": true
}

请注意,默认情况下JWT是未加密的,任何人都可以解读其内容,因此不要构建隐私信息字段,存放保密信息,以防止信息泄露。

JSON对象也使用Base64 URL算法转换为字符串保存。


③签名哈希

签名哈希部分是对上面两部分数据签名,通过指定的算法生成哈希,以确保数据不会被篡改。

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SfcKxwRJSMeKF2QT4fwpMeJf36POkayJV_adQssw6f

在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用"."分隔,就构成整个JWT对象。


④Base64URL算法

如前所述,JWT头和有效载荷序列化的算法都用到了Base64URL。该算法和常见Base64算法类似,稍有差别。

作为令牌的JWT可以放在URL中(例如api.example/?token=xxx)。 Base64中用的三个字符是"+","/“和”=",由于在URL中有特殊含义,因此Base64URL中对他们做了替换:"=“去掉,”+“用”-“替换,”/“用”_"替换,这就是Base64URL算法。


二、JWT源码学习

  • 参考一下常见的代码初学者可能看上去很复杂,接下来分析一波
//登录成功之后,需要生成token
String token = Jwts.builder().setSubject("用户名/用户信息") //主题,可以放用户的详细信息
        .setIssuedAt(new Date()) //token创建时间
        .setExpiration(new Date(System.currentTimeMillis() + 60000)) //token过期时间
        .setId("用户ID") //用户ID
        .setClaims(hashMap) //配置角色信息
        .signWith(SignatureAlgorithm.HS256, "WuHan") //加密方式和加密密码
        .compact();
  • 首先Jwts是Java已经被人写好的一个被final修饰的类,然后里面自带很多方法
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package io.jsonwebtoken;

import io.jsonwebtoken.impl.DefaultClaims;
import io.jsonwebtoken.impl.DefaultHeader;
import io.jsonwebtoken.impl.DefaultJwsHeader;
import io.jsonwebtoken.impl.DefaultJwtBuilder;
import io.jsonwebtoken.impl.DefaultJwtParser;
import java.util.Map;

public final class Jwts {
    private Jwts() {
    }

    public static Header header() {
        return new DefaultHeader();
    }

    public static Header header(Map<String, Object> header) {
        return new DefaultHeader(header);
    }

    public static JwsHeader jwsHeader() {
        return new DefaultJwsHeader();
    }

    public static JwsHeader jwsHeader(Map<String, Object> header) {
        return new DefaultJwsHeader(header);
    }

    public static Claims claims() {
        return new DefaultClaims();
    }

    public static Claims claims(Map<String, Object> claims) {
        return new DefaultClaims(claims);
    }

    public static JwtParser parser() {
        return new DefaultJwtParser();
    }

    public static JwtBuilder builder() {
        return new DefaultJwtBuilder();
    }
}

  • JwtBuilder是一个接口,里面也自带很多方法,每一个方法的返回值类型是自己本身,这里就是给用户设置相关信息的,所以代码你看上去是连着set的。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package io.jsonwebtoken;

import java.security.Key;
import java.util.Date;
import java.util.Map;

public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
    JwtBuilder setHeader(Header var1);

    JwtBuilder setHeader(Map<String, Object> var1);

    JwtBuilder setHeaderParams(Map<String, Object> var1);

    JwtBuilder setHeaderParam(String var1, Object var2);

    JwtBuilder setPayload(String var1);

    JwtBuilder setClaims(Claims var1);

    JwtBuilder setClaims(Map<String, Object> var1);

    JwtBuilder addClaims(Map<String, Object> var1);

    JwtBuilder setIssuer(String var1);

    JwtBuilder setSubject(String var1);

    JwtBuilder setAudience(String var1);

    JwtBuilder setExpiration(Date var1);

    JwtBuilder setNotBefore(Date var1);

    JwtBuilder setIssuedAt(Date var1);

    JwtBuilder setId(String var1);

    JwtBuilder claim(String var1, Object var2);

    JwtBuilder signWith(SignatureAlgorithm var1, byte[] var2);

    JwtBuilder signWith(SignatureAlgorithm var1, String var2);

    JwtBuilder signWith(SignatureAlgorithm var1, Key var2);

    JwtBuilder compressWith(CompressionCodec var1);

    String compact();
}

JWT的解析

try {
    JwtParser parser = Jwts.parser();
    parser.setSigningKey("WuHan");//解析 要和上面“暗号”一样
    Jws<Claims> claimsJws = parser.parseClaimsJws(token);
    Claims body = claimsJws.getBody();
    String username = body.getSubject();
  //  Object role = body.get("role");

    return true;
} catch (ExpiredJwtException e) {
    e.printStackTrace();
} catch (UnsupportedJwtException e) {
    e.printStackTrace();
} catch (MalformedJwtException e) {
    e.printStackTrace();
} catch (SignatureException e) {
    e.printStackTrace();
} catch (IllegalArgumentException e) {
    e.printStackTrace();
}

setSigningKey() 与builder中签名方法signWith()对应,parser中的此方法拥有与signWith()方法相同的三种参数形式,用于设置JWT的签名key,用户后面对JWT进行解析。

package io.jsonwebtoken;

import java.security.Key;
import java.util.Date;

public interface JwtParser {
    char SEPARATOR_CHAR = '.';

    JwtParser requireId(String var1);

    JwtParser requireSubject(String var1);

    JwtParser requireAudience(String var1);

    JwtParser requireIssuer(String var1);

    JwtParser requireIssuedAt(Date var1);

    JwtParser requireExpiration(Date var1);

    JwtParser requireNotBefore(Date var1);

    JwtParser require(String var1, Object var2);

    JwtParser setClock(Clock var1);

    JwtParser setAllowedClockSkewSeconds(long var1);

    JwtParser setSigningKey(byte[] var1);

    JwtParser setSigningKey(String var1);

    JwtParser setSigningKey(Key var1);

    JwtParser setSigningKeyResolver(SigningKeyResolver var1);

    JwtParser setCompressionCodecResolver(CompressionCodecResolver var1);

    boolean isSigned(String var1);

    Jwt parse(String var1) throws ExpiredJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;

    <T> T parse(String var1, JwtHandler<T> var2) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;

    Jwt<Header, String> parsePlaintextJwt(String var1) throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;

    Jwt<Header, Claims> parseClaimsJwt(String var1) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;

    Jws<String> parsePlaintextJws(String var1) throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;

    Jws<Claims> parseClaimsJws(String var1) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
}

Claims认证很难解释清楚,甚至我都找不到一个合适的中文词语来翻译它。只好用一个比喻来说明。

  • 在实行社保卡之前,我们去医院看病的时候,需要拿着身份证去办理一张就诊卡,办卡的工作人员校验完你的身份证以后,会将你的个人信息录入到卡里面。当你去找医生就诊的时候,医生扫描一下你的就诊卡,就知道了你的所有信息。这个就诊卡就相当于Claims认证中的token,里面的每条信息就是一个Claim.
package io.jsonwebtoken;

import java.util.Date;
import java.util.Map;

public interface Claims extends Map<String, Object>, ClaimsMutator<Claims> {
    String ISSUER = "iss";
    String SUBJECT = "sub";
    String AUDIENCE = "aud";
    String EXPIRATION = "exp";
    String NOT_BEFORE = "nbf";
    String ISSUED_AT = "iat";
    String ID = "jti";

    String getIssuer();

    Claims setIssuer(String var1);

    String getSubject();

    Claims setSubject(String var1);

    String getAudience();

    Claims setAudience(String var1);

    Date getExpiration();

    Claims setExpiration(Date var1);

    Date getNotBefore();

    Claims setNotBefore(Date var1);

    Date getIssuedAt();

    Claims setIssuedAt(Date var1);

    String getId();

    Claims setId(String var1);

    <T> T get(String var1, Class<T> var2);
}

上述Claims接口中定义了一些变量,我找到了相关图片给你们参考

iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号

在这里插入图片描述


三、JWT 的特点小结

在这里插入图片描述

(1)JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。

(2)JWT 不加密的情况下,不能将秘密数据写入 JWT。

(3)JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。

(4)JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑

(5)JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。

(6)为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。


The best investment is to invest in yourself

在这里插入图片描述

2020.06.04 记录辰兮的第76篇博客

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员可乐丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值