大家好,我是寶哥! 單點(diǎn)登錄是多域名企業(yè)站點(diǎn)流行的登錄方式。本文以現(xiàn)實(shí)生活場景輔助理解,力爭徹底理清 OAuth2.0 實(shí)現(xiàn)單點(diǎn)登錄的原理流程。同時總結(jié)了權(quán)限控制的實(shí)現(xiàn)方案,及其在微服務(wù)架構(gòu)中的應(yīng)用。
多點(diǎn)登錄 傳統(tǒng)的多點(diǎn)登錄系統(tǒng)中,每個站點(diǎn)都實(shí)現(xiàn)了本站專用的賬號數(shù)據(jù)庫和登錄模塊。各站點(diǎn)的登錄狀態(tài)相互不認(rèn)可,各站點(diǎn)需要逐一手工登錄。如下圖,有兩個術(shù)語含義如下:
認(rèn)證(authentication): 驗(yàn)證用戶的身份; 授權(quán)(authorization): 驗(yàn)證用戶的訪問權(quán)限。 單點(diǎn)登錄
單點(diǎn)登錄,英文是 Single Sign On,縮寫為 SSO。多個站點(diǎn)(192.168.1.20X)共用一臺認(rèn)證授權(quán)服務(wù)器(192.168.1.110,用戶數(shù)據(jù)庫和認(rèn)證授權(quán)模塊共用)。
用戶經(jīng)由其中任何一個站點(diǎn)(比如 192.168.1.201)登錄后,可以免登錄訪問其他所有站點(diǎn)。而且,各站點(diǎn)間可以通過該登錄狀態(tài)直接交互。
OAuth2認(rèn)證授權(quán)的原理流程
生活實(shí)例【★★重點(diǎn)★★】 為了直觀的理解 OAuth2.0 原理流程,我們假設(shè)這樣一個生活場景:
(1)檔案局A( 客戶端 / Client ):以“檔案局ID/密碼”標(biāo)識,是掌握檔案資源的機(jī)構(gòu)。并列還有很多檔案局B/C/…,每個檔案局存儲的檔案內(nèi)容( 資源 / Resource )不一樣,比如政治、經(jīng)濟(jì)、軍事、文化等;
(2)公民張三( 資源所有者 / Resource Owner ):以“用戶名/密碼”標(biāo)識,需要到各個檔案局查檔案;
(3)派出所( 授權(quán)服務(wù)器 / Authentication Server ):可以是單個巨大的派出所,也可以是數(shù)據(jù)共享的派出所集群,掌管的信息、提供的對外接口功能有:
檔案局信息:所有檔案局的“檔案局ID/密碼”,證明檔案局的身份; 公民信息:所有公民的“用戶名/密碼”,能提供張三是張三的用戶身份證明( 認(rèn)證 / Authentication ) 公民對于檔案局的權(quán)限:有張公民和檔案局的權(quán)限的映射表,可查得各公民對各檔案局是否有操作權(quán)限( 授權(quán) / Authorization )。通常,設(shè)計中會增加官職( 角色 / Role )一層,各公民屬于哪個官職(角色),哪個官職(角色)對于特定檔案局有操作權(quán)限。 張三首次訪問檔案局A
張三之前從未到訪檔案局,第一次來檔案局。對照下圖序號理解:
(1)張三來到“檔案局A”的“檔案處”,該處要求實(shí)名登記后才能查詢,被指示到“用戶登記處”辦理( HTTP重定向 );
(2)張三來到“檔案局A”的“用戶登記處”,既不能證明身份( 認(rèn)證 ),又不能證明自己有查檔案A的權(quán)限( 授權(quán) )。張三攜帶檔案局A的標(biāo)識( client-id ),被重定向至“授權(quán)信開具處”;
(3)張三來到“派出所”的“授權(quán)信開具處”,出示檔案局A的標(biāo)識,希望開具授權(quán)信( 授權(quán) )。該處要求首先證明身份( 認(rèn)證 ),被重定向至“用戶身份驗(yàn)證處”;
(4)張三來到“派出所”的“用戶身份驗(yàn)證處”,領(lǐng)取了用戶身份表( 網(wǎng)頁登錄表單 Form );
(5)張三填上自己的用戶名和密碼,交給( 提交 / Submit )“用戶身份驗(yàn)證處”,該處從私用數(shù)據(jù)庫中查得用戶名密碼匹配,確定此人是張三,開具身份證明信,完成 認(rèn)證 。張三帶上身份證明信和檔案局A的標(biāo)識,被重定向至“授權(quán)信開具處”;
(6)張三再次來到“授權(quán)信開具處”,出示身份證明信和檔案局A的標(biāo)識,該處從私用數(shù)據(jù)庫中查得,張三的官職是市長級別(角色),該官職具有檔案局A的查詢權(quán)限,就開具“允許張三查詢檔案局A”的授權(quán)信( 授權(quán)碼 / code ),張三帶上授權(quán)信被重定向至“檔案局”的“用戶登錄處”;
(7)張三到了“檔案局”的“用戶登錄處”,該處私下拿出檔案局A的標(biāo)識( client-id )和密碼,再附上張三出示的授權(quán)信( code ),向“派出所”的“腰牌發(fā)放處”為張三申請的“腰牌”( to ken ),將來張三可以帶著這個腰牌表明身份和權(quán)限。又被重定向到“檔案處”;
(8)張三的會話( Session )已經(jīng)關(guān)聯(lián)上了腰牌( t oken ),可以直接通過“檔案處”查檔案。
張三首次訪問檔案局B 張三已經(jīng)成功訪問了檔案局A,現(xiàn)在他要訪問檔案局B。對照下圖序號理解:
(1)/(2) 同上;
(3)張三已經(jīng)有“身份證明信”,直接在“派出所”的“授權(quán)信開具處”成功開具“訪問檔案局B”的授權(quán)信;
(4)/(5)/(6) 免了;
(7)“檔案局B”的“用戶登記處”完成登記;
(8)“檔案局B”的“檔案處”查得檔案
張三再次訪問檔案局A
張三已經(jīng)成功訪問了檔案局A,現(xiàn)在他要訪問檔案局A。對照下圖序號理解:
(1)直接成功查到了檔案;
(2~8)都免了。
HTTP 重定向原理 HTTP 協(xié)議中,瀏覽器的 REQUEST 發(fā)給服務(wù)器之后,服務(wù)器如果發(fā)現(xiàn)該業(yè)務(wù)不屬于自己管轄,會把你支派到自身服務(wù)器或其他服務(wù)器(host)的某個接口(uri)。
正如我們?nèi)フ块T辦事,每到一個窗口,工作人員會說“你帶上材料A,到本所的X窗口,或者其他Y所的Z窗口”進(jìn)行下一個手續(xù)。
SSO 工作流程 至此,就不難理解 OAuth 2.0 的認(rèn)證/授權(quán)流程,此處不再贅述。請拿下圖對照“2.1 生活實(shí)例”一節(jié)來理解。
OAuth2.0 進(jìn)階 RFC 6749: The OAuth 2.0 Authorization Framework
https://tools./html/rfc6749
RFC 6750: The OAuth 2.0 Authorization Framework: Bearer Token Usage
https://tools./html/rfc6750
幫你深入理解OAuth2.0協(xié)議
https://blog.csdn.net/seccloud/article/details/8192707
根據(jù)官方標(biāo)準(zhǔn),OAuth 2.0 共用四種授權(quán)模式:
Authorization Code: 用在服務(wù)端應(yīng)用之間,這種最復(fù)雜,也是本文采用的模式; Implicit: 用在移動app或者web app(這些app是在用戶的設(shè)備上的,如在手機(jī)上調(diào)起微信來進(jìn)行認(rèn)證授權(quán)) Resource Owner Password Credentials(password): 應(yīng)用直接都是受信任的(都是由一家公司開發(fā)的,本例子使用) Client Credentials: 用在應(yīng)用API訪問。 基于 SpringBoot 實(shí)現(xiàn)認(rèn)證/授權(quán)
官方文檔: Spring Cloud Security
(https:///nVgcBq)
授權(quán)服務(wù)器(Authorization Server) pom.xml
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency>
application.properties
server.port=8110 ## 監(jiān)聽端口
AuthorizationServerApplication.java
@EnableResourceServer // 啟用資源服務(wù)器 public class AuthorizationServerApplication { // ... }
配置授權(quán)服務(wù)的參數(shù)
@Configuration @EnableAuthorizationServer public class Oauth2AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter { @Override public void configure(final ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("webapp" ).secret("secret" ) //客戶端 id/secret .authorizedGrantTypes("authorization code" ) //授權(quán)媽模式 .scopes("user_info" ) .autoApprove(true ) //自動審批 .accessTokenValiditySeconds(3600); //有效期1hour } } @Configuration public class Oauth2WebSecurityConfigurer extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.requestMatchers() .antMatchers("/login" , "/oauth/authorize/oauth/logout" ) .and().authorizeRequests().anyRequest().authenticated() .and().formLogin().permitAll(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser("admin" ).password("admin123" ).roles("ADMIN" ); } }
客戶端(Client, 業(yè)務(wù)網(wǎng)站) pom.xml
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency>
application.properties
server port=8080 security.oauth2.client.client-id=webapp security.oauth2.client.client-secret=secret security.oauth2.client.access-token-uri=http://localhost:8110/oauth/token security.oauth2.client.user-authorization-uri=http://localhost:8110/oauth/authorize security.oauth2.resource.user-info-uri=http://localhost:8110/oauth/user
配置 WEB 安全
@Configuration @EnableOAuth2Sso public class Oauth2WebsecurityConfigurer extends WebSecurityConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http.antMatcher("/**" ).authorizeRequests() .antMatchers("/" , "/login" ).permitAll() .anyRequest().authenticated(); } } @RestController public class Oauth2ClientController { @GetMapping("/" ) public ModelAndView index () { return new ModelAndView("index" ); } @GetMapping("/welcome" ) public ModelAndView welcome () { return new ModelAndView("welcome" ); } }
用戶權(quán)限控制(基于角色) 授權(quán)服務(wù)器中,定義各用戶擁有的角色: user=USER, admin=ADMIN/USER, root=ROOT/ADMIN/USER 業(yè)務(wù)網(wǎng)站中(client),注解標(biāo)明哪些角色可 @RestController public class Oauth2ClientController { @GetMapping("/welcome" ) public ModelAndView welcome () { return new ModelAndView("welcome" ); } @GetMapping("/api/user" ) @PreAuthorize("hasAuthority('USER')" ) public Map<String, Object> apiUser () { } @GetMapping("/api/admin" ) @PreAuthorize("hasAuthority('ADMIN')" ) public Map<String, Object> apiAdmin () { } @GetMapping("/api/root" ) @PreAuthorize("hasAuthority('ROOT')" ) public Map<String, Object> apiRoot () { } }
權(quán)限控制方案 下圖是基本的認(rèn)證/授權(quán)控制方案,主要設(shè)計了認(rèn)證授權(quán)服務(wù)器上相關(guān)數(shù)據(jù)表的基本定義??蓪φ毡疚摹?.1 生活實(shí)例”一節(jié)來理解。
在微服務(wù)架構(gòu)中的應(yīng)用 與常規(guī)服務(wù)架構(gòu)不同,在微服務(wù)架構(gòu)中,Authorization Server/Resource Server 是作為微服務(wù)存在的,用戶的登錄可以通過API網(wǎng)關(guān)一次性完成,無需與無法跳轉(zhuǎn)至內(nèi)網(wǎng)的 Authorization Server 來完成。