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