你好我是辰兮,很高兴你能来阅读,本篇给是初学JWT单点登录后的困惑,现在源码学习后更加了解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篇博客