JSON Web Token (JWT)
自包含
token的創建
org.springframework.security.oauth2.provider.token.DefaultTokenServices#createAccessToken(org.springframework.security.oauth2.provider.OAuth2Authentication, org.springframework.security.oauth2.common.OAuth2RefreshToken)
總結
可見token根據UUID生成的
所以token裏面的信息是無意義的
看上節redis裏面token的存儲,是把相關信息與token做了關聯
如過redis服務器掛掉了,那麼這個token就是毫無意義的,以爲他本身不包含任何信息
jwt 恰恰相反,它是自包含了相關的信息
密籤
密籤的意思 不是 jwt 信息的加密或解密
他是指如果 jwt 裏面的信息被修改以後 我們能夠通過密簽發現
jwt不能存儲敏感信息
可擴展
可以在裏面放任何數據
替換默認token
TokenStoreConfig
這個配置裏面配置了兩個TokenStore
根據不同的配置確認token的類型
默認是JwtTokenStore
@Configuration
public class TokenStoreConfig {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
@ConditionalOnProperty(prefix = "whale.security.oauth2",name = "storeType",havingValue = "redis",matchIfMissing = false)
public TokenStore tokenStore(){
return new RedisTokenStore(redisConnectionFactory);
}
@Configuration
@ConditionalOnProperty(prefix = "whale.security.oauth2",name = "storeType",havingValue = "jwt",matchIfMissing = true)
//prefix 是隻application配置文件中 配置的最後一個點分隔後前面的一部分 這叫前綴
//name 對應的是後綴 最後一個點分隔後的後面的那部分
//即檢查的配置項爲 whale.security.oauth2.storeType
//havingValue = "jwt" 當配置項的值爲jwt的時候 這個類裏面的所有配置生效
//matchIfMissing 如果配置文件裏沒有找到這個屬性,即默認生效
public static class JwtTokenConfig{
@Autowired
private SecurityProperties securityProperties;
//token 的存儲
@Bean
public TokenStore jwtTokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter());
}
//token生成處理
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey(securityProperties.getOauth2().getJwtSigningKey());
return jwtAccessTokenConverter;
}
}
}
WhaleAuthenticationServiceConfig
OAuth2Properties
/**
* 這個是祕鑰 一定要保存好
*
* 發出去的令牌要用它簽名
* 收到的令牌也要用它來驗籤
*
* jwt 是token的標準協議,唯一的安全性就是這個祕鑰
*
*/
private String jwtSigningKey = "whale";
測試 jwt
用表單登錄獲取token的方式
發現 這次的 access_token 很長
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NTg2MDg5NjQsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iLCJST0xFX1VTRVIiXSwianRpIjoiMzYzNjJiZWUtMjFjOS00ZWM3LWIxMmUtYjQyYzYyYzViZjI5IiwiY2xpZW50X2lkIjoid2hhbGUiLCJzY29wZSI6WyJhbGwiLCJyZWFkIiwid3JpdGUiXX0.PJ1vcPCccfVfrHxCJzRs19xoFV6hnu5lMetJuYARylw",
https://jwt.io/ jwt解析
訪問資源成功
jwt token加額外信息
實現TokenEnhancer
WhaleTokenEnhancer
public class WhaleTokenEnhancer implements TokenEnhancer {
/**
*
* @param oAuth2AccessToken
* @param oAuth2Authentication
* @return
*/
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
Map<String,Object> info = new HashMap<>();
info.put("company","埃森哲");
((DefaultOAuth2AccessToken)oAuth2AccessToken).setAdditionalInformation(info);
return oAuth2AccessToken;
}
}
TokenStoreConfig 配置 TokenEnhancer bean
·······························
public static class JwtTokenConfig{
·····················
@Bean
@ConditionalOnMissingBean(name = "jwtTokenEnhancer")
public TokenEnhancer jwtTokenEnhancer(){
return new WhaleTokenEnhancer();
}
}
WhaleAuthenticationServiceConfig 中配置tokenEnhancerChain
部分代碼如下
@Autowired(required = false)
private TokenEnhancer jwtTokenEnhancer;
/**
*
* @param endpoints oauth/token 的入口點
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// super.configure(endpoints);
// endpoints.authenticationManager(authenticationManager).userDetailsService(userDetailsService);
endpoints.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
if(jwtAccessTokenConverter!=null && jwtTokenEnhancer!=null){
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> enhancers = new ArrayList<>();
enhancers.add(jwtTokenEnhancer);
enhancers.add(jwtAccessTokenConverter);
tokenEnhancerChain.setTokenEnhancers(enhancers);
endpoints.tokenEnhancer(tokenEnhancerChain)
.accessTokenConverter(jwtAccessTokenConverter);
}
}
測試
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCIsInJlYWQiLCJ3cml0ZSJdLCJjb21wYW55Ijoi5ZD5qOu5ZOyIiwiZXhwIjoxNTU4NjEzNjQzLCJhdXRob3JpdGllcyI6WyJhZG1pbiIsIlJPTEVfVVNFUiJdLCJqdGkiOiI5OTA3NWZjOC04MTI3LTQ2YWYtYTM2OS0wNTlkODlkNTIwMTEiLCJjbGllbnRfaWQiOiJ3aGFsZSJ9.1Ild0mbgxQn1No0NQIQI2gWrTDFDcgdHYHdC_kdGq_w
解析如下
Authentication解析額外信息?
注意 雖然我們的jwt可以攜帶額外信息
但是 我們的spring 只是 將 jwt規範裏面的信息解析成 Authentication
如果我們需要jwt token 裏的額外信息呢?
爲此我們需要加入新的依賴
https://jwt.io/
https://www.jsonwebtoken.io/
demo pom 文件中加依賴
試了幾個 0.9.0 版本可以
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
解析額外信息 UserController
@GetMapping("/me11")
public Object getCurrentUser11(Authentication authentication, HttpServletRequest request) throws UnsupportedEncodingException {
//spring 會自動找到Authentication類型的數據注入
String header = request.getHeader("Authorization");
String token = StringUtils.substringAfter(header, "Bearer ");
//需要用祕鑰來驗證簽名
//注意 簽名的時候 默認用的是祕鑰的utf-8編碼 ,同時驗籤的時候也要知道 編碼 utf-8
Claims claims = Jwts.parser().setSigningKey(securityProperties.getOauth2().getJwtSigningKey().getBytes("utf-8"))
.parseClaimsJws(token).getBody();
String company = (String) claims.get("company");
System.out.println(company);
return authentication;
}
測試
token令牌的刷新
refresh_token
在用戶無感的情況下用refresh_token得到一個新的token
測試
但我的refresh_token沒有測試成功
也不知道什麼原因
按道理是完全可以的,也不知道問題出在哪
https://auth0.com/docs/tokens/refresh-token/current#9e51c5c22d4b450cab209018dc3be588_java
Use a Refresh Token
To exchange the Refresh Token you received during authorization for a new Access Token, make a POST request to the /oauth/token endpoint in the Authentication API, using grant_type=refresh_token.
curl --request POST \
--url 'https://YOUR_DOMAIN/oauth/token' \
--header 'content-type: application/x-www-form-urlencoded' \
--data 'grant_type=refresh_token&client_id=%24%7Baccount.clientId%7D&client_secret=YOUR_CLIENT_SECRET&refresh_token=YOUR_REFRESH_TOKEN'
https://openapi.baidu.com/wiki/index.php?title=使用Refresh_Token獲取Access_Token
https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/
https://auth0.com/docs/tokens/refresh-token/current
https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/