Spring Security OAuth2.0系列文章:html
前面兩篇文章詳細講解了如何基於spring boot + oath2.0搭建認證中心和資源中心,本篇文章將會講解集成jwt以及將客戶端信息和受權碼信息保存到數據庫。java
JSON Web Token(JWT)是一個開放的行業標準(RFC 7519),它定義了一種簡介的、自包含的協議格式,用於在通訊雙方傳遞json對象,傳遞的信息通過數字簽名能夠被驗證和信任。JWT能夠使用HMAC算法或使用RSA的公鑰/私鑰對來簽名,防止被篡改。git
官網:https://jwt.io/web
標準: https://tools.ietf.org/html/rfc7519算法
JWT令牌的優勢:spring
1)jwt基於json,很是方便解析。sql
2)能夠在令牌中自定義豐富的內容,易擴展。數據庫
3)經過非對稱加密算法及數字簽名技術,JWT防止篡改,安全性高。json
4)資源服務使用JWT可不依賴認證服務便可完成受權。安全
缺點:
1)JWT令牌較長,佔存儲空間比較大,這意味着會耗費必定的帶寬資源
2)JWT簽名和驗籤都要耗費處理器資源
JWT令牌由三部分組成,每部分中間使用點(.)分隔,好比:xxxxx.yyyyy.zzzzz
2.1 Header
頭部包括令牌的類型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA)一個例子以下:
下邊是Header部分的內容
{ "alg": "HS256", "typ": "JWT" }
將上邊的內容使用Base64Url編碼,獲得一個字符串就是JWT令牌的第一部分。
2.2 Payload
第二部分是負載,內容也是一個json對象,它是存放有效信息的地方,它能夠存放jwt提供的現成字段,好比:iss(簽發者),exp(過時時間戳), sub(面向的用戶)等,也可自定義字段。此部分不建議存放敏感信息,由於此部分能夠解碼還原原始內容。最後將第二部分負載使用Base64Url編碼,獲得一個字符串就是JWT令牌的第二部分。
一個例子:
{ "sub": "1234567890", "name": "456", "admin": true }
2.3 Signature
第三部分是簽名,此部分用於防止jwt內容被篡改。
這個部分使用base64url將前兩部分進行編碼,編碼後使用點(.)鏈接組成字符串,最後使用header中聲明簽名算法進行簽名。
一個例子:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
TokenConfig類的修改
@Configuration public class TokenConfig { private static final String SIGNING_KEY = "auth123"; @Bean public TokenStore tokenStore() { return new JwtTokenStore(accessTokenConverter()); } @Bean public JwtAccessTokenConverter accessTokenConverter(){ JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter(); jwtAccessTokenConverter.setSigningKey(SIGNING_KEY);//對稱祕鑰,資源服務器使用該祕鑰來驗證 return jwtAccessTokenConverter; } }
AuthorizationServerTokenServices設置Token加強類
@Autowired private JwtAccessTokenConverter jwtAccessTokenConverter; @Bean public AuthorizationServerTokenServices tokenServices(){ DefaultTokenServices services = new DefaultTokenServices(); services.setClientDetailsService(clientDetailsService); services.setSupportRefreshToken(true); services.setTokenStore(tokenStore); services.setAccessTokenValiditySeconds(7200); services.setRefreshTokenValiditySeconds(259200); TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); tokenEnhancerChain.setTokenEnhancers(Collections.singletonList(jwtAccessTokenConverter)); services.setTokenEnhancer(tokenEnhancerChain); return services; }
而後就能夠測試了:
獲得響應結果:
{ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzMSJdLCJ1c2VyX25hbWUiOiJ6aGFuZ3NhbiIsInNjb3BlIjpbIlJPTEVfQURNSU4iLCJST0xFX1VTRVIiLCJST0xFX0FQSSJdLCJleHAiOjE2MTAzNzI5MzUsImF1dGhvcml0aWVzIjpbInAxIiwicDIiXSwianRpIjoiOWQzMzRmZGMtOTcwZC00YmJkLWI2MmMtZDU4MDZkNTgzM2YwIiwiY2xpZW50X2lkIjoiYzEifQ.gZraRNeX-o_jKiH7XQgg3TlUQBpxUcXa2-qR_Treu8U", "token_type": "bearer", "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzMSJdLCJ1c2VyX25hbWUiOiJ6aGFuZ3NhbiIsInNjb3BlIjpbIlJPTEVfQURNSU4iLCJST0xFX1VTRVIiLCJST0xFX0FQSSJdLCJhdGkiOiI5ZDMzNGZkYy05NzBkLTRiYmQtYjYyYy1kNTgwNmQ1ODMzZjAiLCJleHAiOjE2MTA2MjQ5MzUsImF1dGhvcml0aWVzIjpbInAxIiwicDIiXSwianRpIjoiN2U1NzE0NTgtNmU2Zi00YjlmLTkxODQtOWUzZmVmZmQ1YTNjIiwiY2xpZW50X2lkIjoiYzEifQ.wyiS-z-xhBPZSODXZHQVDJCQ6dcmeJjAwBPWe2GhT94", "expires_in": 7199, "scope": "ROLE_ADMIN ROLE_USER ROLE_API", "jti": "9d334fdc-970d-4bbd-b62c-d5806d5833f0" }
會發現accessToken長了不少,這是由於token是jwt字符串,分爲三部分,第二部分payload攜帶了不少信息,打開jwt.io網站,將上面的accessToken貼上去,能夠看到Base64解碼後的信息:
第一步,將認證服務中的TokenConfig直接拷貝到資源服務中
第二步,修改ResouceServerConfig 類
@Autowired private TokenStore tokenStore; @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources .resourceId(RESOURCE_ID) // .tokenServices(resourceServerTokenServices)//令牌服務 .tokenStore(tokenStore) .stateless(true); }
便可完成資源服務集成jwt的功能。
POST請求 http://127.0.0.1:30000/oauth/token?client_id=c1&client_secret=secret&grant_type=password&username=zhangsan&password=123 獲取令牌,獲取到accessToken以後攜帶token GET 請求 http://127.0.0.1:30001/r1 ,獲得響應結果:
訪問資源r1
便可證實成功。
認證服務客戶端信息仍是保存在內存中,如今將其改造放到數據庫中
DROP TABLE IF EXISTS `oauth_client_details`; CREATE TABLE `oauth_client_details` ( `client_id` varchar(255) NOT NULL COMMENT '客戶端標識', `resource_ids` varchar(255) DEFAULT NULL COMMENT '接入資源列表', `client_secret` varchar(255) DEFAULT NULL COMMENT '客戶端祕鑰', `scope` varchar(255) DEFAULT NULL, `authorized_grant_types` varchar(255) DEFAULT NULL, `web_server_redirect_uri` varchar(255) DEFAULT NULL, `authorities` varchar(255) DEFAULT NULL, `access_token_validity` int(11) DEFAULT NULL, `refresh_token_validity` int(11) DEFAULT NULL, `additional_information` longtext, `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `archived` tinyint(4) DEFAULT NULL, `trusted` tinyint(4) DEFAULT NULL, `autoapprove` varchar(255) DEFAULT NULL, PRIMARY KEY (`client_id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='接入客戶端信息'; /*Data for the table `oauth_client_details` */ insert into `oauth_client_details`(`client_id`,`resource_ids`,`client_secret`,`scope`,`authorized_grant_types`,`web_server_redirect_uri`,`authorities`,`access_token_validity`,`refresh_token_validity`,`additional_information`,`create_time`,`archived`,`trusted`,`autoapprove`) values ('c1','res1','$2a$10$X2xVwW.7cOEh2niPqHYAne9EnjRJFj7QI4TqfmnDou9fT/45sCFEm','ROLE_ADMIN,ROLE_USER,ROLE_API','client_credentials,password,authorization_code,implicit,refresh_token','https://www.baidu.com',NULL,7200,259200,NULL,'2021-01-11 09:09:53',0,0,'false'), ('c2','res2','$2a$10$X2xVwW.7cOEh2niPqHYAne9EnjRJFj7QI4TqfmnDou9fT/45sCFEm','ROLE_API','client_credentials,password,authorization_code,implicit,refresh_token','https://www.baidu.com',NULL,31536000,2592000,NULL,'2021-01-11 09:09:56',0,0,'false'); /*Table structure for table `oauth_code` */ DROP TABLE IF EXISTS `oauth_code`; CREATE TABLE `oauth_code` ( `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `code` varchar(255) DEFAULT NULL, `authentication` blob, KEY `code_index` (`code`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
上述SQL新建了兩張表oauth_client_details
以及oauth_code
分別用於存儲客戶端信息以及受權碼信息,因爲使用了jwt token(jwt token自己就存儲了數據),因此再也不保存數據庫,兩張表均爲spring oauth2.0內置表,不須要寫SQL,內置框架自動識別表。
對應上述兩張表,分別修改Bean對象的建立爲jdbc類型的:
@Bean public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource){ return new JdbcAuthorizationCodeServices(dataSource); } @Bean public ClientDetailsService clientDetailsService(DataSource dataSource) { JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource); clientDetailsService.setPasswordEncoder(passwordEncoder); return clientDetailsService; }
以後修改客戶端配置對象:
@Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.withClientDetails(clientDetailsService); // clients.inMemory() // .withClient("c1") // .secret(new BCryptPasswordEncoder().encode("secret"))//$2a$10$0uhIO.ADUFv7OQ/kuwsC1.o3JYvnevt5y3qX/ji0AUXs4KYGio3q6 // .resourceIds("r1") // .authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token") // .scopes("all") // .autoApprove(false) // .redirectUris("https://www.baidu.com"); }
完成修改。
GET請求:http://127.0.0.1:30000/oauth/authorize?client_id=c1&response_type=code&scope=ROLE_API&redirect_uri=https://www.baidu.com 獲取受權碼後,觀察表oauth_code,裏面應當已經有了受權碼數據。
源碼地址:https://gitee.com/kdyzm/spring-security-oauth-study/tree/v4.0.0
個人博客地址:https://blog.kdyzm.cn/