1.用戶認證
1.1 : 用戶認證與授權
用戶認證
-
當用戶去訪問我們的系統資源的時候,我們的系統需要驗證用戶的身份(比如賬號和密碼認證這是一種方式),如果身份合法則認證通過,頒發相應的免死金牌,如果驗證沒通過,則提示用戶請三思而后行,這就是用戶認證
用戶授權
-
用戶授權一般是與用戶認證相輔相成的,在認證的時候,如果認證通過,我們還會將該用戶的權限信息給收集起來,並將相應信息作為依據,封裝在認證的響應體中(JWT),當用戶認證成功后,訪問我們系統的某一個模塊的時候,該模塊是需要判斷該用戶是否有權訪問,如果沒有訪問該資源的訪問權限,用戶也只有被拒絕訪問,這就是用戶授權
1.2 : 單點登陸問題 SSO
單點登陸
-
單點登陸一般常見於分布式項目中,用戶只需要登陸一次,即認證一次,即可訪問分布式項目中的所有模塊,而不需要每訪問一個模塊就得去登陸認證一次,這樣用戶嫌麻煩,后端認證邏輯也冗余
1.3 : 第三方登陸
比如目前互聯網運用中的微信登陸、微博登陸、支付寶登陸等
-
用戶通過授權,第三方應用給予我們系統訪問他微信相關信息的權限,我們獲取后進行注冊,使其稱為我們系統的注冊人員,實現第三方登陸
2.關於Oauth2認證
2.1 : 認識Oauth2
Oauth2我認為是一種認證與授權的思想,我們可以將其運用在項目中,成為我們項目認證和授權的解決方案
首先Oauth2中有以下幾個角色: <我們就以微信登陸為列>
客戶端
-
就假如是我們自己的系統吧,這個客戶端和Oauth2不沾親帶故,可以是任何獨立的系統,比如我們的某個系統,或者某個app客戶端又或者是我們的系統web客戶端
資源擁有者
-
就是指我們的用戶,比如微信登錄中,在面對微信的數據庫時,我們的系統就是無關人員,而登陸的用戶,在微信的系統中就是該用戶信息的資源擁有者,他掌握着是否將他的微信個人信息暴露給我們的系統
授權服務器
-
在微信登陸中,就是用來辨別用戶的認證是否正確,是否可以成為該微信用戶信息的資源擁有者,在微信登陸中,該系統由微信系統提供,用於鑒別資源擁有者的身份合法性
資源服務器
-
這個就很簡單了,守護該微信用戶信息的服務器,他掌握着微信的用戶信息,我們的客戶端最后就是向他發起請求,想面對甲方一樣,求着他給我們響應該用戶的微信個人信息。
2.2 : Oauth2實現之三方登陸流程
接着上面的我們繼續加固我們對Oauth2的認識,下面我們就以微信登陸為列子,來說說Oauth2的運用實現,已經一般在項目中,Oauth2這種思想可以幫我們解決哪些問題
-
首先用戶,登陸我們的客戶端,點擊微信登陸
-
系統請求微信的授權系統,響應一個二維碼給用戶,用戶掃碼后點擊同意
-
微信的授權服務器會對該微信用戶進行驗證
-
驗證通過后,返回一個詢問頁面,是否授權給某某系統
-
用戶點擊確認,認證服務就會頒發一個授權碼給客戶端,並重定向我們的系統
-
-
此時我們的客戶端獲得授權碼,根據授權碼去微信的認證服務申請令牌
-
微信的認證服務器認證通過后,會頒發一個令牌給我們的系統
-
當系統拿到令牌時,也就是微信登陸成功之時
-
該令牌代表着我們的系統,有權訪問該微信用戶在微信中的個人信息數據
-
-
我們的客戶端攜帶令牌去微信的資源服務器獲取該微信用戶的個人信息
-
微信資源服務器校驗該令牌的合法性,通過后響應該用戶的微信個人信息數據給我們的客戶端
整體流程如下所示
2.3 : Oauth2的運用
思想在上面已經說明的還算通透了,總結以下,受保護的資源需要某種標識,這個標識可以用戶自己攜帶而來的(自己系統),也可以是外來系統在用戶授權后帶來的(外來系統),只要該標識驗證通過,則資源訪問放行,對外暴露請求接口,所以Oauth2的主要運用就是
-
保護某個資源只有在通過授權的情況下,才運行被請求
-
第三方登陸流程是一個列子
-
我們假如做大做強,我們的系統服務也可以通過上面的方式暴露給外來系統某些受保護的數據
-
本系統前端訪問后端資源,也可以解決一個授權問題
-
再其次就是我們系統服務之間的相互調用,當然這個做的就有點嚴謹了,但是思想是這個思想
-
Spring Security + Oauth2:認證授權方案
-
用戶攜帶賬號密碼 (或者三方登陸 )請求認證服務等,請求用戶認證
-
認證通過后,頒發身份令牌可客戶端,並將身份令牌儲存在Redis中
-
用戶攜帶身份令牌請求資源服務,必經網關
-
網關獲取客戶端帶來的令牌和Redis中的令牌進行比對校驗
-
校驗通過,服務轉發,資源服務器獲取到令牌,根據令牌完成授權
-
資源服務通過授權后響應數據給客戶端
3.代碼講解(基於內存)
基於內存是循序漸進的方式進行學習,這個模式我們我們只能用來學習,不能帶入生產環境中
3.1 : 工程搭建
我只展示關鍵性的代碼,后面整個Dmeo會上傳碼雲,有興趣的朋友可以clone下來看看
創建一個常規的SpringBoot的模塊項目
-
主要的依賴,當然還有其他的依賴,比如web,jdbc...等
<!--Oauth2 內含有spring-cloud-starter-security等級聯依賴--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> <version>2.0.2.RELEASE</version> </dependency>
-
入門級的配置
/** * 開啟web安全和啟用security攔截器 * 認證的配置 */ @Configuration @EnableWebSecurity //全局方法攔截 @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true) public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } /** * security 配置默認的密碼加密工具類 * @return */ @Bean public BCryptPasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } /** * 認證 * @param auth * @throws Exception */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { /*最開始在內存中創建了兩個用戶*/ auth.inMemoryAuthentication() .withUser("ninja").password("ninja").roles("USERS") .and() .withUser("admin").password("admin").roles("ADMIN"); } }
/** * 授權的配置 */ @Configuration @EnableAuthorizationServer public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { @Autowired private BCryptPasswordEncoder bCryptPasswordEncoder; /** * 配置客戶端 * @param clients * @throws Exception */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients // 使用內存設置 .inMemory() // client_id .withClient("client") // client_secret .secret("secret") // 授權類型:授權碼模式 .authorizedGrantTypes("authorization_code") // 授權范圍 .scopes("app") // 注冊回調地址 .redirectUris("https://www.cnblogs.com/msi-chen"); } }
-
大家在上面可以看到,我注入了BCryptPasswordEncoder類,按照以往的規矩,Security中所有的密碼都是需要加密的,但是我加密后認證會不通過,后來發現不用加密才能認證通過,所以沒有使用密碼加密,這個后面會處理一下,現在這個階段我們先不去理會
3.2 : 啟動項目
迷惑的是,控制台也沒有給我打印默認的user用戶的密碼,不管了,先演示效果再說吧
訪問:http://localhost:8080/oauth/authorize?client_id=client&response_type=code
輸入我們之前在內存中設置的賬戶和密碼,回車可以發現
-
詢問是否同意授權,勾選左邊的同意,然后授權
然后發現頁面跳轉到了我們之前在配置類中配置的回調地址上
-
注意回調地址后面跟了我們想要的授權碼,我們需要這個授權碼
啟動postman,帶上授權碼申請令牌
-
注意請求:http://client:secret@localhost:8080/oauth/token
-
client和secret都是我們在內存中備案過的數據,這里必須一致
-
-
攜帶參數:grant_type 和 code
-
grant_type 也是在內存中備案過了的,
-
code 是我們剛剛申請到的授權碼
-
3.3 : 疑惑說明
我在上面說到,為什么現在可以使用明文在代碼中進行備案呢,網上查找資料發現是security的版本問題導致的
security在5.0版本之前是可以使用明文備案數據的,但在5.0之后就必須使用BCryptPasswordEncoder進行密碼加密備案
到這里,我們申請到了令牌,接下來就可以使用令牌去訪問我們的資源服務器了
4.代碼講解(基於RBAC)
4.1 : 數據庫適配
但是這個表呢,但是是基於HSQL的,所以不太適配我們的MySQL,需要做以下簡單的更改
-
默認建表語句中主鍵為
VARCHAR(256)
,這超過了最大的主鍵長度,需要手動修改為128
, -
並用
BLOB
替換語句中的LONGVARBINARY
類型
建表語句如下所示:一共七張表導入數據庫即可
CREATE TABLE `clientdetails` ( `appId` varchar(128) NOT NULL, `resourceIds` varchar(256) DEFAULT NULL, `appSecret` varchar(256) DEFAULT NULL, `scope` varchar(256) DEFAULT NULL, `grantTypes` varchar(256) DEFAULT NULL, `redirectUrl` varchar(256) DEFAULT NULL, `authorities` varchar(256) DEFAULT NULL, `access_token_validity` int(11) DEFAULT NULL, `refresh_token_validity` int(11) DEFAULT NULL, `additionalInformation` varchar(4096) DEFAULT NULL, `autoApproveScopes` varchar(256) DEFAULT NULL, PRIMARY KEY (`appId`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `oauth_access_token` ( `token_id` varchar(256) DEFAULT NULL, `token` blob, `authentication_id` varchar(128) NOT NULL, `user_name` varchar(256) DEFAULT NULL, `client_id` varchar(256) DEFAULT NULL, `authentication` blob, `refresh_token` varchar(256) DEFAULT NULL, PRIMARY KEY (`authentication_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `oauth_approvals` ( `userId` varchar(256) DEFAULT NULL, `clientId` varchar(256) DEFAULT NULL, `scope` varchar(256) DEFAULT NULL, `status` varchar(10) DEFAULT NULL, `expiresAt` timestamp NULL DEFAULT NULL, `lastModifiedAt` timestamp NULL DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `oauth_client_details` ( `client_id` varchar(128) 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; CREATE TABLE `oauth_client_token` ( `token_id` varchar(256) DEFAULT NULL, `token` blob, `authentication_id` varchar(128) NOT NULL, `user_name` varchar(256) DEFAULT NULL, `client_id` varchar(256) DEFAULT NULL, PRIMARY KEY (`authentication_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `oauth_code` ( `code` varchar(256) DEFAULT NULL, `authentication` blob ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `oauth_refresh_token` ( `token_id` varchar(256) DEFAULT NULL, `token` blob, `authentication` blob ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
4.2 : 配置文件變化
注意:
-
數據庫連接的url變成了jdbc-url,是為了迎合security的讀取規則,所以這里需要更改一下
server: port: 8080 spring: application: name: auth-server datasource: driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://192.168.217.150:3306/ninja_blog?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false username: root password: root type: com.zaxxer.hikari.HikariDataSource #Hikari數據庫連接池 hikari: minimum-idle: 200 # 最小空閑連接數量 idle-timeout: 20000 # 空閑連接存活最大時間,默認600000(10分鍾) maximum-pool-size: 2000 # 連接池最大連接數,默認是10 auto-commit: true # 此屬性控制從池返回的連接的默認自動提交行為,默認值:true pool-name: OfficialWebsiteHikariCP # 連接池母子 max-lifetime: 1800000 # 此屬性控制池中連接的最長生命周期,值0表示無限生命周期,默認1800000即30分鍾 connection-timeout: 300000 # 數據庫連接超時時間,默認30秒,即30000 connection-test-query: SELECT 1
4.2 : 授權信息刷盤到數據庫
之前我們在代碼中備案了授權的數據,現在我們將其刷盤搭配數據中,不再使用內存的方式寄存數據
-
將以下數據刷盤到數據庫的該表中:oauth_client_details
手動刷盤數據到數據庫中,如下所示
更改之前基於內存的認證配置類如下所示
package com.ninja.blog.config; import jdk.nashorn.internal.parser.TokenStream; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 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.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore; import javax.sql.DataSource; /** * 授權的配置 */ @Configuration @EnableAuthorizationServer public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { @Autowired private BCryptPasswordEncoder bCryptPasswordEncoder; /** * 替換SpringBoot默認裝配的數據源,迎合security裝配新的數據源 * @return */ @Bean @Primary @ConfigurationProperties(prefix = "spring.datasource") public DataSource dataSource(){ return DataSourceBuilder.create().build(); } /** * 令牌保存委托給數據庫 */ @Bean public TokenStore tokenStore(){ return new JdbcTokenStore(dataSource()); } /** * 將client_id secret等客戶端信息的存取委托給數據庫 * @return */ @Bean public ClientDetailsService jdbcClientDetails(){ return new JdbcClientDetailsService(dataSource()); } /** * 令牌的設置 * @param endpoints * @throws Exception */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.tokenStore(tokenStore()); } /** * 配置客戶端 * @param clients * @throws Exception */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { // clients // // 使用內存設置 // .inMemory() // // client_id // .withClient("client") // // client_secret // .secret("secret") // // 授權類型:授權碼模式 // .authorizedGrantTypes("authorization_code") // // 授權范圍 // .scopes("app") // // 注冊回調地址 // .redirectUris("https://www.cnblogs.com/msi-chen"); /*客戶端配置信息讀取寄托於數據庫*/ clients.withClientDetails(jdbcClientDetails()); } }
此時,我們已經將認證的信息刷盤到了數據庫,至於用戶的信息的刷盤我們在下面完成
-
此時即可啟動服務,體驗此次數據刷盤是否成功
我們再次拿到了token令牌,此時可去:oauth_access_token表中查看Token的信息,也是保存在數據庫中的
4.3 : RBAC數據庫模型
RBAC基本的五張表結構
CREATE TABLE `ninja_permission` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `parent_id` bigint(20) DEFAULT NULL COMMENT '父權限', `name` varchar(64) NOT NULL COMMENT '權限名稱', `enname` varchar(64) NOT NULL COMMENT '權限英文名稱', `url` varchar(255) NOT NULL COMMENT '授權路徑', `description` varchar(200) DEFAULT NULL COMMENT '備注', `created` datetime NOT NULL, `updated` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8 COMMENT='權限表'; CREATE TABLE `ninja_role` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `parent_id` bigint(20) DEFAULT NULL COMMENT '父角色', `name` varchar(64) NOT NULL COMMENT '角色名稱', `enname` varchar(64) NOT NULL COMMENT '角色英文名稱', `description` varchar(200) DEFAULT NULL COMMENT '備注', `created` datetime NOT NULL, `updated` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8 COMMENT='角色表'; CREATE TABLE `ninja_role_permission` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `role_id` bigint(20) NOT NULL COMMENT '角色 ID', `permission_id` bigint(20) NOT NULL COMMENT '權限 ID', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8 COMMENT='角色權限表'; CREATE TABLE `ninja_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '用戶名', `password` varchar(64) NOT NULL COMMENT '密碼,加密存儲', `phone` varchar(20) DEFAULT NULL COMMENT '注冊手機號', `email` varchar(50) DEFAULT NULL COMMENT '注冊郵箱', `created` datetime NOT NULL, `updated` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`) USING BTREE, UNIQUE KEY `phone` (`phone`) USING BTREE, UNIQUE KEY `email` (`email`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8 COMMENT='用戶表'; CREATE TABLE `ninja_user_role` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL COMMENT '用戶 ID', `role_id` bigint(20) NOT NULL COMMENT '角色 ID', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8 COMMENT='用戶角色表';
關系圖如下所示
4.4 : 認證信息刷盤到數據庫
上面的代碼中,我們的用戶權限信息都是寄存在內存中,現在我們將認證信息也刷到到數據庫進行一輪測試
-
首先在數據庫中注入一下數據
用戶表:ninja_user
insert into `ninja_user`(`id`,`username`,`password`,`phone`,`email`,`created`,`updated`) values (1,'ninja','ninja','16602832000','https://www.cnblogs.com/msi-chen','2021-05-08 12:39:21','2021-05-08 12:39:21');
角色表:ninja_role
insert into `ninja_role`(`id`,`parent_id`,`name`,`enname`,`description`,`created`,`updated`) values (1,0,'超級管理員','admin',NULL,'2021-05-08 12:39:21','2021-05-08 12:39:21');
權限表:ninja_permission
insert into `ninja_permission`(`id`,`parent_id`,`name`,`enname`,`url`,`description`,`created`,`updated`) values (1,0,'系統管理','System','/',NULL,'2021-05-08 12:39:21','2021-05-08 12:39:21'), (2,1,'用戶管理','SystemUser','/users/',NULL,'2021-05-08 12:39:21','2021-05-08 12:39:21'), (3,2,'查看用戶','SystemUserView','',NULL,'2021-05-08 12:39:21','2021-05-08 12:39:21'), (4,2,'新增用戶','SystemUserInsert','',NULL,'2021-05-08 12:39:21','2021-05-08 12:39:21'), (5,2,'編輯用戶','SystemUserUpdate','',NULL,'2021-05-08 12:39:21','2021-05-08 12:39:21'), (6,2,'刪除用戶','SystemUserDelete','',NULL,'2021-05-08 12:39:21','2021-05-08 12:39:21');
用戶角色關聯表
insert into `ninja_user_role`(`id`,`user_id`,`role_id`) values (1,1,1);
角色權限關聯表
insert into `ninja_role_permission`(`id`,`role_id`,`permission_id`) values (1,1,1), (2,1,2), (3,1,3), (4,1,4), (5,1,5), (6,1,6);
4.5 : 定義查詢接口
在這兒我們定義的查詢接口可以是一個,也可以是兩個,唯一的目的的將用戶名、密碼、權限。數據交給Security
在這里,因為我之前已經大致上對功能做了模塊化的划分,所以我寫了兩個查詢接口,然后遠程調用他們完成最后數據封裝
-
第一個OpenFein遠程調用用戶模塊的接口,通過用戶名查詢用戶信息,得到用戶的id、username、password
-
第一個OpenFein遠程調用用戶模塊模塊的權限查詢接口,通過用戶id,查詢其所有的權限集合
大致業務邏輯如下所示,無論你怎么寫,唯一的目的就是講用戶名、密碼、權限集合封裝好交給security
4.6 : 服務器安全配置
在之前的代碼中,我們的用戶名、密碼、權限信息都是在內存進行寄存的,現在我們要對齊進行變更為數據庫中寄存
4.7 : 其他配置
這里因為涉及到OpenFeign遠程調用,所以這里我們得引入Nacos和OpenFeign的依賴,並做好相應的配置信息
這里我就不多太詳細的代碼說明了,說一下步驟即可,源碼后面我會上傳到碼雲中,有興趣的朋友可以拉下來看一看
-
目前涉及三個模塊
-
ninja-admin
-
提供上面所需的兩個接口:根據username查詢用戶,根據用戶Id查詢其所有權限
-
-
ninja-auth
-
認證與授權中心,openFeign遠程調用ninja-admin中提供的兩個開放接口
-
-
ninja-common
-
存放基本的公共依賴、工具類、實體類、遠程調用客戶端接口等通用代碼
-
-
4.8 : 效果測試
原來的配方和原來的味道
-
訪問:http://localhost:8080/oauth/authorize?client_id=client&response_type=code
-
使用之前在數據庫中手動寫入的賬號和密碼進行登錄
-
然后我在后台打了一個斷點,測試一下數據是否有問題
-
點擊授權后完成頁面的跳轉,獲得授權碼
-
拿着授權碼去申請令牌
-
到這里我們的令牌就算申請到了
5.低配版服務認證與授權
在上面的演示代碼中,我們已經能夠獲取授權碼,再用授權碼去申請令牌,接下來的操作就是我提供一個資源訪問接口,你需要帶着申請的令牌來訪問,且該令牌有訪問權限才會被正確的訪問,如果令牌錯誤或者改令牌沒有訪問權限,你是讀取我寫的接口的,下面我們就來實現一個這個過程
為了后面其他技術的演示,這里我創建一個ninja-order訂單模塊,我們就把這個訂單模塊作為一個資源服務器對外暴露服務
可以看到這個模塊很簡單,就是提供一個訪問接口即可,可以隨便訪問
下面准備接入我們的security的管控,讓他受到保護,不能被隨便的訪問,必須要經過以下步驟才能訪問
-
用戶首先通過用戶名和密碼請求認證中心申請授權碼,同意授權,獲得授權碼
-
帶着授權碼申請令牌
-
帶着令牌訪問我們的資源服務器
-
資源服務器拿到令牌與認證中心進行通信,校驗改令牌的有效性和獲得該令牌的權限列表
-
資源服務器拿着該令牌的權限列表與訪問接口所需權限進行匹配
-
匹配通過,資源請求放行,權限匹配失敗,則無權訪問,直接駁回
5.1 : 資源服務器配置
第一個點:接入Security的管控
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> <version>2.2.2.RELEASE</version> </dependency>
第二個點:配置改模塊security全局攔截,不允許隨意訪問
@Configuration //指定為security的資源服務 @EnableResourceServer //全局方法攔截 @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true) public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http .exceptionHandling() .and() /*session的創建策略*/ // ALWAYS : security總是創建HttpSession來或許security的上下文 // NEVER : secutiry不會創建HttpSession,如果其存在,會使用,不存在則不實用 // IF_REQUIRED : 只會在需要需要時創建HttpSession // STATELESS : 永遠不會創建也不會使用HttpSession .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() // 以下為配置所需保護的資源路徑及權限,需要與認證服務器配置的授權部分對應 .antMatchers("/").hasAuthority("System") .antMatchers("/order/findById/**").hasAuthority("System"); } }
第三個點:配置客戶端信息以及校驗token的多個信息
到這里,我們的資源服務器算是被監控好了
5.2 : 認證中心服務配置
在ninja-order這個資源服務器的yml配置文件中,我們配置好了 token令牌校驗的地址和路徑,但在認證心中中那個接口是需要授權才能訪問的,所以這里認證中心只需要一點點改動,忽略該接口即可
@Configuration @EnableWebSecurity //全局方法攔截 @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true) public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { //...之前的配置省略 @Override public void configure(WebSecurity web) throws Exception { /*不想進行授權就能訪問的配置*/ // /oauth/check_token 是資源服務器向認證服務器發送請求,檢查token令牌正確性的接口,不需要認證,直接放行 // 還有一些靜態資源也不需要授權,都可以放下面 web.ignoring().antMatchers("/oauth/check_token"); } }
然后重啟兩個服務,我們看一下效果
5.3 : 效果測試
-
沒有令牌,直接訪問剛剛我們寫的接口顯示沒有權限:被駁回了
-
再訪問:http://localhost:8000/oauth/authorize?client_id=client&response_type=code
-
使用之前在數據庫備案過的賬戶和密碼申請授權碼:ninja / ninja
-
-
然后再用授權碼申請token令牌
-
攜帶token令牌再去訪問我們剛剛寫的那個接口
-
可以發現,完全沒有任何問題,我們再做下一步測試,模擬我們的ninja用戶沒有訪問該接口的權限
我們把該接口的訪問權限設置為test,但是我們知道,我們的數據庫中ninja這個賬戶是沒有test權限的,
-
下面我們再測試一下看是什么響應