oauth是一個關于授權(authorization)的開放網絡標準,在全世界得到廣泛應用,目前的版本是2.0版。
本文對oauth 2.0的設計思路和運行流程,做一個簡明通俗的解釋,主要參考材料為rfc 6749。
oauth 簡介
oauth 是由 blaine cook、chris messina、larry halff 及 david recordon 共同發起的,目的在于為 api 訪問授權提供一個安全、開放的標準。
基于 oauth 認證授權具有以下特點:
- 安全。oauth 與別的授權方式不同之處在于:oauth 的授權不會使消費方(consumer)觸及到用戶的帳號信息(如用戶名與密碼),也是是說,消費方無需使用用戶的用戶名與密碼就可以申請獲得該用戶資源的授權。
- 開放。任何消費方都可以使用 oauth 認證服務,任何服務提供方 (service provider) 都可以實現自身的 oauth 認證服務。
- 簡單。不管是消費方還是服務提供方,都很容易于理解與使用。
oauth 的解決方案如下圖所示。
圖 1. oauth solution
如 圖 1 所示 oauth 解決方案中用戶、消費方及其服務提供方之間的三角關系:當用戶需要 consumer 為其提供某種服務時,該服務涉及到需要從服務提供方那里獲取該用戶的保護資源。oauth 保證:只有在用戶顯式授權的情況下(步驟 4),消費方才可以獲取該用戶的資源,并用來服務于該用戶。
從宏觀層次來看,oauth 按以下方式工作:
- 消費方與不同的服務提供方建立了關系。
- 消費方共享一個密碼短語或者是公鑰給服務提供方,服務提供方使用該公鑰來確認消費方的身份。
- 消費方根據服務提供方將用戶重定向到登錄頁面。
- 該用戶登錄后告訴服務提供方該消費方訪問他的保護資源是沒問題的。 前提
閱讀本文之前,你需要了解:
- spring boot
- spring mvc
- spring security
- google 瀏覽器插件postman
pom.xml文件如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
<?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>cn.iigrowing.study.oauth2</groupid> <artifactid>demo01</artifactid> <version> 0.0 . 1 -snapshot</version> <packaging>jar</packaging> <name>my.oauth01</name> <description>demo project for spring boot</description> <parent> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-parent</artifactid> <version> 1.5 . 2 .release</version> <relativepath /> <!-- lookup parent from repository --> </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> <dependencies> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-test</artifactid> <scope>test</scope> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-web</artifactid> </dependency> <dependency> <groupid>org.springframework.security.oauth</groupid> <artifactid>spring-security-oauth2</artifactid> </dependency> </dependencies> <build> <plugins> <plugin> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-maven-plugin</artifactid> </plugin> </plugins> </build> </project> |
本項目需要添加的依賴非常簡單,一共只有兩個,一個是spring web,另一個是spring oauth2。
接下來是本文的核心,一共三個配置類。
securityconfig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
package cn.iigrowing.study.oauth2.demo01; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; import org.springframework.security.authentication.authenticationmanager; import org.springframework.security.config.annotation.authentication.builders.authenticationmanagerbuilder; import org.springframework.security.config.annotation.web.builders.httpsecurity; import org.springframework.security.config.annotation.web.configuration.enablewebsecurity; import org.springframework.security.config.annotation.web.configuration.websecurityconfigureradapter; @configuration @enablewebsecurity public class securityconfig extends websecurityconfigureradapter { @override protected void configure(authenticationmanagerbuilder auth) throws exception { auth.inmemoryauthentication() .withuser( "user" ).password( "123456" ).authorities( "role_user" ); } @override protected void configure(httpsecurity http) throws exception { http.httpbasic() .and().csrf().disable() .authorizerequests() .antmatchers( "/login" ).permitall() .anyrequest().authenticated() .and() .formlogin() .and() .logout().permitall(); } @override @bean public authenticationmanager authenticationmanagerbean() throws exception { return super .authenticationmanagerbean(); } } |
此處主要做了兩件事情:
配置系統用戶,這里使用內存存儲,添加了用戶名為 user
,角色為 user
的用戶
1
2
3
4
5
|
@override protected void configure(authenticationmanagerbuilder auth) throws exception { auth.inmemoryauthentication() .withuser( "user" ).password( "123456" ).authorities( "role_user" ); } |
配置了默認表單登陸以及禁用了 csrf
功能,并開啟了 httpbasic
認證
1
2
3
4
5
6
7
8
9
10
11
12
|
@override protected void configure(httpsecurity http) throws exception { http.httpbasic() .and().csrf().disable() .authorizerequests() .antmatchers( "/login" ).permitall() .anyrequest().authenticated() .and() .formlogin() .and() .logout().permitall(); } |
authorizationserverconfig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
package cn.iigrowing.study.oauth2.demo01; import org.springframework.beans.factory.annotation.autowired; import org.springframework.beans.factory.annotation.qualifier; import org.springframework.context.annotation.configuration; import org.springframework.security.authentication.authenticationmanager; 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; @configuration @enableauthorizationserver public class authorizationserverconfig extends authorizationserverconfigureradapter { @autowired @qualifier ( "authenticationmanagerbean" ) private authenticationmanager authenticationmanager; @override public void configure(clientdetailsserviceconfigurer clients) throws exception { clients.inmemory() .withclient( "client" ).secret( "123456" ).scopes( "read" ) .authorizedgranttypes( "authorization_code" ) .redirecturis( "https://www.getpostman.com/oauth2/callback" ); } @override public void configure(authorizationserverendpointsconfigurer endpoints) throws exception { endpoints.authenticationmanager(authenticationmanager); } } |
這個類是oauth2認證的核心配置類,在這個類中,配置了oauth client的信息,這里有幾個地方需要注意:
-
@enableauthorizationserver
這個注解告訴 spring 這個應用是 oauth2 的授權服務器 -
必須配置
authorizedgranttypes
,它代表了oauth client允許認證的類型,其值主要有:
- authorization_code
- password
- client_credentials
- implicit refresh_token
這個配置項接受的類型是個數組,允許配置多個;關于這幾種類型的區別,請查看這里不再贅述
redirecturis
關于這個配置項,是在 oauth2協議中,認證成功后的回調地址,因為稍后我們會使用 postman
作為測試工具,故此處值固定為 https://www.getpostman.com/oauth2/callback
,此值同樣可以配置多個 resourceserverconfig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
package cn.iigrowing.study.oauth2.demo01; import org.springframework.context.annotation.configuration; import org.springframework.security.config.annotation.web.builders.httpsecurity; import org.springframework.security.config.http.sessioncreationpolicy; import org.springframework.security.oauth2.config.annotation.web.configuration.enableresourceserver; import org.springframework.security.oauth2.config.annotation.web.configuration.resourceserverconfigureradapter; import org.springframework.security.oauth2.config.annotation.web.configurers.resourceserversecurityconfigurer; @configuration @enableresourceserver public class resourceserverconfig extends resourceserverconfigureradapter { @override public void configure(resourceserversecurityconfigurer resources) { resources.resourceid(“users-info”); } @override public void configure(httpsecurity http) throws exception { http .sessionmanagement().sessioncreationpolicy(sessioncreationpolicy.stateless) .and() .requestmatchers() .antmatchers(“/users/**”) .and().authorizerequests() .antmatchers(“/users/**”) .authenticated(); } } |
這個類表明了此應用是oauth2 的資源服務器,此處主要指定了受資源服務器保護的資源鏈接,我們將提供以下的資源:
1
2
3
4
5
6
7
8
9
|
@restcontroller @requestmapping ( "users" ) public class usercontroller { @getmapping ( "me" ) public principal me(principal principal) { return principal; } } |
注:
資源服務器可以和授權服務器是同一個,也可以分開部署
最后,我們還需添加 application.yml
, 配置資源服務器的filter的順序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
server: port: 8043 context-path: /uaa logging: level: org.springframework.security: debug spring: application: name: oauth-server security: oauth2: resource: serviceid: ${prefix:}resource # refer to: https: //github.com/spring-projects/spring-boot/wiki/spring-boot-1.5-release-notes#oauth-2-resource-filter filter-order: 3 |
此處的 filter-order
非常重要,因為自spring boot 1.5.* 之后,resource server 的 filter 的順序默認在 basic authentication filter chain
之后,所以如果不配置此項,將會導致使用 access_token
訪問 resource server 的時候返回 401
狀態碼。
好了,所有的開發工作已經完成,無需任何其他配置,現在我們啟動服務器,并通過 postman
訪問 http://localhost:8043/uaa/users/me
因為現在還沒通過認證,所以服務器將返回 401
的狀態碼,并返回以上的錯誤信息。
現在我們使用 postman
來獲取 access_token
,首先選擇 authorization
tab, 然后選擇 type
為 oauth2.0
,最后點擊 get new access token
按鈕:
此時將彈出以下界面:
我們填入對應的信息:
- token name: access_token
- auth url: http://localhost:8043/uaa/oauth/authorize
- access token url: http://localhost:8043/uaa/oauth/token
- client id: client
- client secret: 123456
- grant type: authorization code
此處配置的client相關信息對應了我們在 authorizationserverconfig
里面的配置,之前配置的回調地址也來自于此處的 callback url
。
接下來點擊 request token
按鈕,在彈出的登陸界面中輸入我們之前在 securityconfig
中配置的用戶信息,選擇 approval
并點擊 authorize
按鈕,即可獲取 access_token
:
到這里我們就成功的獲取了 token,此時再次調用 users/me
api,如無意外,將會得到以下的結果:
這個項目實現了以下的功能:
- 使用 jwt token進行認證
- 多resource sever
- 使用數據庫存儲用戶以及oauth client信息
- 提供相關的rest api進行用戶及 client的管理
- 結合了spring cloud
小結
oauth 協議作為一種開放的,基于用戶登錄的授權認證方式,目前互聯網很多 open api 都對 oauth 提供了支持,這包括 google, yahoo,twitter 等。本文以 google 為例子,介紹了 java 桌面程序如何開發 oauth 認證應用。在開發桌面應用訪問 web 資源這樣一類程序時,一般通行的步驟是:使用 oauth 做認證,然后使用獲得的 oauth access token,通過 rest api 訪問用戶在服務提供方的資源。
事實上,目前 oauth 正通過許多實現(包括針對 java、c#、objective-c、perl、php 及 ruby 語言的實現)獲得巨大的動力。大部分實現都由 oauth 項目維護并放在 google 代碼庫 (http://oauth.googlecode.com/svn/) 上。開發者可以利用這些 oauth 類庫編寫自己需要的 oauth 應用。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:http://www.iigrowing.cn/shi_yong_springboot_da_jian_oauth2_0_server.html