在上一節咱們講述的配置是把受權碼存儲在redis中,把相應的請求的路徑用使用in-memory存儲 ,這個是放在了內存中,可是實際開發咱們的數據但願是從數據表中查詢的,那應該怎麼作呢?java
/** * inMemory是存儲到內存中 並未到數據庫 */ clients.inMemory() //client Id .withClient("normal-app") .authorizedGrantTypes("authorization_code", "implicit") .authorities("ROLE_CLIENT") .scopes("read","write") .resourceIds(resourceId) .accessTokenValiditySeconds(accessTokenValiditySeconds)//受權碼存活時間 .and() .withClient("trusted-app") .authorizedGrantTypes("client_credentials", "password") .authorities("ROLE_TRUSTED_CLIENT") .scopes("read", "write") .resourceIds(resourceId) .accessTokenValiditySeconds(accessTokenValiditySeconds) .secret("secret");
若是使用的是這種方式,咱們對應的受權碼的請求路徑以下:web
http://localhost:8787/oauth/authorize?client_id=normal-app&response_type=code&scope=read&redirect_uri=/resources/user
相應的參數請對照上redis
而後咱們使用的是jwt的令牌方式,相應的請求路徑以下:算法
http://localhost:8787/oauth/token?code=r8YBUL&grant_type=authorization_code&client_id=normal-app&redirect_uri=/resources/user
這個是放在內存中的存儲方式spring
@Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { //默認值InMemoryTokenStore對於單個服務器是徹底正常的(即,在發生故障的狀況下,低流量和熱備份備份服務器)。大多數項目能夠從這裏開始,也能夠在開發模式下運行,以便輕鬆啓動沒有依賴關係的服務器。 //這JdbcTokenStore是同一件事的JDBC版本,它將令牌數據存儲在關係數據庫中。若是您能夠在服務器之間共享數據庫,則可使用JDBC版本,若是隻有一個,則擴展同一服務器的實例,或者若是有多個組件,則受權和資源服務器。要使用JdbcTokenStore你須要「spring-jdbc」的類路徑。 //這個地方指的是從jdbc查出數據來存儲 clients.withClientDetails(clientDetails()); }
這裏能夠看到咱們是把以前的從內存讀取的方式給去掉了,取而代之的是clientDetails()這個方法,而後咱們看下這個方法:sql
@Bean public ClientDetailsService clientDetails() { return new JdbcClientDetailsService(dataSource); }
只需配置這個bean便可 可是咱們的datasource是在yml配置文件中配置好了的,只須要注入:數據庫
import javax.sql.DataSource; @Resource private DataSource dataSource;
可是這裏還沒完,咱們首先要講下JdbcClientDetailsService是如何從數據庫讀取的,咱們能夠點擊進入查看相應的源碼,以下所示:json
public JdbcClientDetailsService(DataSource dataSource) { this.updateClientDetailsSql = DEFAULT_UPDATE_STATEMENT; this.updateClientSecretSql = "update oauth_client_details set client_secret = ? where client_id = ?"; this.insertClientDetailsSql = "insert into oauth_client_details (client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, additional_information, autoapprove, client_id) values (?,?,?,?,?,?,?,?,?,?,?)"; this.selectClientDetailsSql = "select client_id, client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, additional_information, autoapprove from oauth_client_details where client_id = ?"; this.passwordEncoder = NoOpPasswordEncoder.getInstance(); Assert.notNull(dataSource, "DataSource required"); this.jdbcTemplate = new JdbcTemplate(dataSource); this.listFactory = new DefaultJdbcListFactory(new NamedParameterJdbcTemplate(this.jdbcTemplate)); }
咱們能夠看到,他本身是有一個默認的字段的表的,裏面有相應的查詢的方法,因此咱們須要創建一個這樣的表,sql以下:安全
-- ---------------------------- -- Table structure for oauth_client_details 將請求的路徑存在數據表 -- ---------------------------- DROP TABLE IF EXISTS `oauth_client_details`; CREATE TABLE `oauth_client_details` ( `client_id` varchar(48) NOT NULL, `resource_ids` varchar(256) DEFAULT NULL, `client_secret` varchar(256) DEFAULT NULL, `scope` varchar(256) DEFAULT NULL, `authorized_grant_types` varchar(256) DEFAULT NULL, `web_server_redirect_uri` varchar(256) DEFAULT NULL, `authorities` varchar(256) DEFAULT NULL, `access_token_validity` int(11) DEFAULT NULL, `refresh_token_validity` int(11) DEFAULT NULL, `additional_information` varchar(4096) DEFAULT NULL, `autoapprove` varchar(256) DEFAULT NULL, PRIMARY KEY (`client_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
這個是默認的類的表,通常用它默認的便可,咱們這邊就須要根據以上的字段配置相關的內容,以下:服務器
這裏配置好了以後咱們的訪問路徑爲:
//步驟:客戶端向認證服務器申請令牌 http://localhost:8787/oauth/token?client_id=normal-app&grant_type=authorization_code&code=1oCj8e&redirect_uri=http://localhost:8787/resources/user
而後令牌的訪問路徑爲:
//拿到令牌後訪問資源: http://localhost:8787/resources/user?access_token=9d62c7b0-780e-4c6a-ad5a-56d79a089342
記得code要換成上一步生成的code
以前咱們用的jwt來存儲令牌token,後來我發現怎麼也不出現jwttoken,通過屢次檢查發現了錯誤,代碼以下:
package urity.demo.oauth2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService; import org.springframework.security.oauth2.provider.token.AccessTokenConverter; import org.springframework.security.oauth2.provider.token.DefaultTokenServices; import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.core.userdetails.User; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; import urity.demo.service.RedisAuthenticationCodeServices; import urity.demo.support.MyUserDetailService; import javax.annotation.Resource; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; @Configuration @EnableAuthorizationServer public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { @Resource private DataSource dataSource; @Value("${resource.id:spring-boot-application}")//默認是spring-boot-application private String resourceId; @Value("${access_token.validity_period:36000}") private int accessTokenValiditySeconds = 36000; //認證管理 很重要 若是security版本高可能會出坑哦 @Resource private AuthenticationManager authenticationManager; @Resource private RedisAuthenticationCodeServices redisAuthenticationCodeServices; @Resource private MyUserDetailService myUserDetailService; //security //定義令牌端點上的安全約束。 @Override public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')"); oauthServer.checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')"); } //這個是定義受權的請求的路徑的Bean @Bean public ClientDetailsService clientDetails() { return new JdbcClientDetailsService(dataSource); } // @Bean // 聲明TokenStore實現 // public JdbcTokenStore jdbcTokenStore() { // return new JdbcTokenStore(dataSource); // } //將ClientDetailsServiceConfigurer(從您的回調AuthorizationServerConfigurer)能夠用來在內存或JDBC實現客戶的細節服務來定義的。客戶端的重要屬性是 //clientId:(必填)客戶端ID。 //secret:(可信客戶端須要)客戶機密碼(若是有)。沒有可不填 //scope:客戶受限的範圍。若是範圍未定義或爲空(默認值),客戶端不受範圍限制。read write all //authorizedGrantTypes:授予客戶端使用受權的類型。默認值爲空。 //authorities授予客戶的受權機構(普通的Spring Security權威機構)。 //客戶端的詳細信息能夠經過直接訪問底層商店(例如,在數據庫表中JdbcClientDetailsService)或經過ClientDetailsManager接口(這兩種實現ClientDetailsService也實現)來更新運行的應用程序。 //注意:JDBC服務的架構未與庫一塊兒打包(由於在實踐中可能須要使用太多變體) @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { //默認值InMemoryTokenStore對於單個服務器是徹底正常的(即,在發生故障的狀況下,低流量和熱備份備份服務器)。大多數項目能夠從這裏開始,也能夠在開發模式下運行,以便輕鬆啓動沒有依賴關係的服務器。 //這JdbcTokenStore是同一件事的JDBC版本,它將令牌數據存儲在關係數據庫中。若是您能夠在服務器之間共享數據庫,則可使用JDBC版本,若是隻有一個,則擴展同一服務器的實例,或者若是有多個組件,則受權和資源服務器。要使用JdbcTokenStore你須要「spring-jdbc」的類路徑。 // /** // * inMemory是存儲到內存中 並未到數據庫 // */ // clients.inMemory() // //client Id // .withClient("normal-app") // .authorizedGrantTypes("authorization_code", "implicit") // .authorities("ROLE_CLIENT") // .scopes("read","write") // .resourceIds(resourceId) // .accessTokenValiditySeconds(accessTokenValiditySeconds)//受權碼存活時間 // .and() // .withClient("trusted-app") // .authorizedGrantTypes("client_credentials", "password") // .authorities("ROLE_TRUSTED_CLIENT") // .scopes("read", "write") // .resourceIds(resourceId) // .accessTokenValiditySeconds(accessTokenValiditySeconds) // .secret("secret"); //這個地方指的是從jdbc查出數據來存儲 clients.withClientDetails(clientDetails()); } //AuthorizationEndpoint能夠經過如下方式配置支持的受權類型AuthorizationServerEndpointsConfigurer。默認狀況下,全部受權類型均受支持,除了密碼(有關如何切換它的詳細信息,請參見下文)。如下屬性會影響受權類型: //authenticationManager:經過注入密碼受權被打開AuthenticationManager。 //userDetailsService:若是您注入UserDetailsService或者全局配置(例如a GlobalAuthenticationManagerConfigurer),則刷新令牌受權將包含對用戶詳細信息的檢查,以確保該賬戶仍然活動 //authorizationCodeServices:定義AuthorizationCodeServices受權代碼受權的受權代碼服務(實例)。 //implicitGrantService:在批准期間管理狀態。 //tokenGranter:(TokenGranter徹底控制授予和忽略上述其餘屬性) //在XML授予類型中包含做爲子元素authorization-server。 /** * /oauth/authorize您能夠從該請求中獲取全部數據, * 而後根據須要進行渲染, * 而後全部用戶須要執行的操做都是回覆有關批准或拒絕受權的信息。 * 請求參數直接傳遞給您UserApprovalHandler, * AuthorizationEndpoint因此您能夠隨便解釋數據 * * @param endpoints * @throws Exception */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(this.authenticationManager); endpoints.accessTokenConverter(accessTokenConverter());//jwt //從數據庫查請求的路徑 // endpoints.tokenStore(jdbcTokenStore()); //從jwt來數據 endpoints.tokenStore(jwtStore()); //受權碼存儲 endpoints.authorizationCodeServices(redisAuthenticationCodeServices); endpoints.userDetailsService(myUserDetailService); // 配置TokenServices參數 注意這個是默認的uuid的存儲設置 與jwt無關 若是要用jwt請註釋掉 // DefaultTokenServices tokenServices = new DefaultTokenServices(); //獲取令牌的是否從jdbc查 顯然 這裏是的 // tokenServices.setTokenStore(endpoints.getTokenStore()); //咱們能夠用jwt來存放token // tokenServices.setTokenStore(jwtStore()); // tokenServices.setSupportRefreshToken(false); // tokenServices.setClientDetailsService(endpoints.getClientDetailsService()); // tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer()); // tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30)); // 30天 // endpoints.tokenServices(tokenServices); // @Bean // RedisTokenStore redisTokenStore(){ // return new RedisTokenStore(redisConnectionFactory); // } // endpoints.tokenStore(redisTokenStore()); } //定義jwttoken的某些屬性 @Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter() { /** * 重寫加強token的方法 * 自定義返回相應的信息 * */ @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { String userName = authentication.getUserAuthentication().getName(); // 與登陸時候放進去的UserDetail實現類一直查看link{SecurityConfiguration} User user = (User) authentication.getUserAuthentication().getPrincipal(); /** 自定義一些token屬性 ***/ final Map<String, Object> additionalInformation = new HashMap<>(); additionalInformation.put("userName", userName); additionalInformation.put("roles", user.getAuthorities()); ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation); OAuth2AccessToken enhancedToken = super.enhance(accessToken, authentication); return enhancedToken; } }; // 測試用,資源服務使用相同的字符達到一個對稱加密的效果,生產時候使用RSA非對稱加密方式 accessTokenConverter.setSigningKey("123"); return accessTokenConverter; } @Bean public TokenStore jwtStore() { TokenStore tokenStore = new JwtTokenStore(accessTokenConverter()); return tokenStore; } /** * 建立一個默認的資源服務token * * @return */ @Bean public ResourceServerTokenServices defaultTokenServices() { final DefaultTokenServices defaultTokenServices = new DefaultTokenServices(); defaultTokenServices.setTokenEnhancer(accessTokenConverter()); defaultTokenServices.setTokenStore(jwtStore()); return defaultTokenServices; } }
請注意:DefaultTokenServices與jwttoken的配置不能都存在,不然系統只找DefaultTokenServices的配置, 也就是生成的token會一直是默認的UUID,這裏咱們只能二者選其一配置在代碼中
/ 配置TokenServices參數 注意這個是默認的uuid的存儲設置 與jwt無關 若是要用jwt請註釋掉 // DefaultTokenServices tokenServices = new DefaultTokenServices(); //獲取令牌的是否從jdbc查 顯然 這裏是的 // tokenServices.setTokenStore(endpoints.getTokenStore()); //咱們能夠用jwt來存放token // tokenServices.setTokenStore(jwtStore()); // tokenServices.setSupportRefreshToken(false); // tokenServices.setClientDetailsService(endpoints.getClientDetailsService()); // tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer()); // tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30)); // 30天 // endpoints.tokenServices(tokenServices);
咱們從路徑受權後得到了code,就能夠用code請求相應對的路徑換取jwttoken,咱們用postman來進行測試:
這裏咱們用basic Auth的方式 只須要填寫name:normal-app便可,密碼能夠不填
http://localhost:8787/oauth/token?client_id=normal-app&grant_type=authorization_code&code=csTjhK&redirect_uri=http://localhost:8787/resources/user
咱們能夠看下得到的json:
這裏生成的jwttoken中攜帶了相應這個是jwt的信息,這個一段字符串其實是Header和Payload加密後拼接而成的,相應的能夠查看下一篇jwt的相關解析.
咱們能夠訪問:https://www.jsonwebtoken.io/ 來解析下這個token裏面的信息:
這裏能夠看到Header和Payload的信息,Header主要存儲的是type和加密算法,這裏是HS256,咱們主要看Payload的信息:
{ "aud": [ "resourceId" ], "user_name": "test", "scope": [ "read" ], "roles": [ { "authority": "ROLE_USER" }, { "authority": "admin" } ], "exp": 1532662701, "userName": "test", "authorities": [ "admin", "ROLE_USER" ], "jti": "066cefa0-0a7a-40da-87a0-133c5a9c64d3", "client_id": "normal-app", "iat": 1532659101 }
這裏能夠看到登陸的用戶名,token的生命週期等.咱們就能夠更清晰瞭解生成的jwttoken攜帶的信息有哪些了.