单点登录实现
基于JWT的Token认证机制,服务端无需保存凭证信息,这种方案称为无状态方案
1,什么是JWT
JSON Web Token(JWT)是一个开放的行业标准,用于在通信双方传递JSON对象,传递的信息经过数字签名可以被验证和信任。通过对称加密算法及数字签名技术防止篡改,同时,资源服务器使用JWT技术可以实现不依赖认证服务器即可完成授权。
2,JWT工作流程
差别:对令牌的认证,是基于算法的方式,而不是去查询比如第三方的存储如Redis来做判断
3,JWT的组成部分
- 头部
- 载荷
- 签名
3.1 头部
头部用于描述JWT最基本信息。
例如其类型及签名所用的算法等,可以表示为一个JSON对象。
{
"type":"JWT",
"alg":"HS256"
}
然后将这部分通过BASE64进行编码,就得到第一部分。Java提供了BASE64Encoder和BASE64Decoder来实现编码和解码。
BASE64是一种基于64个可打印字符来表示二进制数据的表示方法。
因为2的6次方等于64,所以每6个比特为一个单元,对应某个可打印字符。
3.2 载荷
载荷是存放有效信息的地方。也是一个JSON对象。此处信息可以在客户端解密,所以不建议添加敏感信息,一般添加业务的必要信息即可。可以添加的声明如下:
iss:JWT签发者
sub:JWT所面向的用户
aud:接收JWT的一方
iat:JWT的签发时间
exp:JWT的过期时间,必须大于签发时间
nbf:定义在什么时间之前,该JWT都是不可用的
jti:JWT的唯一身份标识,主要用来作为一次性token
然后还是一样用BASE64进行编码之后,得到第二部分。
3.3 签证
签名的作用是防止JWT内容被篡改。
签名的算法规则:
HMACSHA256(BASE64Encoder(header)+"."+BASE64Encoder(payload), 密钥)
首先,通过BASE64Encoder(header)+"."+BASE64Encoder(payload)组成字符串,然后通过使用header中声明的加密算法+secret进行加密加盐,最终构成JWT的第三部分(签名)。
secret和jwt的签发都是在服务端生成,secret就是服务端的密钥。
4,Java的实现方案--JJWT
JJWT是一个提供JWT创建和验证的Java库。
5,引入依赖
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
6,生成Token案例
@Test
public void jwtTokenCreateTest(){
JwtBuilder builder = Jwts.builder()
.setId("666").setSubject("行走在牛A的路上")
.setIssuedAt(new Date())
//添加自定义属性
.claim("role","admin")
.setExpiration(new Date(new Date().getTime()+600000))
.signWith(SignatureAlgorithm.HS256,"key");
String jwtToken = builder.compact();
System.out.println(jwtToken);
}
7,解析Token,验证token的正确性
@Test
public void jwtTokenParseTest(){
String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI2NjYiLCJzdWIiOiLooYzotbDlnKjniZt" + "B55qE6Lev5LiKIiwiaWF0IjoxNTYxMjE2ODc4LCJleHAiOjE1NjEyMTY4ODF9.GhmAQ_G8aFExXc84Wefl13SwAJBqkDtQ05EsAqpAUUw";
Claims claims = Jwts.parser().setSigningKey("key")
.parseClaimsJws(token).getBody();
System.out.println(claims.getId());
System.out.println(claims.getSubject());
System.out.println(claims.getIssuedAt());
System.out.println(claims.getExpiration());
//获取属性
System.out.println(claims.get("role"));
}
注意:过期或者信息不正确,都会抛出相关异常。
8,开发工具类
public class JwtUtils {
//密钥由调用方来决定
private String secretKey;
//有效期也由调用方来决定
private long ttl;
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public long getTtl() {
return ttl;
}
public void setTtl(long ttl) {
this.ttl = ttl;
}
public String createJwtToken(String id,String subject){
long now = System.currentTimeMillis();
JwtBuilder jwtBuilder = Jwts.builder()
.setId(id).setSubject(subject)
.setIssuedAt(new Date(now))
.signWith(SignatureAlgorithm.HS256,secretKey);
if(ttl > 0){
jwtBuilder.setExpiration(new Date(now+ttl));
}
return jwtBuilder.compact();
}
public Claims parseJwtToken(String jwtToken){
return Jwts.parser().setSigningKey(secretKey)
.parseClaimsJws(jwtToken).getBody();
}
}