JWT 对称加密
JWT将 相关信息放在 令牌里
jwt全称 JSON Web Token。这个实现方式不用管如何进行存储(内存或磁盘),
因为它可以把相关信息数据编码存放在令牌里
。JwtTokenStore 不会保存任何数据,
但是它在转换令牌值以及授权信息方面与 DefaultTokenServices 所扮演的角色是一样的。
安全性
OAuth2提供了JwtAccessTokenConverter实现,添加jwtSigningKey,以此生成秘钥,以此进行签名,只有jwtSigningKey才能获取信息。
认证服务器
/**
* 对Jwt签名时,增加一个密钥
* JwtAccessTokenConverter:对Jwt来进行编码以及解码的类
*/
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("test-secret");
return converter;
}
/**
* 设置token 由Jwt产生,不使用默认的透明令牌
*/
@Bean
public JwtTokenStore jwtTokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.tokenStore(jwtTokenStore())
.accessTokenConverter(accessTokenConverter())
.allowedTokenEndpointRequestMethods(HttpMethod.GET,
HttpMethod.POST);
资源服务器
security:
oauth2:
resource:
user-info-uri: http://localhost:9098/users/current
jwt:
key-value: test-secret
认证
http://localhost:9098/oauth/token?grant_type=authorization_code&code=ABB3bH&client_id=client_2&client_secret=123456&redirect_uri=http://www.baidu.com
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjI5MTIyOTIsInVzZXJfbmFtZSI6InVzZXJfMSIsImF1dGhvcml0aWVzIjpbIlVTRVIiXSwianRpIjoiZDgwODZjMDEtYzczZi00ODgzLTgzODctMDBiYjBmZTY1MGY5IiwiY2xpZW50X2lkIjoiY2xpZW50XzIiLCJzY29wZSI6WyJhbGwiXX0.nQfDpO5fIZ5BJQr8FyxPkqosHRMxPDhMFjyimIC5FNg",
"token_type": "bearer",
"expires_in": 604799,
"scope": "all",
"jti": "d8086c01-c73f-4883-8387-00bb0fe650f9"
}
token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjIzNDUzNjgsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUExBVEZPUk1fTElTVCIsIkFQUExJQ0FUSU9OX0xJU1QiXSwianRpIjoiNTc5ODE1NjctMDEzMC00ZTIzLTkwZjMtZDk1ZmYzNzg3YTE5IiwiY2xpZW50X2lkIjoiY2R0eWUtaXRwcy1kc2MtY2xpZW50Iiwic2NvcGUiOlsicmVhZCIsIndyaXRlIiwidHJ1c3QiXX0.J8Hqf2fhD5_7_2-tYWOqHOk6WDBjF1wH0venMFZOQIY
解析后内容
{
alg: "HS256",
typ: "JWT"
}.
{
exp: 1562345368,
user_name: "admin",
authorities: [
"PLATFORM_LIST",
"APPLICATION_LIST"
],
jti: "57981567-0130-4e23-90f3-d95ff3787a19",
client_id: "cdtye-itps-dsc-client",
scope: [
"read",
"write",
"trust"
]
}.
token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjI5MTIyOTIsInVzZXJfbmFtZSI6InVzZXJfMSIsImF1dGhvcml0aWVzIjpbIlVTRVIiXSwianRpIjoiZDgwODZjMDEtYzczZi00ODgzLTgzODctMDBiYjBmZTY1MGY5IiwiY2xpZW50X2lkIjoiY2xpZW50XzIiLCJzY29wZSI6WyJhbGwiXX0.nQfDpO5fIZ5BJQr8FyxPkqosHRMxPDhMFjyimIC5FNg
解析后内容
{
alg: "HS256",
typ: "JWT"
}.
{
exp: 1562912292,
user_name: "user_1",
authorities: [
"USER"
],
jti: "d8086c01-c73f-4883-8387-00bb0fe650f9",
client_id: "client_2",
scope: [
"all"
]
}.
JWT 非对称加密(公钥秘钥)
生成jks 文件
keytool -genkeypair -alias mytest -keyalg RSA -keypass mypass -keystore mytest.jks -storepass mypass
导出公钥
keytool -list -rfc --keystore mytest.jks | openssl x509 -inform pem -pubkey
拷贝创建public.txt文件
mytest.jks 和 public.txt 放入认证中心 resource资源路径
认证服务端
/**
* 对Jwt签名时,增加一个密钥
* JwtAccessTokenConverter:对Jwt来进行编码以及解码的类
*/
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
ClassPathXmlApplicationContext cx = new ClassPathXmlApplicationContext();
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(cx.getResource("classpath:mytest.jks"), "mypass".toCharArray());
converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest"));
return converter;
}
/**
* 设置token 由Jwt产生,不使用默认的透明令牌
*/
@Bean
public JwtTokenStore jwtTokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
/**
* 用于扩展JWT
* @return
*/
@Bean
@ConditionalOnMissingBean(name = "jwtTokenEnhancer")
public TokenEnhancer jwtTokenEnhancer(){
return new MyJwtTokenEnhancer();
}
endpoints.authenticationManager(authenticationManager).userDetailsService(userDetailsService).reuseRefreshTokens(true)
.tokenStore(jwtTokenStore())
.allowedTokenEndpointRequestMethods(HttpMethod.GET,
HttpMethod.POST);
// 自定义token生成方式
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(new MyJwtTokenEnhancer(),accessTokenConverter()));
endpoints.tokenEnhancer(tokenEnhancerChain);
public class MyJwtTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
final Map<String, Object> additionalInfo = new HashMap<>();
User user = (User) authentication.getUserAuthentication().getPrincipal();
additionalInfo.put("username", user.getUsername());
additionalInfo.put("authorities_", user.getAuthorities());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
资源服务端
ResourceServerConfigurerAdapter
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
ClassPathXmlApplicationContext cx = new ClassPathXmlApplicationContext();
Resource resource = cx.getResource("classpath:public.txt");
String publicKey = null;
try {
publicKey = inputStream2String(resource.getInputStream());
} catch (final IOException e) {
throw new RuntimeException(e);
}
converter.setVerifierKey(publicKey);
return converter;
}
private String inputStream2String(InputStream inputStream) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
StringBuffer buffer = new StringBuffer();
String line = "";
while ((line = in.readLine()) != null){
buffer.append(line);
}
return buffer.toString();
}
token
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjI5MTg3MzQsInVzZXJfbmFtZSI6InVzZXJfMSIsImF1dGhvcml0aWVzIjpbIlVTRVIiXSwianRpIjoiMjM5ZjNmZDctOGVlYS00ZmQ0LWE3MTYtY2IyODJjY2ExODAxIiwiY2xpZW50X2lkIjoiY2xpZW50XzIiLCJzY29wZSI6WyJhbGwiXX0.eXsTBbbtnUILD7KwNnhW8yAlH3FvDZE7k8RnLze5dJ9vkI_L_qVCOJ86DdcM_W8HEBTfZ3GetWkASBfmfrdH9pmDMU6EP--4dcIBTJ6-BYTp5nHpuzkREzZx6mjepDgfuKYpueJ6zqNzuaTJalozb7zsuGm8c8syv5urM9PK5iI5ndudG_w3RoW6PlkFVLdDcST5NF-K_flcOS7g40rw16gzLo-I2FH5JEHKVFbInlCvOlaITc9ren90aN3E1YKfngaPoY7-r0HUSqduqQsqVlLk3ckg4pitTuxVjyfU5PtCQZNKL4-TIB5IaotnshnSg15sAg9vnu0wvCnGhHOlqQ
解析后内容
{
alg: "RS256",
typ: "JWT"
}.
{
exp: 1562918734,
user_name: "user_1",
authorities: [
"USER"
],
jti: "239f3fd7-8eea-4fd4-a716-cb282cca1801",
client_id: "client_2",
scope: [
"all"
]
}.
携带自定义信息的token
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyXzEiLCJzY29wZSI6WyJhbGwiXSwiZXhwIjoxNTYyOTE5NjY5LCJhdXRob3JpdGllc18iOlt7ImF1dGhvcml0eSI6IlVTRVIifV0sImF1dGhvcml0aWVzIjpbIlVTRVIiXSwianRpIjoiMjZkMWZkOGMtNTFiYS00NzIxLTg4ZDYtYzUxNTcwNmZhMDY2IiwiY2xpZW50X2lkIjoiY2xpZW50XzIiLCJ1c2VybmFtZSI6InVzZXJfMSJ9.EnhmeHF8Bykd6-AjerrMjYrb6orH7foRvturU62j6j76iqoSEQ-LtjTOAxVUlSjkkIjblzdD0qgJ_HNnKTZROttPqF59Ej8IbCtfrPh08JxaaeBBkhpMHwSM178Zd70eBUGVUt9k28ujF7l9C5HBxVZtxTEo4x7SuWlo5M1x0_mKyktvyGBzBWxievEhHVH59rtmh9vMc6RIfbMx-bzrVeDj6Pw2ARv9miyEqekh7sULTJugRZ4iQhg6H4j8NuodvV7UsLzQXm-5rpDiue69_WgYXMhtYYYdDdrVcBvBLnGjOuZ8u40aLls9uRFoiNPXdBxNhzTSjrXC2Z8pdvTsKw
{
alg: "RS256",
typ: "JWT"
}.
{
user_name: "user_1",
scope: [
"all"
],
exp: 1562919669,
authorities_: [
{
authority: "USER"
}
],
authorities: [
"USER"
],
jti: "26d1fd8c-51ba-4721-88d6-c515706fa066",
client_id: "client_2",
username: "user_1"
}.
JWT 自定义token客户端验证
- RemoteTokenServices 调认证中心CheckToken
@Override
public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {
MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
formData.add(tokenName, accessToken);
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", getAuthorizationHeader(clientId, clientSecret));
Map<String, Object> map = postForMap(checkTokenEndpointUrl, formData, headers);
if (map.containsKey("error")) {
if (logger.isDebugEnabled()) {
logger.debug("check_token returned error: " + map.get("error"));
}
throw new InvalidTokenException(accessToken);
}
// gh-838
if (map.containsKey("active") && !"true".equals(String.valueOf(map.get("active")))) {
logger.debug("check_token returned active attribute: " + map.get("active"));
throw new InvalidTokenException(accessToken);
}
return tokenConverter.extractAuthentication(map);
}
- 拓展JWT 携带用户信息
- 重写资源服务器处理行为 重写 remoteTokenServices