Spring Security OAuth2分布式系统认证解决方案
目錄
- 1 什么是分布式系統
- 2 分布式認證需求
- 3 分布式認證方案
- 3.1 選型分析
- 3.2 技術方案
- 4 OAuth2.0
- 4.1 OAuth2.0介紹
- 4.2 Spring Cloud Security OAuth2
- 4.2.1 環境介紹
- 4.2.2 環境搭建
- 4.2.2.1 父工程
- 4.2.2.2 創建UAA授權服務工程
- 4.2.2.3 創建Order資源服務工程
- 4.2.2.授權服務器配置
- 4.2.2.1 EnableAuthorizationServer
- 4.2.2.1.配置客戶端詳細信息
- 4.2.2.2.管理令牌
- 4.2.2.3.令牌訪問端點配置
- 4.2.2.4.令牌端點的安全約束
- 4.2.2.5 web安全配置
- 4.2.3.授權碼模式
- 4.2.3.1 授權碼模式介紹
- 4.2.3.2 測試
- 4.2.4. 簡化模式
- 4.2.4.1 簡化模式介紹
- 4.2.5.密碼模式
- 4.2.5.1 授權碼模式介紹
- 4.2.5.2 測試
- 4.2.6.客戶端模式
- 4.2.6.1 客戶端模式介紹
- 4.2.7.資源服務測試
- 4.2.7.1 資源服務器配置
- 4.2.7.2 驗證token
- 4.2.7.3 編寫資源
- 4.2.7.4 添加安全訪問控制
- 4.2.7.5 測試
1 什么是分布式系統
隨著軟件環境和需求的變化 ,軟件的架構由單體結構演變為分布式架構,具有分布式架構的系統叫分布式系統,分布式系統的運行通常依賴網絡,它將單體結構的系統分為若干服務,服務之間通過網絡交互來完成用戶的業務處理,當前流行的微服務架構就是分布式系統架構,如下圖:
分布式系統具體如下基本特點:
1、分布性:每個部分都可以獨立部署,服務之間交互通過網絡進行通信,比如:訂單服務、商品服務。
2、伸縮性:每個部分都可以集群方式部署,并可針對部分結點進行硬件及軟件擴容,具有一定的伸縮能力。
3、共享性:每個部分都可以作為共享資源對外提供服務,多個部分可能有操作共享資源的情況。
4、開放性:每個部分根據需求都可以對外發布共享資源的訪問接口,并可允許第三方系統訪問。
2 分布式認證需求
分布式系統的每個服務都會有認證、授權的需求,如果每個服務都實現一套認證授權邏輯會非常冗余,考慮分布式系統共享性的特點,需要由獨立的認證服務處理系統認證授權的請求;考慮分布式系統開放性的特點,不僅對系統內部服務提供認證,對第三方系統也要提供認證。分布式認證的需求總結如下:
統一認證授權
提供獨立的認證服務,統一處理認證授權。
無論是不同類型的用戶,還是不同種類的客戶端(web端,H5、APP),均采用一致的認證、權限、會話機制,實現統一認證授權。
實現統一則認證方式必須可擴展,支持各種認證需求,比如:用戶名密碼認證、短信驗證碼、二維碼、人臉識別等認證方式,并可以非常靈活的切換。
應用接入認證
應提供擴展和開放能力,提供安全的系統對接機制,并可開放部分API給接入第三方使用,一方應用(內部 系統服務)和三方應用(第三方應用)均采用統一機制接入。
3 分布式認證方案
3.1 選型分析
1、基于session的認證方式
在分布式的環境下,基于session的認證會出現一個問題,每個應用服務都需要在session中存儲用戶身份信息,通過負載均衡將本地的請求分配到另一個應用服務需要將session信息帶過去,否則會重新認證。
這個時候,通常的做法有下面幾種:
Session復制:多臺應用服務器之間同步session,使session保持一致,對外透明。
Session黏貼:當用戶訪問集群中某臺服務器后,強制指定后續所有請求均落到此機器上。
Session集中存儲:將Session存入分布式緩存中,所有服務器應用實例統一從分布式緩存中存取Session。
總體來講,基于session認證的認證方式,可以更好的在服務端對會話進行控制,且安全性較高。但是,session機制方式基于cookie,在復雜多樣的移動客戶端上不能有效的使用,并且無法跨域,另外隨著系統的擴展需提高session的復制、黏貼及存儲的容錯性。
2、基于token的認證方式
基于token的認證方式,服務端不用存儲認證數據,易維護擴展性強, 客戶端可以把token 存在任意地方,并且可以實現web和app統一認證機制。其缺點也很明顯,token由于自包含信息,因此一般數據量較大,而且每次請求都需要傳遞,因此比較占帶寬。另外,token的簽名驗簽操作也會給cpu帶來額外的處理負擔。
3.2 技術方案
根據 選型的分析,決定采用基于token的認證方式,它的優點是:
1、適合統一認證的機制,客戶端、一方應用、三方應用都遵循一致的認證機制。
2、token認證方式對第三方應用接入更適合,因為它更開放,可使用當前有流行的開放協議Oauth2.0、JWT等。
3、一般情況服務端無需存儲會話信息,減輕了服務端的壓力。
分布式系統認證技術方案見下圖:
流程描述:
(1)用戶通過接入方(應用)登錄,接入方采取OAuth2.0方式在統一認證服務(UAA)中認證。
(2)認證服務(UAA)調用驗證該用戶的身份是否合法,并獲取用戶權限信息。
(3)認證服務(UAA)獲取接入方權限信息,并驗證接入方是否合法。
(4)若登錄用戶以及接入方都合法,認證服務生成jwt令牌返回給接入方,其中jwt中包含了用戶權限及接入方權限。
(5)后續,接入方攜帶jwt令牌對API網關內的微服務資源進行訪問。
(6)API網關對令牌解析、并驗證接入方的權限是否能夠訪問本次請求的微服務。
(7)如果接入方的權限沒問題,API網關將原請求header中附加解析后的明文Token,并將請求轉發至微服務。
(8)微服務收到請求,明文token中包含登錄用戶的身份和權限信息。因此后續微服務自己可以干兩件事:1,用戶授權攔截(看當前用戶是否有權訪問該資源)2,將用戶信息存儲進當前線程上下文(有利于后續業務邏輯隨時獲取當前用戶信息)
流程所涉及到UAA服務、API網關這三個組件職責如下:
1)統一認證服務(UAA)
它承載了OAuth2.0接入方認證、登入用戶的認證、授權以及生成令牌的職責,完成實際的用戶認證、授權功能。
2)API網關
作為系統的唯一入口,API網關為接入方提供定制的API集合,它可能還具有其它職責,如身份驗證、監控、負載均衡、緩存等。API網關方式的核心要點是,所有的接入方和消費端都通過統一的網關接入微服務,在網關層處理所有的非業務功能。
4 OAuth2.0
4.1 OAuth2.0介紹
OAuth(開放授權)是一個開放標準,允許用戶授權第三方應用訪問他們存儲在另外的服務提供者上的信息,而不需要將用戶名和密碼提供給第三方應用或分享他們數據的所有內容。OAuth2.0是OAuth協議的延續版本,但不向后兼容OAuth 1.0即完全廢止了OAuth1.0。很多大公司如Google,Yahoo,Microsoft等都提供了OAUTH認證服務,這些都足以說明OAUTH標準逐漸成為開放資源授權的標準。
Oauth協議目前發展到2.0版本,1.0版本過于復雜,2.0版本已得到廣泛應用。
參考:https://baike.baidu.com/item/oAuth/7153134?fr=aladdin
Oauth 協議:https://tools.ietf.org/html/rfc6749
下邊分析一個 Oauth2認證的例子,通過例子去理解OAuth2.0協議的認證流程,本例子是某個客戶端網站使用微信認證的過程,這個過程的簡要描述如下:
用戶借助微信認證登錄客戶端網站,用戶就不用單獨在客戶端注冊用戶,怎么樣算認證成功嗎?客戶端網站需要成功從微信獲取用戶的身份信息則認為用戶認證成功,那如何從微信獲取用戶的身份信息?用戶信息的擁有者是用戶本人,微信需要經過用戶的同意方可為客戶端網站生成令牌,客戶端網站拿此令牌方可從微信獲取用戶的信息。
1、客戶端請求第三方授權
用戶進入客戶端的登錄頁面,點擊微信的圖標以微信賬號登錄系統,用戶是自己在微信里信息的資源擁有者。
點擊“微信”出現一個二維碼,此時用戶掃描二維碼,開始給客戶端網站授權。
2、資源擁有者同意給客戶端授權
資源擁有者掃描二維碼表示資源擁有者同意給客戶端授權,微信會對資源擁有者的身份進行驗證, 驗證通過后,微信會詢問用戶是否給授權客戶端網站訪問自己的微信數據,用戶點擊“確認登錄”表示同意授權,微信認證服務器會頒發一個授權碼,并重定向到客戶端的網站。
3、客戶端獲取到授權碼,請求認證服務器申請令牌:
此過程用戶看不到,客戶端應用程序請求認證服務器,請求攜帶授權碼。
4、認證服務器向客戶端響應令牌:
微信認證服務器驗證了客戶端請求的授權碼,如果合法則給客戶端頒發令牌,令牌是客戶端訪問資源的通行證。此交互過程用戶看不到,當客戶端拿到令牌后,用戶在客戶端網站看到已經登錄成功。
5、客戶端請求資源服務器的資源
客戶端攜帶令牌訪問資源服務器的資源。
客戶端網站攜帶令牌請求訪問微信服務器獲取用戶的基本信息。
6、資源服務器返回受保護資源
資源服務器校驗令牌的合法性,如果合法則向用戶響應資源信息內容。
以上認證授權詳細的執行流程如下:
通過上邊的例子我們大概了解了OAauth2.0的認證過程,下邊我們看OAuth2.0認證流程:
引自OAauth2.0協議rfc6749 https://tools.ietf.org/html/rfc6749
OAauth2.0包括以下角色:
1、客戶端
本身不存儲資源,需要通過資源擁有者的授權去請求資源服務器的資源,比如:Android客戶端、Web客戶端(瀏覽器端)、微信客戶端等。
2、資源擁有者
通常為用戶,也可以是應用程序,即該資源的擁有者。
3、授權服務器(也稱認證服務器)
用于服務提供商對資源擁有的身份進行認證、對訪問資源進行授權,認證成功后會給客戶端發放令牌(access_token),作為客戶端訪問資源服務器的憑據。本例為微信的認證服務器。
4、資源服務器
存儲資源的服務器,本例子為微信存儲的用戶信息。
現在還有一個問題,服務提供商能允許隨便一個客戶端就接入到它的授權服務器嗎?答案是否定的,服務提供商會給準入的接入方一個身份,用于接入時的憑據:
client_id:客戶端標識 client_secret:客戶端秘鑰
因此,準確來說,授權服務器對兩種OAuth2.0中的兩個角色進行認證授權,分別是資源擁有者、客戶端。
4.2 Spring Cloud Security OAuth2
4.2.1 環境介紹
Spring-Security-OAuth2是對OAuth2的一種實現,并且跟我們之前學習的Spring Security相輔相成,與Spring Cloud體系的集成也非常便利,接下來,我們需要對它進行學習,最終使用它來實現我們設計的分布式認證授權解決方案。
OAuth2.0的服務提供方涵蓋兩個服務,即授權服務 (Authorization Server,也叫認證服務) 和資源服務 (Resource Server),使用 Spring Security OAuth2 的時候你可以選擇把它們在同一個應用程序中實現,也可以選擇建立使用同一個授權服務的多個資源服務。
**授權服務 (Authorization Server)**應包含對接入端以及登入用戶的合法性進行驗證并頒發token等功能,對令牌的請求端點由 Spring MVC 控制器進行實現,下面是配置一個認證服務必須要實現的endpoints:
- AuthorizationEndpoint 服務于認證請求。默認 URL: /oauth/authorize 。
- TokenEndpoint 服務于訪問令牌的請求。默認 URL: /oauth/token 。
資源服務 (Resource Server),應包含對資源的保護功能,對非法請求進行攔截,對請求中token進行解析鑒權等,下面的過濾器用于實現 OAuth 2.0 資源服務:
- OAuth2AuthenticationProcessingFilter 用來對請求給出的身份令牌解析鑒權。
本教程分別創建uaa授權服務(也可叫認證服務)和order訂單資源服務。
認證流程如下:
1、客戶端請求UAA授權服務進行認證。
2、認證通過后由UAA頒發令牌。
3、客戶端攜帶令牌Token請求資源服務。
4 、資源服務校驗令牌的合法性,合法即返回資源信息。
4.2.2 環境搭建
4.2.2.1 父工程
創建maven工程作為父工程,依賴如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven- 4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.lw.security</groupId><artifactId>distributed-security</artifactId><version>1.0-SNAPSHOT</version><packaging>pom</packaging><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.3.RELEASE</version></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>Greenwich.RELEASE</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>provided</scope></dependency><dependency><groupId>javax.interceptor</groupId> <artifactId>javax.interceptor-api</artifactId><version>1.2</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.47</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.0</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-jwt</artifactId><version>1.0.10.RELEASE</version></dependency><dependency><groupId>org.springframework.security.oauth.boot</groupId><artifactId>spring-security-oauth2-autoconfigure</artifactId><version>2.1.3.RELEASE</version></dependency></dependencies></dependencyManagement><build><finalName>${project.name}</finalName><resources><resource><directory>src/main/resources</directory><filtering>true</filtering><includes><include>**/*</include></includes></resource><resource> <directory>src/main/java</directory><includes><include>**/*.xml</include></includes></resource></resources><plugins><!--<plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin>--><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>1.8</source><target>1.8</target></configuration></plugin><plugin><artifactId>maven-resources-plugin</artifactId><configuration><encoding>utf-8</encoding><useDefaultDelimiters>true</useDefaultDelimiters></configuration></plugin></plugins></build> </project>4.2.2.2 創建UAA授權服務工程
1、創建distributed-security-uaa
創建distributed-security-uaa作為授權服務工程,依賴如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven- 4.0.0.xsd"><parent><artifactId>distributed-security</artifactId><groupId>com.lw.security</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>distributed-security-uaa</artifactId><dependencies><!--<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency>--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-hystrix</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-ribbon</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><dependency><groupId>com.netflix.hystrix</groupId><artifactId>hystrix-javanica</artifactId></dependency><dependency><groupId>org.springframework.retry</groupId><artifactId>spring-retry</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId></dependency><dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-commons</artifactId></dependency><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><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-jwt</artifactId></dependency><dependency><groupId>javax.interceptor</groupId><artifactId>javax.interceptor-api</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies> </project>工程結構如下:
2、啟動類
本工程采用SpringBoot開發,每個工程編寫一個啟動類:
@SpringBootApplication @EnableDiscoveryClient @EnableHystrix @EnableFeignClients(basePackages = {"com.lw.security.distributed.uaa"}) public class UAAServer {public static void main(String[] args) {SpringApplication.run(UAAServer.class, args);} }3、配置文件
在resources下創建application.properties
spring.application.name=uaa-service server.port=53020 spring.main.allow-bean-definition-overriding = true logging.level.root = debug logging.level.org.springframework.web = info spring.http.encoding.enabled = true spring.http.encoding.charset = UTF-8 spring.http.encoding.force = true server.tomcat.remote_ip_header = x-forwarded-for server.tomcat.protocol_header = x-forwarded-proto server.use-forward-headers = true server.servlet.context-path = /uaa spring.freemarker.enabled = true spring.freemarker.suffix = .html spring.freemarker.request-context-attribute = rc spring.freemarker.content-type = text/html spring.freemarker.charset = UTF-8 spring.mvc.throw-exception-if-no-handler-found = true spring.resources.add-mappings = false spring.datasource.url = jdbc:mysql://localhost:3306/user_db?useUnicode=true spring.datasource.username = root spring.datasource.password = mysql spring.datasource.driver-class-name = com.mysql.jdbc.Driver #eureka.client.serviceUrl.defaultZone = http://localhost:53000/eureka/ #eureka.instance.preferIpAddress = true #eureka.instance.instance-id = ${spring.application.name}:${spring.cloud.client.ip-address}:${spring.application.instance_id:${server.port}} management.endpoints.web.exposure.include = refresh,health,info,env feign.hystrix.enabled = true feign.compression.request.enabled = true feign.compression.request.mime-types[0] = text/xml feign.compression.request.mime-types[1] = application/xml feign.compression.request.mime-types[2] = application/json feign.compression.request.min-request-size = 2048 feign.compression.response.enabled = true4.2.2.3 創建Order資源服務工程
本工程為Order訂單服務工程,訪問本工程的資源需要認證通過。
本工程的目的主要是測試認證授權的功能,所以不涉及訂單管理相關業務。
1、創建Order工程
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven- 4.0.0.xsd"><parent><artifactId>distributed-security</artifactId><groupId>com.lw.security</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>distributed-security-order</artifactId> <dependencies><!--<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency>--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><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><dependency><groupId>javax.interceptor</groupId><artifactId>javax.interceptor-api</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies> </project>2、工程結構
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-CvhF8cmR-1644219205107)(data:image/svg+xml;utf8, )]
3、配置文件
在resources中創建application.properties
spring.application.name=order-service server.port=53021 spring.main.allow-bean-definition-overriding = true logging.level.root = debug logging.level.org.springframework.web = info spring.http.encoding.enabled = true spring.http.encoding.charset = UTF-8 spring.http.encoding.force = true server.tomcat.remote_ip_header = x-forwarded-for server.tomcat.protocol_header = x-forwarded-proto server.use-forward-headers = true server.servlet.context-path = /order spring.freemarker.enabled = true spring.freemarker.suffix = .html spring.freemarker.request-context-attribute = rc spring.freemarker.content-type = text/html spring.freemarker.charset = UTF-8 spring.mvc.throw-exception-if-no-handler-found = true spring.resources.add-mappings = false #eureka.client.serviceUrl.defaultZone = http://localhost:53000/eureka/ #eureka.instance.preferIpAddress = true #eureka.instance.instance-id = ${spring.application.name}:${spring.cloud.client.ip-address}:${spring.application.instance_id:${server.port}} management.endpoints.web.exposure.include = refresh,health,info,env feign.hystrix.enabled = true feign.compression.request.enabled = true feign.compression.request.mime-types[0] = text/xml feign.compression.request.mime-types[1] = application/xml feign.compression.request.mime-types[2] = application/json feign.compression.request.min-request-size = 2048 feign.compression.response.enabled = true4.2.2.授權服務器配置
4.2.2.1 EnableAuthorizationServer
可以用@EnableAuthorizationServer注解并繼承AuthorizationServerConfigurerAdapter來配置OAuth2.0 授權服務器。
在Config包下創建AuthorizationServer:
@Configuration @EnableAuthorizationServer public class AuthorizationServer extendsAuthorizationServerConfigurerAdapter {//略...}AuthorizationServerConfigurerAdapter要求配置以下幾個類,這幾個類是由Spring創建的獨立的配置對象,它們會被Spring傳入AuthorizationServerConfigurer中進行配置。
public class AuthorizationServerConfigurerAdapter implements AuthorizationServerConfigurer {public AuthorizationServerConfigurerAdapter() {}public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {}public void configure(ClientDetailsServiceConfigurer clients) throws Exception {}public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {} }-
ClientDetailsServiceConfigurer :用來配置客戶端詳情服務(ClientDetailsService),客戶端詳情信息在這里進行初始化,你能夠把客戶端詳情信息寫死在這里或者是通過數據庫來存儲調取詳情信息。
-
AuthorizationServerEndpointsConfigurer :用來配置令牌(token)的訪問端點和令牌服務(token services)。
-
AuthorizationServerSecurityConfigurer :用來配置令牌端點的安全約束.
4.2.2.1.配置客戶端詳細信息
ClientDetailsServiceConfigurer 能夠使用內存或者JDBC來實現客戶端詳情服務(ClientDetailsService),ClientDetailsService負責查找ClientDetails,而ClientDetails有幾個重要的屬性如下列表:
- clientId :(必須的)用來標識客戶的Id。
- secret :(需要值得信任的客戶端)客戶端安全碼,如果有的話。
- scope :用來限制客戶端的訪問范圍,如果為空(默認)的話,那么客戶端擁有全部的訪問范圍。
- authorizedGrantTypes :此客戶端可以使用的授權類型,默認為空。
- authorities :此客戶端可以使用的權限(基于Spring Security authorities)。
客戶端詳情(Client Details)能夠在應用程序運行的時候進行更新,可以通過訪問底層的存儲服務(例如將客戶端詳情存儲在一個關系數據庫的表中,就可以使用 JdbcClientDetailsService)或者通過自己實現ClientRegistrationService接口(同時你也可以實現 ClientDetailsService 接口)來進行管理。
我們暫時使用內存方式存儲客戶端詳情信息,配置如下:
@Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { // clients.withClientDetails(clientDetailsService); clients.inMemory()// 使用in‐memory存儲.withClient("c1")// client_id.secret(new BCryptPasswordEncoder().encode("secret")).resourceIds("res1").authorizedGrantTypes("authorization_code","password","client_credentials","implicit","refresh_token")// 該client允許的授權類型authorization_code,password,refresh_token,implicit,client_credentials.scopes("all")// 允許的授權范圍.autoApprove(false)//加上驗證回調地址.redirectUris("http://www.baidu.com"); }4.2.2.2.管理令牌
AuthorizationServerTokenServices 接口定義了一些操作使得你可以對令牌進行一些必要的管理,令牌可以被用來加載身份信息,里面包含了這個令牌的相關權限。
自己可以創建 AuthorizationServerTokenServices 這個接口的實現,則需要繼承 DefaultTokenServices 這個類,里面包含了一些有用實現,你可以使用它來修改令牌的格式和令牌的存儲。默認的,當它嘗試創建一個令牌的時候,是使用隨機值來進行填充的,除了持久化令牌是委托一個 TokenStore 接口來實現以外,這個類幾乎幫你做了所有的事情。并且 TokenStore 這個接口有一個默認的實現,它就是 InMemoryTokenStore ,如其命名,所有的令牌是被保存在了內存中。除了使用這個類以外,你還可以使用一些其他的預定義實現,下面有幾個版本,它們都實現了TokenStore接口:
- InMemoryTokenStore :這個版本的實現是被默認采用的,它可以完美的工作在單服務器上(即訪問并發量壓力不大的情況下,并且它在失敗的時候不會進行備份),大多數的項目都可以使用這個版本的實現來進行嘗試,你可以在開發的時候使用它來進行管理,因為不會被保存到磁盤中,所以更易于調試。
- JdbcTokenStore :這是一個基于JDBC的實現版本,令牌會被保存進關系型數據庫。使用這個版本的實現時,你可以在不同的服務器之間共享令牌信息,使用這個版本的時候請注意把"spring-jdbc"這個依賴加入到你的classpath當中。
- JwtTokenStore :這個版本的全稱是 JSON Web Token(JWT),它可以把令牌相關的數據進行編碼(因此對于后端服務來說,它不需要進行存儲,這將是一個重大優勢),但是它有一個缺點,那就是撤銷一個已經授權令牌將會非常困難,所以它通常用來處理一個生命周期較短的令牌以及撤銷刷新令牌(refresh_token)。另外一個缺點就是這個令牌占用的空間會比較大,如果你加入了比較多用戶憑證信息。JwtTokenStore 不會保存任何數據,但是它在轉換令牌值以及授權信息方面與 DefaultTokenServices 所扮演的角色是一樣的。
1、定義TokenConfig
在config包下定義TokenConfig,我們暫時先使用InMemoryTokenStore,生成一個普通的令牌。
@Configuration public class TokenConfig {@Beanpublic TokenStore tokenStore() {return new InMemoryTokenStore();} }2、定義AuthorizationServerTokenServices在AuthorizationServer中定義AuthorizationServerTokenServices
@Autowired private TokenStore tokenStore;@Autowiredprivate ClientDetailsService clientDetailsService;@Beanpublic AuthorizationServerTokenServices tokenService() {DefaultTokenServices service=new DefaultTokenServices();service.setClientDetailsService(clientDetailsService);service.setSupportRefreshToken(true);service.setTokenStore(tokenStore);service.setAccessTokenValiditySeconds(7200); // 令牌默認有效期2小時service.setRefreshTokenValiditySeconds(259200); // 刷新令牌默認有效期3天return service;}4.2.2.3.令牌訪問端點配置
AuthorizationServerEndpointsConfigurer 這個對象的實例可以完成令牌服務以及令牌endpoint配置。
配置授權類型(Grant Types)
AuthorizationServerEndpointsConfigurer 通過設定以下屬性決定支持的授權類型(Grant Types):
- authenticationManager :認證管理器,當你選擇了資源所有者密碼(password)授權類型的時候,請設置這個屬性注入一個 AuthenticationManager 對象。
- userDetailsService :如果你設置了這個屬性的話,那說明你有一個自己的 UserDetailsService 接口的實現,或者你可以把這個東西設置到全局域上面去(例如 GlobalAuthenticationManagerConfigurer 這個配置對象),當你設置了這個之后,那么 “refresh_token” 即刷新令牌授權類型模式的流程中就會包含一個檢查,用來確保這個賬號是否仍然有效,假如說你禁用了這個賬戶的話。
- authorizationCodeServices :這個屬性是用來設置授權碼服務的(即 AuthorizationCodeServices 的實例對象),主要用于 “authorization_code” 授權碼類型模式。
- implicitGrantService :這個屬性用于設置隱式授權模式,用來管理隱式授權模式的狀態。
- tokenGranter :當你設置了這個東西(即 TokenGranter 接口實現),那么授權將會交由你來完全掌控,并且會忽略掉上面的這幾個屬性,這個屬性一般是用作拓展用途的,即標準的四種授權模式已經滿足不了你的需求的時候,才會考慮使用這個。
配置授權端點的URL(Endpoint URLs):
AuthorizationServerEndpointsConfigurer 這個配置對象有一個叫做 pathMapping() 的方法用來配置端點URL鏈接,它有兩個參數:
- 第一個參數: String 類型的,這個端點URL的默認鏈接。
- 第二個參數: String 類型的,你要進行替代的URL鏈接。
以上的參數都將以 “/” 字符為開始的字符串,框架的默認URL鏈接如下列表,可以作為這個 pathMapping() 方法的第一個參數:
- /oauth/authorize :授權端點。
- /oauth/token :令牌端點。
- /oauth/confirm_access :用戶確認授權提交端點。
- /oauth/error :授權服務錯誤信息端點。
- /oauth/check_token :用于資源服務訪問的令牌解析端點。
- /oauth/token_key :提供公有密匙的端點,如果你使用JWT令牌的話。
需要注意的是授權端點這個URL應該被Spring Security保護起來只供授權用戶訪問.
在AuthorizationServer配置令牌訪問端點
@Autowired private AuthorizationCodeServices authorizationCodeServices;@Autowiredprivate AuthenticationManager authenticationManager;@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) {endpoints.authenticationManager(authenticationManager).authorizationCodeServices(authorizationCodeServices).tokenServices(tokenService()).allowedTokenEndpointRequestMethods(HttpMethod.POST);}@Beanpublic AuthorizationCodeServices authorizationCodeServices() { //設置授權碼模式的授權碼如何存取,暫時采用內存方式return new InMemoryAuthorizationCodeServices();}因為配置令牌訪問端點需要認證管理器,也要配置
4.2.2.4.令牌端點的安全約束
**AuthorizationServerSecurityConfigurer :**用來配置令牌端點(Token Endpoint)的安全約束,在AuthorizationServer中配置如下.
@Override public void configure(AuthorizationServerSecurityConfigurer security){security.tokenKeyAccess("permitAll()") //(1) .checkTokenAccess("permitAll()") //(2) .allowFormAuthenticationForClients() //(3) ; }(1)tokenkey這個endpoint當使用JwtToken且使用非對稱加密時,資源服務用于獲取公鑰而開放的,這里指這個endpoint完全公開。
(2)checkToken這個endpoint完全公開
(3) 允許表單認證
授權服務配置總結:授權服務配置分成三大塊,可以關聯記憶。
既然要完成認證,它首先得知道客戶端信息從哪兒讀取,因此要進行客戶端詳情配置。
既然要頒發token,那必須得定義token的相關endpoint,以及token如何存取,以及客戶端支持哪些類型的token。
既然暴露除了一些endpoint,那對這些endpoint可以定義一些安全上的約束等。
4.2.2.5 web安全配置
將Spring-Boot工程中的WebSecurityConfig拷貝到UAA工程中。
@Configuration @EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Override@Beanpublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}//安全攔截機制(最重要)@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().authorizeRequests().antMatchers("/r/r1").hasAnyAuthority("p1").antMatchers("/login*").permitAll().anyRequest().authenticated().and() .formLogin();} }4.2.3.授權碼模式
4.2.3.1 授權碼模式介紹
下圖是授權碼模式交互圖:
(1)資源擁有者打開客戶端,客戶端要求資源擁有者給予授權,它將瀏覽器被重定向到授權服務器,重定向時會附加客戶端的身份信息。如:(瀏覽器輸入以下)
http://localhost:53020/uaa/oauth/authorize?client_id=c1&response_type=code&scope=all&redirect_uri=http://www.baidu.com參數列表如下:
- client_id :客戶端準入標識。
- response_type :授權碼模式固定為code。
- scope :客戶端權限。
- redirect_uri :跳轉uri,當授權碼申請成功后會跳轉到此地址,并在后邊帶上code參數(授權碼)。
(2)瀏覽器出現向授權服務器授權頁面,之后將用戶同意授權。
(3)授權服務器將授權碼(AuthorizationCode)轉經瀏覽器發送給client(通過redirect_uri)。
(4)客戶端拿著授權碼向授權服務器索要訪問access_token,請求如下:
http://localhost:53020/uaa/oauth/token?client_id=c1&client_secret=secret&grant_type=authorization_code&code=5PgfcD&redirect_uri=http://www.baidu.com參數列表如下
- client_id :客戶端準入標識。
- client_secret :客戶端秘鑰。
- grant_type :授權類型,填寫authorization_code,表示授權碼模式
- code :授權碼,就是剛剛獲取的授權碼,注意:授權碼只使用一次就無效了,需要重新申請。
- redirect_uri :申請授權碼時的跳轉url,一定和申請授權碼時用的redirect_uri一致。
(5)授權服務器返回令牌(access_token)
這種模式是四種模式中最安全的一種模式。一般用于client是Web服務器端應用或第三方的原生App調用資源服務的時候。因為在這種模式中access_token不會經過瀏覽器或移動端的App,而是直接從服務端去交換,這樣就最大限度的減小了令牌泄漏的風險。
4.2.3.2 測試
瀏覽器訪問認證頁面:
http://localhost:53020/uaa/oauth/authorize?client_id=c1&response_type=code&scope=all&redirect_uri=http://www.baidu.com然后輸入模擬的賬號和密碼點登陸之后進入授權頁面:
確認授權后,瀏覽器會重定向到指定路徑(oauth_client_details表中的web_server_redirect_uri)并附加驗證碼?code=sc0N9W(每次不一樣),最后使用該驗證碼獲取token。
POST http://localhost:53020/uaa/oauth/token4.2.4. 簡化模式
4.2.4.1 簡化模式介紹
下圖是簡化模式交互圖:
(1)資源擁有者打開客戶端,客戶端要求資源擁有者給予授權,它將瀏覽器被重定向到授權服務器,重定向時會附加客戶端的身份信息。如:
http://localhost:53020/uaa/oauth/authorize?client_id=c1&response_type=token&scope=all&redirect_uri=http://www.baidu.com參數描述同授權碼模式 ,注意response_type=token,說明是簡化模式。
(2)瀏覽器出現向授權服務器授權頁面,之后將用戶同意授權。
(3)授權服務器將授權碼將令牌(access_token)以Hash的形式存放在重定向uri的fargment中發送給瀏覽器。
注:fragment 主要是用來標識 URI 所標識資源里的某個資源,在 URI 的末尾通過 (#)作為 fragment 的開頭,其中 # 不屬于 fragment 的值。如https://domain/index#L18這個 URI 中 L18 就是 fragment 的值。大家只需要知道js通過響應瀏覽器地址欄變化的方式能獲取到fragment 就行了。
一般來說,簡化模式用于沒有服務器端的第三方單頁面應用,因為沒有服務器端就無法接收授權碼。
6.2.4.2 測試
瀏覽器訪問認證頁面:
http://localhost:53020/uaa/oauth/authorize?client_id=c1&response_type=token&scope=all&redirect_uri=http://www.baidu.com然后輸入模擬的賬號和密碼點登陸之后進入授權頁面:
確認授權后,瀏覽器會重定向到指定路徑(oauth_client_details表中的web_server_redirect_uri)并以Hash的形式存放在重定向uri的fargment中,如:
http://www.baidu.com/receive#access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZW5hbn...4.2.5.密碼模式
4.2.5.1 授權碼模式介紹
下圖是密碼模式交互圖:
(1)資源擁有者將用戶名、密碼發送給客戶端
(2)客戶端拿著資源擁有者的用戶名、密碼向授權服務器請求令牌(access_token),請求如下:
http://localhost:53020/uaa/oauth/token?client_id=c1&client_secret=secret&grant_type=password&username=shangsan&password=123參數列表如下:
- client_id :客戶端準入標識。
- client_secret :客戶端秘鑰。
- grant_type :授權類型,填寫password表示密碼模式
- username :資源擁有者用戶名。
- password :資源擁有者密碼。
(3)授權服務器將令牌(access_token)發送給client
這種模式十分簡單,但是卻意味著直接將用戶敏感信息泄漏給了client,因此這就說明這種模式只能用于client是我們自己開發的情況下。因此密碼模式一般用于我們自己開發的,第一方原生App或第一方單頁面應用。
4.2.5.2 測試
POST http://localhost:53020/uaa/oauth/token請求參數:
返回結果:
4.2.6.客戶端模式
4.2.6.1 客戶端模式介紹
(1)客戶端向授權服務器發送自己的身份信息,并請求令牌(access_token)
(2)確認客戶端身份無誤后,將令牌(access_token)發送給client,請求如下:
http://localhost:53020/uaa/oauth/token?client_id=c1&client_secret=secret&grant_type=client_credentials參數列表如下:
- client_id :客戶端準入標識。
- client_secret :客戶端秘鑰。
- grant_type :授權類型,填寫client_credentials表示客戶端模式
這種模式是最方便但最不安全的模式。因此這就要求我們對client完全的信任,而client本身也是安全的。因此這種模式一般用來提供給我們完全信任的服務器端服務。比如,合作方系統對接,拉取一組用戶信息。
6.2.6.2 客戶端模式介紹
POST http://localhost:53020/uaa/oauth/token請求參數:
返回結果:
4.2.7.資源服務測試
4.2.7.1 資源服務器配置
@EnableResourceServer 注解到一個 @Configuration 配置類上,并且必須使用 ResourceServerConfigurer 這個配置對象來進行配置(可以選擇繼承自 ResourceServerConfigurerAdapter 然后覆寫其中的方法,參數就是這個
對象的實例),下面是一些可以配置的屬性:
ResourceServerSecurityConfigurer中主要包括:
- tokenServices :ResourceServerTokenServices 類的實例,用來實現令牌服務。
- tokenStore :TokenStore類的實例,指定令牌如何訪問,與tokenServices配置可選
- resourceId :這個資源服務的ID,這個屬性是可選的,但是推薦設置并在授權服務中進行驗證。
- 其他的拓展屬性例如 tokenExtractor 令牌提取器用來提取請求中的令牌。
HttpSecurity配置這個與Spring Security類似:
- 請求匹配器,用來設置需要進行保護的資源路徑,默認的情況下是保護資源服務的全部路徑。
- 通過 http.authorizeRequests()來設置受保護資源的訪問規則
- 其他的自定義權限保護規則通過 HttpSecurity 來進行配置。
@EnableResourceServer 注解自動增加了一個類型為 OAuth2AuthenticationProcessingFilter 的過濾器鏈
編寫ResouceServerConfig:
@Configuration @EnableResourceServer @EnableGlobalMethodSecurity(prePostEnabled = true) public class ResouceServerConfig extends ResourceServerConfigurerAdapter {public static final String RESOURCE_ID = "res1";@Overridepublic void configure(ResourceServerSecurityConfigurer resources) {resources.resourceId(RESOURCE_ID).tokenServices(tokenService()).stateless(true);}@Overridepublic void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/**").access("#oauth2.hasScope('all')").and().csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);} }4.2.7.2 驗證token
ResourceServerTokenServices 是組成授權服務的另一半,如果你的授權服務和資源服務在同一個應用程序上的話,你可以使用 DefaultTokenServices ,這樣的話,你就不用考慮關于實現所有必要的接口的一致性問題。如果你的資源服務器是分離開的,那么你就必須要確保能夠有匹配授權服務提供的 ResourceServerTokenServices,它知道如何對令牌進行解碼。
令牌解析方法: 使用 DefaultTokenServices 在資源服務器本地配置令牌存儲、解碼、解析方式 使用RemoteTokenServices 資源服務器通過 HTTP 請求來解碼令牌,每次都請求授權服務器端點 /oauth/check_token
使用授權服務的 /oauth/check_token 端點你需要在授權服務將這個端點暴露出去,以便資源服務可以進行訪問,這在咱們授權服務配置中已經提到了,下面是一個例子,在這個例子中,我們在授權服務中配置了/oauth/check_token 和 /oauth/token_key 這兩個端點:
@Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {security .tokenKeyAccess("permitAll()")// /oauth/token_key 安全配置 .checkTokenAccess("permitAll()") // /oauth/check_token 安全配置 }在資源 服務配置RemoteTokenServices ,在ResouceServerConfig中配置:
// 資源服務令牌解析服務 @Bean public ResourceServerTokenServices tokenService() {//使用遠程服務請求授權服務器校驗token,必須指定校驗token 的url、client_id,client_secretRemoteTokenServices service=new RemoteTokenServices();service.setCheckTokenEndpointUrl("http://localhost:53020/uaa/oauth/check_token");service.setClientId("c1");service.setClientSecret("secret");return service; }4.2.7.3 編寫資源
在controller包下編寫OrderController,此controller表示訂單資源的訪問類:
@RestController public class OrderController {@GetMapping(value = "/r1")@PreAuthorize("hasAnyAuthority('p1')")public String r1(){return "訪問資源1";} }4.2.7.4 添加安全訪問控制
@Configuration @EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter {//安全攔截機制(最重要)@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().authorizeRequests() // .antMatchers("/r/r1").hasAuthority("p2") // .antMatchers("/r/r2").hasAuthority("p2").antMatchers("/r/**").authenticated()//所有/r/**的請求必須認證通過.anyRequest().permitAll()//除了/r/**,其它的請求可以訪問;} }配置引導類OrderServer
@SpringBootApplication public class OrderServer {public static void main(String[] args) {SpringApplication.run(OrderServer.class, args);} }4.2.7.5 測試
1、申請令牌
這里我們使用密碼方式
2、請求資源
按照oauth2.0協議要求,請求資源需要攜帶token,如下:
token的參數名稱為:Authorization,值為:Bearer token值
如果token錯誤,則授權失敗,如下:
總結
以上是生活随笔為你收集整理的Spring Security OAuth2分布式系统认证解决方案的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《一周学完光线追踪》学习 三 光线相机和
- 下一篇: java信息管理系统总结_java实现科