1.OAuth2简介
- OAuth 是一个开放标准,该标准允许用户让第三方应用访问该用户在某一网站上存储的私密资源(如头像、照片、视频等),而在这个过程中无须将用户名和密码提供给第三方应用。实现这一功能是通过提供一个令牌(token),而不是用户名和密码来访问他们存放在特定服务提供者的数据。
- 每一个令牌授权一个特定的网站在特定的时段内访问特定的资源。这样,OAuth 让用户可以授权第三方网站灵活地访问存储在另外一些资源服务器的特定信息,而非所有内容。目前主流的 qq,微信等第三方授权登录方式都是基于 OAuth2 实现的。
- OAuth 2 是 OAuth 协议的下一版本,但不向下兼容 OAuth 1.0。OAuth 2 关注客户端开发者的简易性,同时为 Web 应用、桌面应用、移动设备、起居室设备提供专门的认证流程。
- 传统的 Web 开发登录认证一般都是基于 Session 的,但是在前后端分离的架构中继续使用 Session 会有许多不便,因为移动端(Android、iOS、微信小程序等)要么不支持Cookie(微信小程序),要么使用非常不便,对于这些问题,使用 OAuth 2 认证都能解决
2.OAuth2的四大角色
在Oauth2中,总共有如下四种角色:
名称 | 英文名称 | 描述 |
---|---|---|
资源拥有者 | Resource Owner | 能够授予对受保护资源的访问权的实体。当资源所有者是一个人时,它就是用户。 |
资源服务器 | Resource Server | 承载受保护资源的服务器,能够使用访问令牌接受和响应受保护资源请求。是一个web站点或者web service API,用户的受保护数据保存于此。 |
客户端 | Client | 代表资源所有者及其授权发出受保护资源请求的应用程序。通常是一个web或者无线应用,它需要访问用户的受保护资源。 |
授权服务器 | Authorization Server | 在成功认证资源所有者并获得授权后,服务器向客户端发出访问令牌。 |
授权服务器可以是与资源服务器相同的服务器或单独的服务器。 单个授权服务器可以发出由多个资源服务器接受的访问令牌。
授权流程图大概如下:
- (A)客户端向资源所有者请求授权。授权请求可以直接对资源所有者(如图所示)进行,或者通过授权服务器作为中介进行间接访问(首选方案)。
- (B)资源所有者允许授权,并返回凭证(如code)。
- (C)客户端通过授权服务器进行身份验证,并提供授权凭证(如code),请求访问令牌(access token)。
- (D)授权服务器对客户端进行身份验证,并验证授权凭证,如果有效,则发出访问令牌。
- (E)客户端向资源服务器请求受保护的资源,并通过提供访问令牌来进行身份验证。
- (F)资源服务器验证访问令牌,如果正确则返回受保护资源。
3.OAuth2 授权流程
从运行流程不难看出,要获取access token必须先得到用户授权(authorzation grant),那么如果获取这么用户授权呢?OAuth 2.0定义了四种类型的授权类型:
- 授权码模式(authorization code)
- 简化模式(implicit)
- 密码模式(resource owner password credentials)
- 客户端模式(client credentials)
3.1 授权码模式(authorization code)
授权码模式是功能最完整、使用最广泛、流程最严密的授权模式。
由于这是一个基于重定向的流,所以客户端必须能够与资源所有者的用户代理(通常是web浏览器)进行交互,并且能够从授权服务器接收传入的请求(通过重定向)。
步骤如下:
- (A)用户访问客户端,后者将前者导向认证服务器。
- (B)用户选择是否给予客户端授权。
- (C)假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码。
- (D)客户端收到授权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
- (E)认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。
下面接着介绍各个步骤所需的参数:
对于步骤A,客户端申请授权请求的URI,包含以下参数:
- response_type 授权类型。必选项,其值固定为code。
- client_id 客户端id。必选项,用于标识授权服务器中已注册的客户端。
- redirect_uri 重定向URI。可选项,如果不填写则使用注册在授权服务器端与client_id对应的redirect_uri。
- scope 申请的权限范围,如read或write。可选项,如果申请的请求访问超出授权服务器定义的可操作范围则会失败。
- state 表示客户端当前状态。可选项,可以指定任意值,授权服务器会原封不动地返回这个值。
C步骤中,服务器回应客户端的URI,包含以下参数:
- code授权码。必选项,授权码必须在颁发后很快过期以减小泄露风险,建议最长时间设为10分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与client id和重定向URI,是一一对应关系。
- state如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。
D步骤中,客户端向认证服务器申请令牌的HTTP请求,包含以下参数:
- grant_type许可类型(授权模式)。必选项,此处固定值为authorization_code。
- code上一步获得的授权码。必选项。
- redirect_uri表示重定向URI。必选项,且必须与A步骤中的该参数值保持一致。
- client_id表示客户端ID,必选项。
在Spring Security Oauth2 中 上面的API 如下所示,这些 API 称为端点:
- 授权端点
/oauth/authorize:授权端点,通过此端点跳转到 授权服务器 进行认证,完成第一个请求。携带如下参数:
参数名称 | 是否必填 | 描述 |
---|---|---|
response_type | 必填 | 必须为 code |
client_id | 必填 | 客户端的ID |
redirect_uri | 可选 | 获取授权码后重定向地址 |
scope | 可选 | 申请的权限范围 |
state | 推荐 | 客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值,推荐。 |
授权成功的情况,会携带以下两个参数重定向到到 redirect_uri 中:
参数名称 | 是否必返回 | 描述 |
---|---|---|
code | 是 | 授权服务器生成的授权代码。授权代码必须在发布后不久过期,以降低泄漏的风险。最大授权代码生命周期为 10 分钟 |
state | 是 | 如果上一步中提供 state 参数,会原封不动地返回这个值。 |
- 令牌端点
/oauth/token:令牌端点,通过上一步获取到的 授权码 验证与生成令牌,完成第二个请求,携带如下参数:
参数名称 | 是否必填 | 描述 |
---|---|---|
grant_type | 必填 | 使用的授权模式,值固定为"authorization_code" |
code | 必填 | 上一步获得的授权码 |
redirect_uri | 必填 | 重定向 URI,必须与上一步中的该参数值保持一致 |
client_id | 必填 | 客户端的 ID |
scope | 推荐 | 授权范围,必须与第一步相同 |
如果访问令牌请求有效且经过授权,授权服务器将发出访问令牌和可选的刷新令牌,可以得到如下响应参数:
参数名称 | 是否必返回 | 描述 |
---|---|---|
access_token | 是 | 授权服务器颁发的访问令牌 |
token_type | 是 | 令牌类型,该值大小写不敏感,可以是 bearer 类型或 mac 类型 |
expires_in | 否(设置过期时间必返回) | 过期时间,单位为秒 |
refresh_token | 否 | 表示更新令牌,用来获取下一次的访问令牌 |
scope | 否 | 权限范围,如果有,则与客户端申请的范围一致 |
3.2 简化模式(Implicit Grant)
简化模式(implicit grant type)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。具体步骤可参阅RFC6749 4.2(https://tools.ietf.org/html/rfc6749#section-4.2 )节。
步骤如下:
- (A)客户端将用户导向认证服务器。
- (B)用户决定是否给于客户端授权。
- (C)假设用户给予授权,认证服务器将用户导向客户端指定的"重定向URI",并在URI的Hash部分包含了访问令牌。
- (D)浏览器向资源服务器发出请求,其中不包括上一步收到的Hash值。
- (E)资源服务器返回一个网页,其中包含的代码可以获取Hash值中的令牌。
- (F)浏览器执行上一步获得的脚本,提取出令牌。
- (G)浏览器将令牌发给客户端。
3.3 密码模式(Resource Owner Password Credentials Grant)
密码模式中,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向"服务商提供商"索要授权。在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品。而认证服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。可参阅RFC6749 4.3(https://tools.ietf.org/html/rfc6749#section-4.3 )节。
步骤如下:
- (A)用户向客户端提供用户名和密码。
- (B)客户端将用户名和密码发给认证服务器,向后者请求令牌。
- (C)认证服务器确认无误后,向客户端提供访问令牌。
该步骤中只设计到一个请求。在Spring Security Oauth2 中,该端点如下:
- 令牌端点
/oauth/token:令牌端点,携带如下参数请求即可:
请求参数 | 是否必填 | 描述 |
---|---|---|
grant_type | 必填 | 使用的密码模式,值固定为 password |
username | 必填 | 用户名 |
password | 必填 | 密码 |
scope | 可选 | 请求权限范围 |
请求响应参考授权码模式。
3.4 客户端模式(Client Credentials Grant)
客户端模式(Client Credentials Grant)指客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,其实不存在授权问题。可参阅RFC6749 4.4(https://tools.ietf.org/html/rfc6749#section-4.4 )节。
步骤如下:
- (A)客户端向认证服务器进行身份认证,并要求一个访问令牌。
- (B)认证服务器确认无误后,向客户端提供访问令牌。
该步骤中只设计到一个请求。在Spring Security Oauth2 中,该端点如下:
- 令牌端点
/oauth/token:令牌端点,携带如下参数请求即可:
请求参数 | 是否必填 | 描述 |
---|---|---|
grant_type | 必填 | 使用的密码模式,值固定为 client_credentials |
scope | 可选 | 请求权限范围 |
请求响应参考授权码模式。
4.搭建演示工程
本案例主要演示密码模式的认证授权方式,后续在Spring Cloud中继承Oauth2再增加其他模式的案例;
4.1 添加依赖
pom.xml如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hxmec</groupId>
<artifactId>spring-security-oauth2-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-security-oauth2-demo</name>
<description>spring security oauth2 demo</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<!-- jdk版本 -->
<java.version>1.8</java.version>
<!-- SpringCloud版本号,官方最新稳定版本 -->
<spring-cloud.version>Hoxton.SR3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</