OAuth2

摘要:开放授权OAuth):是一个行业的标准授权协议,主要用来授权第三方应用获取有限的权限。重点是 OAuth2 的四种角色和客户端的四种授权模式。

目录

[TOC]

OAuth2

开放授权OAuth):是一个行业的标准授权协议,主要用来授权第三方应用获取有限的权限。

  • 最终目的是:为第三方应用颁发一个有时效性的令牌 Token,使得第三方应用能够(通过该令牌)获取相关的资源,如该用户在某一网站上存储的私密资源(如照片)。
  • OAuth 允许用户提供一个令牌(access token),而不是用户名和密码来访问(存放在特定服务提供者的)数据。
  • 而 OAuth 2.0 是对 OAuth 1.0 的完全重新设计,更快、更容易实现,OAuth 1.0 已经被废弃。

OAuth 2.0 常用场景:

  • 比较常用的场景就是第三方登录
  • 另外,也常见于支付场景(微信支付、支付宝支付)和开发平台(微信开放平台、阿里开放平台等)。

参考:

阮一峰提供了几篇关于 OAuth2.0 非常不错的文章,推荐胖友去从瞅瞅。

同时,本文也会直接引用它的内容,方便胖友统一理解。

角色

在 OAuth2.0 中,有如下角色:

  1. Resource Owner:资源所有者。最终用户,他有访问资源的账号密码
    • 可以简单理解成,她在使用 Client 访问资源。
  2. User Agent:用户代理,Client 客户端。它请求资源服务器时,会带上访问令牌,从而成功访问资源。
    • 可以是浏览器、客户端,也可以是内部服务。第三方登录时的当前 APP
  3. Authorization server认证(授权)服务器,用于认证用户。如果客户端认证通过,则发放访问资源服务器的令牌
    • 第三方登录时的第三方 服务器
  4. Resource server资源服务器,,拥有受保护资源。如果请求包含正确的访问令牌,则可以访问资源。
    • 提供管理后台、客户端 API 的服务,都可以认为是 Resource Server。
    • 与认证服务器,可以是同一台服务器,也可以是不同的。
    • 第三方登录时的第三方 服务器。这个时候的资源,主要指的是三方开放平台的用户资料等。

OAuth 2.0 第三方登录,授权码模式的认证流程:

客户端的四种授权模式

客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。

OAuth 2.0定义了四种授权方式:

  1. 密码模式(resource owner password credentials):用户向客户端(原生 APP)提供自己的用户名和密码。客户端使用这些信息,向授权服务器索要授权、申请令牌(token)
  2. 授权码模式(authorization code):通过客户端的后台服务器(作为代理客户端),与服务提供商的认证服务器进行互动。功能最完整、流程最严密。如常见的第三方平台登录功能。
    • 一般情况下,在有客户端的情况下,我们与第三方平台常常采用这种方式。
  3. 简化模式(implicit):不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了”授权码“这个步骤。
    • 所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证
    • 一般用于网站是纯静态页面。
  4. 客户端模式(client credentials):指客户端以自己的名义,而不是以用户的名义,向授权服务器进行认证。
    • 在这种模式中,用户直接向客户端注册,客户端以自己的名义要求授权服务器提供服务,其实不存在授权问题。
    • 如,对接微信公众号时。后端服务器就扮演“客户端”的角色,与微信公众号的后端服务器进行交互。
  5. refresh_token:?

其中,密码模式授权码模式比较常用。

如何选择:FROM 《深度剖析 OAuth2 和微服务安全架构》

授权类型选择

当然,对于黄框部分,对于笔者还是比较困惑的。笔者认为,第三方的单页应用 SPA ,也是适合采用 Authorization Code Grant 授权模式的。例如,《微信网页授权》

具体而言,网页授权流程分为四步:

  • 1、引导用户进入授权页面同意授权,获取 code
  • 2、通过 code 换取网页授权 access_token(与基础支持中的 access_toke n不同)
  • 3、如果需要,开发者可以刷新网页授权 access_token,避免过期
  • 4、通过网页授权 access_token 和 openid 获取用户基本信息(支持 UnionID 机制)

所以,艿艿猜测,之所以图中画的是 Implicit Grant 的原因是,受 Google 的 《OAuth 2.0 for Client-side Web Applications》 一文中,推荐使用了 Implicit Grant 。

  • 当然,具体使用 Implicit Grant 还是 Authorization Code Grant 授权模式,没有定论。
  • 笔者,偏向于使用 Authorization Code Grant,对于第三方客户端的场景。

密码模式

认证流程:

  • (A)用户向客户端提供用户名和密码
  • (B)客户端将其发给授权服务器,向后者请求令牌
  • (C)授权服务器确认无误后,向客户端提供访问令牌。

密码模式

认证及验证:

img

搭建授权服务器

SecurityConfig

通过 Spring Security 提供认证功能

OAuth2AuthorizationServerConfig

创建 OAuth2AuthorizationServerConfig 配置类,进行授权服务器。

其中,/oauth/check_token 端点对应 CheckTokenEndpoint 类,用于校验访问令牌的有效性。

  • 在客户端访问资源服务器时,会在请求中带上访问令牌
  • 在资源服务器收到客户端的请求时,会使用请求中的访问令牌,找授权服务器确认该访问令牌的有效性。
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
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    /**
     * 用户认证 Manager
     */
    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer.checkTokenAccess("isAuthenticated()");
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory() // <4.1>
                .withClient("clientapp").secret("112233") // <4.2> Client 账号、密码。
                .authorizedGrantTypes("password") // <4.2> 密码模式
                .scopes("read_userinfo", "read_contacts") // <4.2> 可授权的 Scope
//                .and().withClient() // <4.3> 可以继续配置新的 Client
                ;
    }

}
简单测试

执行 AuthorizationServerApplication 启动授权服务器。下面,使用 Postman 模拟一个 Client

Basic Auth

POST 请求 http://localhost:8080/oauth/token 地址,使用密码模式进行授权

 +  进行 Client 认证

密码模式的认证

请求说明:

  • 通过 Basic Auth 的方式,填写 client-id + client-secret 作为用户名与密码,实现 Client 客户端有效性的认证。
  • 请求参数 grant_type"password",表示使用密码模式
  • 请求参数 usernamepassword,表示用户的用户名与密码。

响应说明:

  • 响应字段 access_token访问令牌,后续客户端在访问资源服务器时,通过它作为身份的标识。
  • 响应字段 token_type令牌类型,一般是 bearer 或是 mac 类型。
  • 响应字段 expires_in 为访问令牌的过期时间,单位为秒。
  • 响应字段 scope权限范围

友情提示:/oauth/token 对应 TokenEndpoint 端点,提供 OAuth2.0 的四种授权模式。感兴趣的胖友,可以后续去撸撸。

check token

POST 请求 http://localhost:8080/oauth/check_token 地址,校验访问令牌的有效性。如下图所示:

 +  进行 Client 认证

密码模式的认证

搭建资源服务器

配置文件

创建 application.yml 配置文件,添加 Spring Security OAuth 相关配置。

  • security.oauth2.client 配置项,OAuth2 Client 配置,对应 OAuth2ClientProperties 类。在这个配置项中,我们添加了客户端的 client-idclient-secret

    • 为什么要添加这个配置项呢?因为资源服务器会调用授权服务器的 /oauth/check_token 接口,而考虑到安全性,配置了该接口需要进过客户端认证

    友情提示:这里艿艿偷懒了,其实单独给资源服务器配置一个 Client 的 client-idclient-secret。我们可以把资源服务器理解成授权服务器的一个特殊的客户端

  • security.oauth2.resource 配置项,OAuth2 Resource 配置,对应 ResourceServerProperties 类。

    这里,通过 token-info-uri 配置项,设置使用授权服务器的 /oauth/check_token 接口,校验访问令牌的有效性。

  • security.access-token-uri 配置项,是我们自定义的,设置授权服务器的 oauth/token 接口,获取访问令牌。因为稍后我们将在 LoginController 中,实现一个 /login 登录接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
  port: 9090

security:
  oauth2:
    # OAuth2 Client 配置,对应 OAuth2ClientProperties 类
    client:
      client-id: clientapp
      client-secret: 112233
    # OAuth2 Resource 配置,对应 ResourceServerProperties 类
    resource:
      token-info-uri: http://127.0.0.1:8080/oauth/check_token # 获得 Token 信息的 URL
    # 访问令牌获取 URL,自定义的
    access-token-uri: http://127.0.0.1:8080/oauth/token
OAuth2ResourceServerConfig

创建 OAuth2ResourceServerConfig 类,进行资源服务器。

  • ① 在类上添加 @EnableResourceServer 注解,声明开启 OAuth 资源服务器的功能。
  • #configure(HttpSecurity http) 方法,设置 HTTP 权限。这里,我们设置 /login 接口无需权限访问,其它接口认证后可访问。
    • 这样,客户端在访问资源服务器时,其请求中的访问令牌会被资源服务器调用授权服务器的 /oauth/check_token 接口,进行校验访问令牌的正确性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
@EnableResourceServer
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            // 设置 /login 无需权限访问
            .antMatchers("/login").permitAll()
            // 设置其它请求,需要认证后访问
            .anyRequest().authenticated()
            ;
    }

}

授权码模式

授权码模式的认证流程:

(A)用户打开客户端以后,客户端跳转到到授权服务器,要求用户给予授权。

(B)用户同意给予客户端授权。(授权服务器将跳转到客户端事先指定的”重定向 URI”(Redirection URI),同时附上一个授权码

(C)客户端使用上一步获得的授权,向认证服务器申请令牌。(这一步是在客户端的后台的服务器上完成的,对用户不可见)

(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌

(E)客户端使用令牌,向资源服务器申请获取资源。

(F)资源服务器确认令牌无误,同意向客户端开放资源。

下图是 Slack OAuth 2.0 第三方登录的示意图:

另一种步骤:

  • (A)用户访问客户端,后者将前者跳转到到授权服务器。
  • (B)用户选择是否给予客户端授权。
  • (C)假设用户给予授权,授权服务器将跳转到客户端事先指定的”重定向 URI”(Redirection URI),同时附上一个授权码
  • (D)客户端收到授权码,附上早先的”重定向 URI”,向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
  • (E)认证服务器核对了授权码重定向 URI,确认无误后,向客户端发送访问令牌

授权码模式

简化模式

步骤:

  • (A)用户访问客户端,后者将前者跳转到到授权服务器。
  • (B)用户选择是否给予客户端授权。
  • (C)假设用户给予授权,授权服务器将用户导向客户端指定的”重定向URI”,并在 URI 的 Hash 部分包含了访问令牌
  • (D)浏览器向资源服务器发出请求,其中不包括上一步收到的 Hash 值。
  • (E)资源服务器返回一个网页,(其中包含的代码)可以获取 Hash 值中的令牌
  • (F)浏览器执行上一步获得的脚本,提取出令牌。
  • (G)浏览器将令牌发给客户端

简化模式

客户端模式

步骤:

  • (A)客户端向授权服务器进行身份认证,并要求一个访问令牌
  • (B)授权服务器确认无误后,向客户端提供访问令牌。

客户端模式

合并服务器

在项目比较小时,考虑到节省服务器资源,会考虑将授权服务器和资源服务器合并到一个项目中,避免启动多个 Java 进程。

Refresh Token

在 OAuth2.0 中,一共有两类令牌:

  1. 访问令牌(Access Token)
  2. 刷新令牌(Refresh Token):在访问令牌过期时,可以使用刷新令牌向授权服务器获取一个新的访问令牌

为什么会有刷新令牌呢?

  • 每次请求资源服务器时,都会在请求上带上访问令牌,这样它的泄露风险是相对高的。

  • 因此,出于安全性的考虑,访问令牌的过期时间比较短,刷新令牌的过期时间比较长
  • 这样,如果访问令牌即使被盗用走,那么在一定的时间后,访问令牌也能在较短的时间吼过期。
  • 当然,安全也是相对的,如果使用刷新令牌后,获取到新的访问令牌,访问令牌后续可能被盗用。

常用开放平台的令牌过期时间:

开放平台 Access Token 有效期 Refresh Token 有效期
微信开放平台 2 小时 未知
腾讯开放平台 90 天 未知
小米开放平台 90 天 10 年

删除令牌

在用户登出系统时,会有删除令牌的需求。

  • 虽然说,可以通过客户端本地删除令牌的方式实现。如,清除本地 Cookie、localStorage 的令牌缓存
  • 但是,考虑到真正的彻底的实现删除令牌,必然服务端自身需要删除令牌。

在 Spring Security OAuth2 中,并没有提供内置的接口,所以需要自己去实现。

0%