OAuth2.0 原理流程及其單點登入和許可權控制
單點登入是多域名企業站點流行的登入方式。本文以現實生活場景輔助理解,力爭徹底理清 OAuth2.0 實現單點登入的原理流程。同時總結了許可權控制的實現方案,及其在微服務架構中的應用。
作者:王克鋒
出處:https://kefeng.wang/2018/04/06/oauth2-sso/
版權:自由轉載-非商用-非衍生-保持署名,轉載請標明作者和出處。
1 什麼是單點登入
1.1 多點登入
傳統的多點登入系統中,每個站點都實現了本站專用的帳號資料庫和登入模組。各站點的登入狀態相互不認可,各站點需要逐一手工登入。如下圖,有兩個術語含義如下:
- 認證(authentication): 驗證使用者的身份;
- 授權(authorization): 驗證使用者的訪問許可權。
1.2 單點登入
單點登入,英文是 Single Sign On,縮寫為 SSO。
多個站點(192.168.1.20X)共用一臺認證授權伺服器(192.168.1.110,使用者資料庫和認證授權模組共用)。使用者經由其中任何一個站點(比如 192.168.1.201)登入後,可以免登入訪問其他所有站點。而且,各站點間可以通過該登入狀態直接互動。
2 OAuth2 認證授權的原理流程
2.1 生活例項【★★重點★★】
為了直觀的理解 OAuth2.0 原理流程,我們假設這樣一個生活場景:
(1)檔案局A(客戶端 / Client
資源 / Resource
)不一樣,比如政治、經濟、軍事、文化等; (2)公民張三(
資源所有者 / Resource Owner
):以“使用者名稱/密碼”標識,需要到各個檔案局查檔案; (3)派出所(
授權伺服器 / Authentication Server
):可以是單個巨大的派出所,也可以是資料共享的派出所叢集,掌管的資訊、提供的對外介面功能有:
- 檔案局資訊:所有檔案局的“檔案局ID/密碼”,證明檔案局的身份;
- 公民資訊:所有公民的“使用者名稱/密碼”,能提供張三是張三的使用者身份證明(
認證 / Authentication
- 公民對於檔案局的許可權:有張公民和檔案局的許可權的對映表,可查得各公民對各檔案局是否有操作許可權(
授權 / Authorization
)。通常,設計中會增加官職(角色 / Role
)一層,各公民屬於哪個官職(角色),哪個官職(角色)對於特定檔案局有操作許可權。
2.1.1 張三首次訪問檔案局A
張三之前從未到訪檔案局,第一次來檔案局。對照下圖序號理解:
(1)張三來到“檔案局A”的“檔案處”,該處要求實名登記後才能查詢,被指示到“使用者登記處”辦理(HTTP重定向);
(2)張三來到“檔案局A”的“使用者登記處”,既不能證明身份(認證
),又不能證明自己有查檔案A的許可權(授權
)。張三攜帶檔案局A的標識(client-id
),被重定向至“授權信開具處”;
(3)張三來到“派出所”的“授權信開具處”,出示檔案局A的標識,希望開具授權信(授權
)。該處要求首先證明身份(認證
),被重定向至“使用者身份驗證處”;
(4)張三來到“派出所”的“使用者身份驗證處”,領取了使用者身份表(網頁登入表單 Form
);
(5)張三填上自己的使用者名稱和密碼,交給(提交 / Submit
)“使用者身份驗證處”,該處從私用資料庫中查得使用者名稱密碼匹配,確定此人是張三,開具身份證明信,完成認證
。張三帶上身份證明信和檔案局A的標識,被重定向至“授權信開具處”;
(6)張三再次來到“授權信開具處”,出示身份證明信和檔案局A的標識,該處從私用資料庫中查得,張三的官職是市長級別(角色),該官職具有檔案局A的查詢許可權,就開具“允許張三查詢檔案局A”的授權信(授權碼 / code
),張三帶上授權信被重定向至“檔案局”的“使用者登入處”;
(7)張三到了“檔案局”的“使用者登入處”,該處私下拿出檔案局A的標識(client-id
)和密碼,再附上張三出示的授權信(code
),向“派出所”的“腰牌發放處”為張三申請的“腰牌”(token
),將來張三可以帶著這個腰牌表明身份和許可權。又被重定向到“檔案處”;
(8)張三的會話(Session)已經關聯上了腰牌(token),可以直接通過“檔案處”查檔案。
2.1.2 張三首次訪問檔案局B
張三已經成功訪問了檔案局A,現在他要訪問檔案局B。對照下圖序號理解:
(1)/(2) 同上;
(3)張三已經有“身份證明信”,直接在“派出所”的“授權信開具處”成功開具“訪問檔案局B”的授權信;
(4)/(5)/(6) 免了;
(7)“檔案局B”的“使用者登記處”完成登記;
(8)“檔案局B”的“檔案處”查得檔案。
2.1.3 張三再次訪問檔案局A
張三已經成功訪問了檔案局A,現在他要訪問檔案局A。對照下圖序號理解:
(1)直接成功查到了檔案;
(2~8)都免了。
2.2 HTTP 重定向原理
HTTP 協議中,瀏覽器的 REQUEST 發給伺服器之後,伺服器如果發現該業務不屬於自己管轄,會把你支派到自身伺服器或其他伺服器(host)的某個介面(uri)。正如我們去政府部門辦事,每到一個視窗,工作人員會說“你帶上材料A,到本所的X視窗,或者其他Y所的Z視窗”進行下一個手續。
2.3 SSO 工作流程
至此,就不難理解 OAuth 2.0 的認證/授權流程,此處不再贅述。請拿下圖對照“2.1 生活例項”一節來理解。
2.4 OAuth2.0 進階
根據官方標準,OAuth 2.0 共用四種授權模式:
- Authorization Code: 用在服務端應用之間,這種最複雜,也是本文采用的模式;
- Implicit: 用在移動app或者web app(這些app是在使用者的裝置上的,如在手機上調起微信來進行認證授權)
- Resource Owner Password Credentials(password): 應用直接都是受信任的(都是由一家公司開發的,本例子使用)
- Client Credentials: 用在應用API訪問。
3 基於 SpringBoot 實現認證/授權
(1) pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
(2) application.properties
server.port=8110 ## 監聽埠
(3) AuthorizationServerApplication.java
@EnableResourceServer // 啟用資源伺服器
public class AuthorizationServerApplication {
// ...
}
(4) 配置授權服務的引數
@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") //授權媽模式
.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");
}
}
3.2 客戶端(Client, 業務網站)
(1) pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
(2) 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
(3) 配置 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");
}
}
3.3 使用者許可權控制(基於角色)
- 授權伺服器中,定義各使用者擁有的角色: user=USER, admin=ADMIN/USER, root=ROOT/ADMIN/USER
- 業務網站中(client),註解標明哪些角色可
@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() {
}
}
4 綜合運用
4.1 許可權控制方案
下圖是基本的認證/授權控制方案,主要設計了認證授權伺服器上相關資料表的基本定義。可對照本文“2.1 生活例項”一節來理解。
4.2 在微服務架構中的應用
與常規服務架構不同,在微服務架構中,Authorization Server/Resource Server 是作為微服務存在的,使用者的登入可以通過API閘道器一次性完成,無需與無法跳轉至內網的 Authorization Server 來完成。