Spring Security+OAuth2 + JWT認證以及攜帶使用者資訊
Spring Boot,Spring Security實現OAuth2 + JWT認證
閱讀此文,希望是對JWT以及OAuth2有一定了解的童鞋。
JWT認證,提供了對稱加密以及非對稱的實現。
涉及到原始碼中兩個服務
spring-boot-oauth-jwt-server
spring-boot-oauth-jwt-resource-server
認證服務端
提供認證、授權服務
實現方式,主要複寫AuthorizationServerConfigurerAdapter實現
認證服務1-對稱加密方式
對稱加密,表示認證服務端和認證客戶端的共用一個金鑰
實現方式
- AccessToken轉換器-定義token的生成方式,這裡使用JWT生成token,對稱加密只需要加入key等其他資訊(自定義)。
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("123");
return converter;
}
- 告訴spring security token的生成方式
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter())
.authenticationManager(authenticationManager);
}
以上對稱加密的JWT方式的認證服務端就OK了,後面有對應的資源服務端的內容。
認證服務2-非對稱加密方式(公鑰金鑰)
服務端生成公鑰和金鑰,每個客戶端使用獲取到的公鑰到伺服器做認證。
因此首先要生成一個證書,匯出公鑰再後續步驟
實現方式
生成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
生成公鑰文字
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhAF1qpL+8On3rF2M77lR +l3WXKpGXIc2SwIXHwQvml/4SG7fJcupYVOkiaXj4f8g1e7qQCU4VJGvC/gGJ7sW fn+L+QKVaRhs9HuLsTzHcTVl2h5BeawzZoOi+bzQncLclhoMYXQJJ5fULnadRbKN HO7WyvrvYCANhCmdDKsDMDKxHTV9ViCIDpbyvdtjgT1fYLu66xZhubSHPowXXO15 LGDkROF0onqc8j4V29qy5iSnx8I9UIMEgrRpd6raJftlAeLXFa7BYlE2hf7cL+oG hY+q4S8CjHRuiDfebKFC1FJA3v3G9p9K4slrHlovxoVfe6QdduD8repoH07jWULu qQIDAQAB -----END PUBLIC KEY-----
儲存為public.txt。把 mytest.jks和public.txt放入resource目錄下
- 這裡我們要修改JwtAccessTokenConverter,把證書匯入
@Bean
public TokenEnhancer accessTokenConverter() {
final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyStoreKeyFactory keyStoreKeyFactory =
new KeyStoreKeyFactory(new ClassPathResource("mytest.jks"), "mypass".toCharArray());
converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest"));
converter.setAccessTokenConverter(new CustomerAccessTokenConverter());
return converter;
}
以上,就可以實現非對稱加密了
額外資訊(這部分資訊不關乎加密方式)
- 自定義生成token攜帶的資訊
有時候需要額外的資訊加到token返回中,這部分也可以自定義,此時我們可以自定義一個TokenEnhancer
TokenEnhancer 介面提供一個 enhance(OAuth2AccessToken var1, OAuth2Authentication var2) 方法,用於對token資訊的新增,資訊來源於 OAuth2Authentication
這裡我們加入了使用者的授權資訊。
@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;
}
同樣要告訴spring security,我們把這個TokenEnhancer加入到TokenEnhancer鏈中(鏈,所以可以好多個)
// 自定義token生成方式
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(customerEnhancer(), accessTokenConverter()));
endpoints.tokenEnhancer(tokenEnhancerChain);
- 自定義token資訊中新增的資訊
JWT中,需要在token中攜帶額外的資訊,這樣可以在服務之間共享部分使用者資訊,spring security預設在JWT的token中加入了user_name,如果我們需要額外的資訊,需要自定義這部分內容。
JwtAccessTokenConverter是我們用來生成token的轉換器,所以我們需要配置這裡面的部分資訊來達到我們的目的。
JwtAccessTokenConverter預設使用DefaultAccessTokenConverter來處理token的生成、裝換、獲取。DefaultAccessTokenConverter中使用UserAuthenticationConverter來對應處理token與userinfo的獲取、轉換。因此我們需要重寫下UserAuthenticationConverter對應的轉換方法就可以
@Override
public Map<String, ?> convertUserAuthentication(Authentication authentication) {
LinkedHashMap response = new LinkedHashMap();
response.put("user_name", authentication.getName());
response.put("name", ((User) authentication.getPrincipal()).getName());
response.put("id", ((User) authentication.getPrincipal()).getId());
response.put("createAt", ((User) authentication.getPrincipal()).getCreateAt());
if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
response.put("authorities", AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
}
return response;
}
告訴JwtAccessTokenConverter ,把我們的方式替換預設的方式
@Bean
public TokenEnhancer accessTokenConverter() {
final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("123");
converter.setAccessTokenConverter(new CustomerAccessTokenConverter());
return converter;
}
資源服務端
實現方式,主要複寫ResourceServerConfigurerAdapter實現
資源服務1-對稱加密方式
此處配置與認證服務基本一致,不同的是,資源伺服器配置是在ResourceServerConfigurerAdapter做配置,其他的看原始碼吧,大差不差。
資源服務2-非對稱加密方式(公鑰)
把 public.txt放入resource目錄下
修改JwtAccessTokenConverter如下:
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
Resource resource = new ClassPathResource("public.txt");
String publicKey = null;
try {
publicKey = inputStream2String(resource.getInputStream());
} catch (final IOException e) {
throw new RuntimeException(e);
}
converter.setVerifierKey(publicKey);
converter.setAccessTokenConverter(new CustomerAccessTokenConverter());
return converter;
}
然後就可以跑起來了。
效果驗證
token獲取
code獲取:
http://127.0.0.1:8081/oauth/authorize?client_id=clientId&response_type=code&redirect_uri=http://127.0.0.1:8082/login/my
redirect_uri:需要與配置在認證伺服器中的一致。
client_id:client_id也是預先在認證伺服器中,這裡是儲存在資料庫裡的
response_type:寫死code
瀏覽器進入後的頁面。
輸入賬號密碼,這個也是儲存在資料庫,預設是儲存在記憶體中。
授權之後就可以獲得code
http://127.0.0.1:8082/login/my?code=rTKETX
根據這個code,POST下獲取token
http://127.0.0.1:8081/oauth/token?grant_type=authorization_code&code=rTKETX&redirect_uri=http://127.0.0.1:8082/login/my&client_id=clientId&client_secret=secret
grant_type:這裡寫死authorization_code
code:上面得到的rTKETX
redirect_uri:同上不變
client_id:同上不變
client_secret:對應的密碼
結果返回如下:
token驗證
http://127.0.0.1:8081/oauth/check_token?token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
使用者資訊獲取
資源服務端就不展示了…