一、JWT令牌介绍
通过Spring Cloud Security OAuth2的测试我们发现,当资源服务和授权服务不在一起时资源服务使用RemoteTokenServices远程请求授权服务谭政token,如果访问量大将会影响系统的性能。
为了解决上面的问题,可以采用JWT格式即可解决,用户认证通过后会得到一个JWT令牌,JWT令牌中已经包含了用户相关的信息,客户端只需要哦携带JWT访问资源服务,资源服务根据事先约定的算法自行完成令牌校验,无需每次都请求认证服务完成校验。
1、什么是JWT?
JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),它定义了一种简介的、自包含的协议格式,用于在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。JWT可以使用HMAC算法或者RSA的公钥/私钥对签名,防止被篡改。
JWT官网、JWT标准
JWT令牌的优点:
1)jwt基于json,非常方便解析。
2)可以在令牌中自定义丰富的内容,易扩展。
3)通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高。
4)资源服务使用JWT可不依赖认证服务即可完成校验授权。
JWT令牌的缺点:
1)JWT令牌较长,占存储空间比较大。
2、JWT令牌结构
通过学习JWT令牌结构为自定义JWT令牌打好基础。
JWT令牌由三部分(Header、Payload、Signature)组成,每部分中间使用点(.)分隔,比如:xxxx.yyyy.zzzz(eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.cThIIoDvwdueQB468K5xDc5633seEFoqwxjF_xSJyQQ
)。
1)Header
头部包括令牌的类型(即JWT)及使用的哈希算法(如HMACSHA256或RSA),下边是Header部分的内容:
{
"alg": "HS256",
"typ": "JWT"
}
2)Payload
第二部分是负载,内容也是一个json对象,它是存放有效信息的地方,它可以存放jwt提供的现成字段,比如:iss(签发者)、exp(过期时间戳)、sub(面向的用户)等,也可自定义字段。此部分不建议存放敏感信息,因为此部分可以解码还原原始内容。将第二部分负载使用Base64Url编码,得到一个字符串就是JWT令牌的第二部分。
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
3)Signature
第三部分是签名,此部分用于防止jwt内容被篡改。这个部分使用Base64Url将前面两部分进行编码,编码后使用点(.)连接组成字符串,最后使用Header中声明的签名算法进行签名。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
your-256-bit-secret
)
base64UrlEncode(header):jwt令牌的第一部分Header。
base64UrlEncode(payload):jwt令牌的第二部分Payload。
your-256-bit-secret:签名所使用的的秘钥。
二、生成令牌和校验令牌
2.1 生成JWT令牌
在UAA中配置jwt令牌服务,即可实现生成jwt格式的令牌。
1、TokenConfig
/**
* Token令牌配置
*/
@Configuration
public class TokenConfig {
/**
* jwt令牌加密秘钥
*/
private static final String SIGNING_KEY = "uaa123";
/**
* 令牌的存储策略:使用jwt令牌
* @return TokenStore
*/
@Bean
public TokenStore tokenStore() {
// JWT令牌存储方案
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
// 对称秘钥,资源服务器使用该秘钥来验证
converter.setSigningKey(SIGNING_KEY);
return converter;
}
/**
* 临牌的存储策略
* @return TokenStore
*/
/*@Bean
public TokenStore tokenStore() {
// 使用内存方式存储令牌(普通令牌)
return new InMemoryTokenStore();
}*/
}
2、定义JWT令牌服务AuthorizationServer
/**
* 令牌管理服务
* @return
*/
@Bean
public AuthorizationServerTokenServices tokenService() {
DefaultTokenServices service = new DefaultTokenServices();
// 客户端详情服务
service.setClientDetailsService(clientDetailsService);
// 是否产生支持刷新令牌
service.setSupportRefreshToken(true);
// 令牌存储策略
service.setTokenStore(tokenStore);
// 设置令牌增强
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Collections.singletonList(accessTokenConverter));
service.setTokenEnhancer(tokenEnhancerChain);
// 令牌桶默认有效期2小时
service.setAccessTokenValiditySeconds(7200);
// 刷新令牌默认有效期3天
service.setRefreshTokenValiditySeconds(259200);
return service;
}
2.2 校验JWT令牌
资源服务需要和授权服务拥有一致的签名、令牌服务等:
1、将授权服务中的TokenConfig类拷贝到资源服务项目中。
2、屏蔽掉资源服务中原来的令牌服务类,不再使用HTTP的方式调用授权服务来校验JWT令牌。
/**
* Token配置
*/
@Configuration
public class TokenConfig {
/**
* jwt令牌加密秘钥,必须和uaa授权服务端保持一致
*/
private static final String SIGNING_KEY = "uaa123";
/**
* 令牌的存储策略:使用jwt令牌
* @return TokenStore
*/
@Bean
public TokenStore tokenStore() {
// JWT令牌存储方案
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
// 对称秘钥,资源服务器使用该秘钥来验证
converter.setSigningKey(SIGNING_KEY);
return converter;
}
}