使用JWT的OAuth2的SSO分析

參考:https://github.com/spring-guides/tut-spring-security-and-angular-js/blob/master/oauth2/README.adoc
http://jwt.io/introduction/
本文在<使用OAuth2的SSO分析>文章的基礎上擴展,使用jwt可減少了向認證服務器的請求,但jwt比swt(Simple Web Tokens)要長不少,還要依賴公鑰解密.
這裏寫圖片描述
1.瀏覽器向UI服務器點擊觸發要求安全認證
2.跳轉到授權服務器獲取授權許可碼
3.從授權服務器帶授權許可碼跳回來
4.UI服務器向授權服務器獲取AccessToken
5.返回AccessToken到UI服務器
6.發出/resource/請求到UI服務器
7.UI服務器將/resource/請求轉發到Resource服務器
Resource服務器從請求取出accessToken,解碼,直接轉化爲認證授權信息進行判斷後(最後會響應給UI服務器,UI服務器再響應給瀏覽中器)

這裏與<使用OAuth2的SSO分析>主要不同的是,accessToken是jwt,經過解碼,轉化就可成爲認證授權信息,無需再向授權服務器協助獲得認證授權信息,關於jwt可參看前面提供的鏈接.本文還修改了自定義登錄頁和授權頁,這種方案開始接近於生產了.

一.先創建OAuth2授權服務器
1.因爲使用了自定義頁面,添加了wro4j-maven-plugin插件和以下依賴到pom.xml

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-jwt</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

2.主類修改比較大,主類繼承WebMvcConfigurerAdapter主要是註冊視圖控制器;繼承WebSecurityConfigurerAdapter的內部類主要修改自定義權限控制;關鍵是繼承AuthorizationServerConfigurerAdapter的授權服務器配置,裏面配置了JwtAccessTokenConverter(密鑰就在這裏使用),並使用這個Bean;@EnableResourceServer一樣是放在主類上.
3.application配置將oauth的配置移到了OAuth2AuthorizationConfig內部類內部.增加了一個密鑰庫文件和兩個freemarker頁面
啓動授權服務器後,可測試了:
a.打開瀏覽器輸入地址http://localhost:9999/uaa/oauth/authorize?response_type=code&client_id=acme&redirect_uri=http://example.com發出請求,然後根據以上配置,輸入用戶名/密碼,點同意,獲取返回的授權許可碼
b.在linux的bash或mac的terminal輸入
[root@dev ~]#curl acme:[email protected]:9999/uaa/oauth/token \
-d grant_type=authorization_code -d client_id=acme \
-d redirect_uri=http://example.com -d code=fjRdsL
回車獲取access token,其中fjRdsL替換上步獲取的授權許可碼.返回結果類似如下:
{"access_token":"eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0NTk1NTUxNTYsInVzZXJfbmFtZSI6InVzZXIiLCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIiwiUk9MRV9VU0VSIl0sImp0aSI6IjI5MjcyYWJiLTQ4MjUtNGYwMS1hZjllLTg5ZGE1ZDE1MDBiNyIsImNsaWVudF9pZCI6ImFjbWUiLCJzY29wZSI6WyJvcGVuaWQiXX0.cQd88GYItHUDJuwkd_Rd0Yo8QM1R0dccuK0-xZ4OynC7EnqClLunaNOZ9jXwtilIFJNxbkbhQ8ymXdvlAF5Zjo8lpRGotdVo9rgQc39BDse7hGy1EfA9ZADQmJ-EuwkTNo0IBEXYC33XxQNK_3I_E92cnIPXq-FZHuZMRzpr-SlriwLa3aZVidmeyXK2U5dsjViWoHHKhcg-9c-VBPtyTJfPZOvj3s7DrbfCgOAGOhHkd_MBCdLDFb7QFhzIRsMfcD9rOAGTqk-hU2pHkkakKQ7_vL604UU7Qh3Zzkn6VbHPy0HAAiB9cnUhkQxK3Qb-wbHG-l3FC2pDlhtlhMHNfg","token_type":"bearer","refresh_token":"eyJhbGciOiJSUzI1NiJ9.eyJ1c2VyX25hbWUiOiJ1c2VyIiwic2NvcGUiOlsib3BlbmlkIl0sImF0aSI6IjI5MjcyYWJiLTQ4MjUtNGYwMS1hZjllLTg5ZGE1ZDE1MDBiNyIsImV4cCI6MTQ2MjEwMzk1NiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiIsIlJPTEVfVVNFUiJdLCJqdGkiOiIzNWM5OWY0Yy0xMGM0LTQ5ZTAtODAwYi1lZTc5ZTQ3ODNkNmUiLCJjbGllbnRfaWQiOiJhY21lIn0.bUvJ9HmrFU92euLzd5eesJKFlav5v1WyfBEgd3pO6I2D2yYy98oPwfNwCrbP44M2ilO48LJEovLLoZFYvjfA8xe6XO1Fx55Tik5SrWfizAEsNFsFg25zE92T3YNocStxuJWFSVBLlwjtxpVmnHOgPefku2G6N5seziX0SOBJleHSUObNAYtiBVQjKWXA3jGnMoZSP0dMbgtrWinwRJLwvaMgMDNnxYFSdvSW99XKjCyQNVmbGa4aRyy-xblTr7qlSqdcZIdRBfKkHM5S9jaenNVc85vGAYQFPrdkRWhk4v-8nlHJiYdBa6ZspgbVWw_oPLgP8cbuzJev86q55p1gAw","expires_in":43199,"scope":"openid","jti":"29272abb-4825-4f01-af9e-89da5d1500b7"}
從返回結果複製access_token,繼續:
[root@dev ~]# TOKEN=eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0NTk1NTUxNTYsInVzZXJfbmFtZSI6InVzZXIiLCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIiwiUk9MRV9VU0VSSI6IjI5MjcyYWJiLTQ4MjUtNGYwMS1hZjllLTg5ZGE1ZDE1MDBiNyIsImNsaWVudF9pZCI6ImFjbWUiLCJzY29wZSI6WyJvcGVuaWQiXX0.cQd88GYItHUDJuwkd_Rd0Yo8QM1R0dccuK0-xZ4OynC7EnqClLunaNOZ9jXwtilIFJNxbkbhQ8ymXdvlAF5Zjo8lpRGotdVo9rgQc39BDse7hGy1EfA9ZADQmJ-EuwkTNo0IBEXYC33XxQNK_3I_E92cnIPXq-FZHuZMRzpr-SlriwLa3aZVidmeyXK2U5dsjViWoHHKhcg-9c-VBPtyTJfPZOvj3s7DrbfCgOAGOhHkd_MBCdLDFb7QFhzIRsMfcD9rOAGTqk-hU2pHkkakKQ7_vL604UU7Qh3Zzkn6VbHPy0HAAiB9cnUhkQxK3Qb-wbHG-l3FC2pDlhtlhMHNfg
[root@dev ~]# curl -H “Authorization: Bearer $TOKEN” 192.168.1.115:9999/uaa/user
第二個命令返回結果類似如下:
{"details":{"remoteAddress":"192.168.1.194","sessionId":null,"tokenValue":"eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0NTk1NTUxNTYsInVzZXJfbmFtZSI6InVzZXIiLCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIiwiUk9MRV9VU0VSIl0sImp0aSI6IjI5MjcyYWJiLTQ4MjUtNGYwMS1hZjllLTg5ZGE1ZDE1MDBiNyIsImNsaWVudF9pZCI6ImFjbWUiLCJzY29wZSI6WyJvcGVuaWQiXX0.cQd88GYItHUDJuwkd_Rd0Yo8QM1R0dccuK0-xZ4OynC7EnqClLunaNOZ9jXwtilIFJNxbkbhQ8ymXdvlAF5Zjo8lpRGotdVo9rgQc39BDse7hGy1EfA9ZADQmJ-EuwkTNo0IBEXYC33XxQNK_3I_E92cnIPXq-FZHuZMRzpr-SlriwLa3aZVidmeyXK2U5dsjViWoHHKhcg-9c-VBPtyTJfPZOvj3s7DrbfCgOAGOhHkd_MBCdLDFb7QFhzIRsMfcD9rOAGTqk-hU2pHkkakKQ7_vL604UU7Qh3Zzkn6VbHPy0HAAiB9cnUhkQxK3Qb-wbHG-l3FC2pDlhtlhMHNfg","tokenType":"Bearer","decodedDetails":null},"authorities":[{"authority":"ROLE_ADMIN"},{"authority":"ROLE_USER"}],"authenticated":true,"userAuthentication":{"details":null,"authorities":[{"authority":"ROLE_ADMIN"},{"authority":"ROLE_USER"}],"authenticated":true,"principal":"user","credentials":"N/A","name":"user"},"credentials":"","principal":"user","oauth2Request":{"clientId":"acme","scope":["openid"],"requestParameters":{"client_id":"acme"},"resourceIds":[],"authorities":[],"approved":true,"refresh":false,"redirectUri":null,"responseTypes":[],"extensions":{},"grantType":null,"refreshTokenRequest":null},"clientOnly":false,"name":"user"}
從結果來看,使用access token訪問資源一切正常,說明授權服務器沒問題.

二.再看分離的資源服務器
spring-security-jwt依賴也要加入pom.xml;主類沒改動;application配置文件使用security.oauth2.resource.jwt.keyValue替換security.oauth2.resource.userInfoUri選項,使用這個公鑰來解密jwt.

最後運行主類的main方法測試(授權服務器前面啓動了,access_token也得到了),於是在使用curl命令:
[root@dev ~]# curl -H “Authorization: Bearer $TOKEN” 192.168.1.115:9000
返回結果類似如下:
{"id":"03af8be3-2fc3-4d75-acf7-c484d9cf32b1","content":"Hello World"}
跟蹤下獲取認證授權的信息過程:
當使用curl -H “Authorization: Bearer $TOKEN” 192.168.1.115:9000發出請求時,直到被OAuth2AuthenticationProcessingFilter攔截器處理,
org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter#doFilter{
Authentication authentication = tokenExtractor.extract(request);//抽取Token
Authentication authResult = authenticationManager.authenticate(authentication);//還原解碼認證授權信息
}
org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationManager#authenticate{
OAuth2Authentication auth = tokenServices.loadAuthentication(token);//這裏的tokenServices是DefaultTokenServices
}
org.springframework.security.oauth2.provider.token.DefaultTokenServices#loadAuthentication{
OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue);//tokenStore是JwtTokenStore
OAuth2Authentication result = tokenStore.readAuthentication(accessToken);
}
org.springframework.security.oauth2.provider.token.store.JwtTokenStore#readAccessToken{
OAuth2AccessToken accessToken = convertAccessToken(tokenValue);
}
org.springframework.security.oauth2.provider.token.store.JwtTokenStore#convertAccessToken{
return jwtTokenEnhancer.extractAccessToken(tokenValue, jwtTokenEnhancer.decode(tokenValue));
}
org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter#extractAccessToken
經過上面這個過程,用到jwt的公鑰對jwt進行解碼,,從中抽取OAuth2Authentication,這個Authentication本身就包含了用戶認證的信息.無需再向授權服務器發請求解碼

三.UI服務器作爲SSO的客戶端.
同樣UI服務器也要添加spring-security-jwt依賴到pom.xml;主類也基本不改動;和資源服務器一樣,使用security.oauth2.resource.jwt.keyValue替換security.oauth2.resource.userInfoUri選項.其它的分析與<使用OAuth2的SSO分析>類似.可以三臺服務器都啓動測試了.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章