1. 添加依賴
受權服務是基於Spring Security的,所以須要在項目中引入兩個依賴:mysql
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency>
前者爲 Security,後者爲Security的OAuth2擴展。git
2. 添加註解和配置github
在啓動類中添加@EnableAuthorizationServer註解:web
@SpringBootApplication @EnableAuthorizationServer public class AlanOAuthApplication { public static void main(String[] args) { SpringApplication.run(AlanOAuthApplication.class, args); } }
完成這些咱們的受權服務最基本的骨架就已經搭建完成了。
可是要想跑通整個流程,咱們必須分配 client_id, client_secret才行。
Spring Security OAuth2的配置方法是編寫@Configuration類繼承AuthorizationServerConfigurerAdapter,
而後重寫void configure(ClientDetailsServiceConfigurer clients)方法,如:spring
@Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() // 使用in-memory存儲 .withClient("client") // client_id .secret("secret") // client_secret .authorizedGrantTypes("authorization_code") // 該client容許的受權類型 .scopes("app"); // 容許的受權範圍 }
3. 受權流程
訪問受權頁面:sql
localhost:8080/oauth/authorize?client_id=client&response_type=code&redirect_uri=http://www.baidu.com數據庫
此時瀏覽器會讓你輸入用戶名密碼,這是由於 Spring Security 在默認狀況下會對全部URL添加Basic Auth認證。
默認的用戶名爲user, 密碼是隨機生成的,在控制檯日誌中能夠看到。瀏覽器
畫風雖然很簡陋,可是基本功能都具有了。點擊Authorize後,瀏覽器就會重定向到百度,並帶上code參數:服務器
拿到code之後,就能夠調用app
POST/GET http://client:secret@localhost:8080/oauth/token
來換取access_token了:
curl -X POST -H "Content-Type: application/x-www-form-urlencoded"
-d 'grant_type=authorization_code&code=Li4NZo&redirect_uri=http://www.baidu.com'
"http://client:secret@localhost:8080/oauth/token"
注意,URL中的client爲上文中經過ClientDetailsServiceConfigurer類指定的clientId。
因爲authorization_code的受權方式不須要 client_secret, 所以secret能夠填寫任意值
返回以下:
{
"access_token": "32a1ca28-bc7a-4147-88a1-c95abcc30556", // 令牌
"token_type": "bearer",
"expires_in": 2591999,
"scope": "app"
}
到此咱們最最基本的受權服務就搭建完成了。然而,這僅僅是個demo,若是要在生產環境中使用,還須要作更多的工做。
4. 使用MySQL存儲access_token和client信息
在上面的例子中,全部的token信息都是保存在內存中的,這顯然沒法在生產環境中使用(進程結束後全部token丟失, 用戶須要從新受權),所以咱們須要將這些信息進行持久化操做。 把受權服務器中的數據存儲到數據庫中並不難,由於 Spring Cloud Security OAuth 已經爲咱們設計好了一套Schema和對應的DAO對象。但在使用以前,咱們須要先對相關的類有必定的瞭解。
4.1 相關接口
Spring Cloud Security OAuth2經過DefaultTokenServices類來完成token生成、過時等 OAuth2 標準規定的業務邏輯,而DefaultTokenServices又是經過TokenStore接口完成對生成數據的持久化。在上面的demo中,TokenStore的默認實現爲InMemoryTokenStore,即內存存儲。 對於Client信息,ClientDetailsService接口負責從存儲倉庫中讀取數據,在上面的demo中默認使用的也是InMemoryClientDetialsService實現類。說到這裏就能看出,要想使用數據庫存儲,只須要提供這些接口的實現類便可。慶幸的是,框架已經爲咱們寫好JDBC實現了,即JdbcTokenStore和JdbcClientDetailsService。
4.2 建表
要想使用這些JDBC實現,首先要建表。框架爲咱們提早設計好了schema, 在github上:https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql
在使用這套表結構以前要注意的是,對於MySQL來講,默認建表語句中主鍵是varchar(255)類型,在mysql中執行會報錯,緣由是mysql對varchar主鍵長度有限制。因此這裏改爲128便可。其次,語句中會有某些字段爲LONGVARBINARY類型,它對應mysql的blob類型,也須要修改一下。
4.3 配置
數據庫建好後,下一步就是配置框架使用JDBC實現。方法仍是編寫@Configuration類繼承AuthorizationServerConfigurerAdapter:
@Autowired private AuthenticationManager authenticationManager; @Autowired private DataSource dataSource; @Bean // 聲明TokenStore實現 public TokenStore tokenStore() { return new JdbcTokenStore(dataSource); } @Bean // 聲明 ClientDetails實現 public ClientDetailsService clientDetails() { return new JdbcClientDetailsService(dataSource); } @Override // 配置框架應用上述實現 public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager); endpoints.tokenStore(tokenStore()); // 配置TokenServices參數 DefaultTokenServices tokenServices = new DefaultTokenServices(); tokenServices.setTokenStore(endpoints.getTokenStore()); tokenServices.setSupportRefreshToken(false); tokenServices.setClientDetailsService(endpoints.getClientDetailsService()); tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer()); tokenServices.setAccessTokenValiditySeconds( (int) TimeUnit.DAYS.toSeconds(30)); // 30天 endpoints.tokenServices(tokenServices); }
完成這些後,框架就會將中間產生的數據寫到mysql中了。oauth_client_details是client表,能夠直接在該表中添加記錄來添加client:
4.4 須要注意的地方
這裏不得不說 Spring 設計有一個奇葩地的方。注意看oauth_access_token表是存放訪問令牌的,可是並無直接在字段中存放token。Spring 使用OAuth2AccessToken來抽象與令牌有關的全部屬性,在寫入到數據庫時,Spring將該對象經過JDK自帶的序列化機制序列成字節 直接保存到了該表的token字段中。也就是說,若是隻看數據表你是看不出access_token的值是多少,過時時間等信息的。這就給資源服務器的實現帶來了麻煩。咱們的資源提供方並無使用Spring Security,也不想引入 Spring Security 的任何依賴,這時候就只能將 DefaultOAuth2AccessToken的源碼copy到資源提供方的項目中,而後讀取token字段並反序列化還原對象來獲取token信息。可是若是這樣作還會遇到反序列化兼容性的問題,具體解決方法參考我另外一篇博文: http://blog.csdn.net/neosmith/article/details/52539614
5. 總結
至此一個能在生產環境下使用的受權服務就搭建好了。其實咱們在實際使用時應該適當定製JdbcTokenStore或ClientDetailsService來實適應業務須要,甚至能夠直接從0開始實現接口,徹底不用框架提供的實現。另外,Spring 直接將DefaultOAuth2AccessToken序列化成字節保存到數據庫中的設計,我認爲是很是不合理的。或許設計者的初衷是保密access_token,可是經過加密的方法也能夠實現,徹底不該該直接扔字節。不過經過定製TokenStore接口,咱們可使用本身的表結構而不拘泥於默認實現。
6. 我的見解
Spring的OAuth2實現有些過於複雜了,oauth2自己只是個很是簡單的協議,徹底能夠本身在SpringMVC的基礎上自由實現,沒有難度,也不復雜。我想不少人去用框架應該是擔憂oauth2協議複雜實現起來健壯性不足,實際上是多慮了。若是是開發我我的的項目,我確定會不使用任何框架。
github地址: https://github.com/wanghongfei/spring-security-oauth2-example
項目使用的是MySql存儲, 須要先建立如下表結構:
CREATE SCHEMA IF NOT EXISTS `alan-oauth` DEFAULT CHARACTER SET utf8 ; USE `alan-oauth` ; -- ----------------------------------------------------- -- Table `alan-oauth`.`clientdetails` -- ----------------------------------------------------- CREATE TABLE IF NOT EXISTS `alan-oauth`.`clientdetails` ( `appId` VARCHAR(128) NOT NULL, `resourceIds` VARCHAR(256) NULL DEFAULT NULL, `appSecret` VARCHAR(256) NULL DEFAULT NULL, `scope` VARCHAR(256) NULL DEFAULT NULL, `grantTypes` VARCHAR(256) NULL DEFAULT NULL, `redirectUrl` VARCHAR(256) NULL DEFAULT NULL, `authorities` VARCHAR(256) NULL DEFAULT NULL, `access_token_validity` INT(11) NULL DEFAULT NULL, `refresh_token_validity` INT(11) NULL DEFAULT NULL, `additionalInformation` VARCHAR(4096) NULL DEFAULT NULL, `autoApproveScopes` VARCHAR(256) NULL DEFAULT NULL, PRIMARY KEY (`appId`)) ENGINE = InnoDB DEFAULT CHARACTER SET = utf8; -- ----------------------------------------------------- -- Table `alan-oauth`.`oauth_access_token` -- ----------------------------------------------------- CREATE TABLE IF NOT EXISTS `alan-oauth`.`oauth_access_token` ( `token_id` VARCHAR(256) NULL DEFAULT NULL, `token` BLOB NULL DEFAULT NULL, `authentication_id` VARCHAR(128) NOT NULL, `user_name` VARCHAR(256) NULL DEFAULT NULL, `client_id` VARCHAR(256) NULL DEFAULT NULL, `authentication` BLOB NULL DEFAULT NULL, `refresh_token` VARCHAR(256) NULL DEFAULT NULL, PRIMARY KEY (`authentication_id`)) ENGINE = InnoDB DEFAULT CHARACTER SET = utf8; -- ----------------------------------------------------- -- Table `alan-oauth`.`oauth_approvals` -- ----------------------------------------------------- CREATE TABLE IF NOT EXISTS `alan-oauth`.`oauth_approvals` ( `userId` VARCHAR(256) NULL DEFAULT NULL, `clientId` VARCHAR(256) NULL DEFAULT NULL, `scope` VARCHAR(256) NULL DEFAULT NULL, `status` VARCHAR(10) NULL DEFAULT NULL, `expiresAt` DATETIME NULL DEFAULT NULL, `lastModifiedAt` DATETIME NULL DEFAULT NULL) ENGINE = InnoDB DEFAULT CHARACTER SET = utf8; -- ----------------------------------------------------- -- Table `alan-oauth`.`oauth_client_details` -- ----------------------------------------------------- CREATE TABLE IF NOT EXISTS `alan-oauth`.`oauth_client_details` ( `client_id` VARCHAR(128) NOT NULL, `resource_ids` VARCHAR(256) NULL DEFAULT NULL, `client_secret` VARCHAR(256) NULL DEFAULT NULL, `scope` VARCHAR(256) NULL DEFAULT NULL, `authorized_grant_types` VARCHAR(256) NULL DEFAULT NULL, `web_server_redirect_uri` VARCHAR(256) NULL DEFAULT NULL, `authorities` VARCHAR(256) NULL DEFAULT NULL, `access_token_validity` INT(11) NULL DEFAULT NULL, `refresh_token_validity` INT(11) NULL DEFAULT NULL, `additional_information` VARCHAR(4096) NULL DEFAULT NULL, `autoapprove` VARCHAR(256) NULL DEFAULT NULL, PRIMARY KEY (`client_id`)) ENGINE = InnoDB DEFAULT CHARACTER SET = utf8; -- ----------------------------------------------------- -- Table `alan-oauth`.`oauth_client_token` -- ----------------------------------------------------- CREATE TABLE IF NOT EXISTS `alan-oauth`.`oauth_client_token` ( `token_id` VARCHAR(256) NULL DEFAULT NULL, `token` BLOB NULL DEFAULT NULL, `authentication_id` VARCHAR(128) NOT NULL, `user_name` VARCHAR(256) NULL DEFAULT NULL, `client_id` VARCHAR(256) NULL DEFAULT NULL, PRIMARY KEY (`authentication_id`)) ENGINE = InnoDB DEFAULT CHARACTER SET = utf8; -- ----------------------------------------------------- -- Table `alan-oauth`.`oauth_code` -- ----------------------------------------------------- CREATE TABLE IF NOT EXISTS `alan-oauth`.`oauth_code` ( `code` VARCHAR(256) NULL DEFAULT NULL, `authentication` BLOB NULL DEFAULT NULL) ENGINE = InnoDB DEFAULT CHARACTER SET = utf8; -- ----------------------------------------------------- -- Table `alan-oauth`.`oauth_refresh_token` -- ----------------------------------------------------- CREATE TABLE IF NOT EXISTS `alan-oauth`.`oauth_refresh_token` ( `token_id` VARCHAR(256) NULL DEFAULT NULL, `token` BLOB NULL DEFAULT NULL, `authentication` BLOB NULL DEFAULT NULL) ENGINE = InnoDB DEFAULT CHARACTER SET = utf8;
而後在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, autoapprove 'client', NULL, 'secret', 'app', 'authorization_code', 'http://www.baidu.com', NULL, NULL, NULL, NULL, NULL
這時就能夠訪問受權頁面了:
localhost:8080/oauth/authorize?client_id=client&response_type=code&redirect_uri=http://www.baidu.com
訪問時Spring讓你登錄,隨便輸入一個用戶名密碼便可。 注意, 若是每次登錄時輸入的用戶名不同,那麼Spring Security會認爲是不一樣的用戶,所以訪問/token/authorize會再次顯示受權頁面。若是用戶名一致, 則只須要受權一次
數據庫鏈接信息在application.properties
中配置。
Spring Cloud Security OAuth2 是 Spring 對 OAuth2 的開源實現,優勢是能與Spring Cloud技術棧無縫集成,若是所有使用默認配置,開發者只須要添加註解就能完成 OAuth2 受權服務的搭建。
https://github.com/wanghongfei/spring-security-oauth2-example