Spring Cloud Security 整合 OAuth 2.0,从原理到实战一次说明白

作者:微信小助手

发布时间:2021-12-03T03:11:20

本篇文章介绍一下OAuth2.0相关的知识点,并且手把手带大家搭建一个认证授权中心资源服务进行OAuth2.0四种授权模式的验证,案例源码详细,一梭子带大家了解清楚。

本篇文章的案例源码项目架构为:Spring Boot + Spring Cloud Alibaba + Spring Security 。

文章目录如下:

为什么需要OAuth2.0?

编码永远都是为了解决生产中的问题,想要理解为什么需要OAuth2,当然要从实际生活出发。

举个例子:小区的业主点了一份外卖,但是小区的门禁系统不给外卖人员进入,此时想要外卖员进入只能业主下来开门或者告知门禁的密码。

密码告知外卖员岂不是每次都能凭密码进入小区了,这明显造成了安全隐患

那么有没有一种方案:既能不泄露密码,也能让外卖小哥进入呢?

于是此时就想到了一个授权机制,分为以下几个步骤:

  1. 门禁系统中新增一个 授权按钮,外卖小哥只需要点击授权按钮呼叫对应业主
  2. 业主收到小哥的呼叫,知道小哥正在要求授权,于是做出了 应答授权
  3. 此时门禁系统弹出一个 密码(类似于 access_token), 有效期30分钟,在30分钟内,小哥可以凭借这个密码进入小区。
  4. 小哥输入密码进入小区

另外这个授权的密码不仅可以通过门禁,还可以通过楼下的门禁,这就非常类似于网关微服务了。

令牌和密码的区别?

上述例子中令牌和密码的作用是一样的,都可以进入小区,但是存在以下几点差异:

  1. 时效不同:令牌一般都是存在过期时间的,比如30分钟后失效,这个是无法修改的,除非重新申请授权;而密码一般都是永久的,除非主人去修改
  2. 权限不同:令牌的权限是有限的,比如上述例子中,小哥获取了令牌,能够打开小区的门禁、业主所在的楼下门禁,但是可能无法打开其它幢的门禁;
  3. 令牌可以撤销:业主可以撤销这个令牌的授权,一旦撤销了,这个令牌也就失效了,无法使用;但是密码一般不允许撤销。

什么是OAuth2?

OAuth 是一个开放标准,该标准允许用户让第三方应用访问该用户在某一网站上存储的私密资源(如头像、照片、视频等),而在这个过程中无需将用户名密码提供给第三方应用。实现这一功能是通过提供一个令牌(token),而不是用户名和密码来访问他们存放在特定服务提供者的数据。

采用令牌(token)的方式可以让用户灵活的对第三方应用授权或者收回权限。

OAuth2 是 OAuth 协议的下一版本,但不向下兼容 OAuth 1.0

传统的 Web 开发登录认证一般都是基于 session 的,但是在前后端分离的架构中继续使用 session 就会有许多不便,因为移动端(Android、iOS、微信小程序等)要么不支持 cookie(微信小程序),要么使用非常不便,对于这些问题,使用 OAuth2 认证都能解决。

对于大家而言,我们在互联网应用中最常见的 OAuth2 应该就是各种第三方登录了,例如 QQ 授权登录微信授权登录微博授权登录GitHub 授权登录等等。

OAuth2.0的四种模式?

OAuth2.0协议一共支持 4 种不同的授权模式:

  1. 授权码模式:常见的第三方平台登录功能基本都是使用这种模式。
  2. 简化模式:简化模式是不需要客户端服务器参与,直接在浏览器中向授权服务器申请令牌(token),一般如果网站是纯静态页面则可以采用这种方式。
  3. 密码模式:密码模式是用户把用户名密码直接告诉客户端,客户端使用说这些信息向授权服务器申请令牌(token)。这需要用户对客户端高度信任,例如客户端应用和服务提供商就是同一家公司,自己做前后端分离登录就可以采用这种模式。
  4. 客户端模式:客户端模式是指客户端使用自己的名义而不是用户的名义向服务提供者申请授权,严格来说,客户端模式并不能算作 OAuth 协议要解决的问题的一种解决方案,但是,对于开发者而言,在一些前后端分离应用或者为移动端提供的认证授权服务器上使用这种模式还是非常方便的。

1、授权码模式

这种方式是最常用的流程,安全性也最高,它适用于那些有后端的 Web 应用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。

令牌获取的流程如下:

授权码模式

上图中涉及到两个角色,分别是客户端、认证中心,客户端负责拿令牌,认证中心负责发放令牌。

但是不是所有客户端都有权限请求令牌的,需要事先在认证中心申请,比如微信并不是所有网站都能直接接入,而是要去微信后台开通这个权限。

至少要提前向认证中心申请的几个参数如下:

  1. client_id:客户端唯一id,认证中心颁发的唯一标识
  2. client_secret:客户端的秘钥,相当于密码
  3. scope:客户端的权限
  4. redirect_uri:授权码模式使用的跳转uri,需要事先告知认证中心。

1、请求授权码

客户端需要向认证中心拿到授权码,比如第三方登录使用微信,扫一扫登录那一步就是向微信的认证中心获取授权码。

请求的url如下:

/oauth/authorize?client_id=&response_type=code&scope=&redirect_uri=

上述这个url中携带的几个参数如下:

  • client_id:客户端的id,这个由认证中心分配,并不是所有的客户端都能随意接入认证中心
  • response_type:固定值为 code,表示要求返回授权码。
  • scope:表示要求的授权范围,客户端的权限
  • redirect_uri:跳转的uri,认证中心同意或者拒绝授权跳转的地址,如果同意会在uri后面携带一个 code=xxx,这就是授权码

2、返回授权码

第1步请求之后,认证中心会要求登录、是否同意授权,用户同意授权之后直接跳转到redirect_uri(这个需要事先在认证中心申请配置),授权码会携带在这个地址后面,如下:

http://xxxx?code=NMoj5y

上述链接中的NMoj5y就是授权码了。

3、请求令牌

客户端拿到授权码之后,直接携带授权码发送请求给认证中心获取令牌,请求的url如下:

/oauth/token?
 client_id=&
 client_secret=&
 grant_type=authorization_code&
 code=NMoj5y&
 redirect_uri=

相同的参数同上,不同参数解析如下:

  • grant_type:授权类型,授权码固定的值为 authorization_code
  • code:这个就是上一步获取的授权码

4、返回令牌

认证中心收到令牌请求之后,通过之后,会返回一段JSON数据,其中包含了令牌access_token,如下:

{    
  "access_token":"ACCESS_TOKEN",
  "token_type":"bearer",
  "expires_in":2592000,
  "refresh_token":"REFRESH_TOKEN",
  "scope":"read",
  "uid":100101
}

access_token则是颁发的令牌,refresh_token是刷新令牌,一旦令牌失效则携带这个令牌进行刷新。

2、简化模式

这种模式不常用,主要针对那些无后台的系统,直接通过web跳转授权,流程如下图:

简化模式

这种方式把令牌直接传给前端,是很不安全的。因此,只能用于一些安全要求不高的场景,并且令牌的有效期必须非常短,通常就是会话期间(session)有效,浏览器关掉,令牌就失效了。

1、请求令牌

客户端直接请求令牌,请求的url如下:

/oauth/authorize?
  response_type=token&
  client_id=CLIENT_ID&
  redirect_uri=CALLBACK_URL&
  scope=

这个url正是授权码模式中获取授权码的url,各个参数解析如下:

  • client_id:客户端的唯一Id
  • response_type:简化模式的固定值为 token
  • scope:客户端的权限
  • redirect_uri:跳转的uri,这里后面携带的直接是 令牌,不是授权码了。

2、返回令牌

认证中心认证通过后,会跳转到redirect_uri,并且后面携带着令牌,链接如下:

https://xxxx#token=NPmdj5

#token=NPmdj5这一段后面携带的就是认证中心携带的,令牌为NPmdj5

3、密码模式

密码模式也很简单,直接通过用户名密码获取令牌,流程如下:

密码模式

1、请求令牌

认证中心要求客户端输入用户名、密码,认证成功则颁发令牌,请求的url如下:

/oauth/token?
  grant_type=password&
  username=&
  password=&
  client_id=&
  client_secret=

参数解析如下:

  • grant_type:授权类型,密码模式固定值为password
  • username:用户名
  • password:密码
  • client_id:客户端id
  • client_secret:客户端的秘钥

2、返回令牌

上述认证通过,直接返回JSON数据,不需要跳转,如下:

{    
  "access_token":"ACCESS_TOKEN",
  "token_type":"bearer",
  "expires_in":2592000,
  "refresh_token":"REFRESH_TOKEN",
  "scope":"read",
  "uid":100101
}

access_token则是颁发的令牌,refresh_token是刷新令牌,一旦令牌失效则携带这个令牌进行刷新。

4、客户端模式

适用于没有前端的命令行应用,即在命令行下请求令牌。

这种方式给出的令牌,是针对第三方应用的,而不是针对用户的,即有可能多个用户共享同一个令牌。

流程如下:

客户端模式

1、请求令牌

请求的url为如下:

/oauth/token?
grant_type=client_credentials&
client_id=&
client_secret=

参数解析如下:

  • grant_type:授权类型,客户端模式固定值为 client_credentials
  • client_id:客户端id
  • client_secret:客户端秘钥

2、返回令牌

认证成功后直接返回令牌,格式为JSON数据,如下:

{
    "access_token""ACCESS_TOKEN",
    "token_type""bearer",
    "expires_in"7200,
    "scope""all"
}

OAuth2.0的认证中心搭建

为了方便测试OAuth2的四种授权模式,这里为了方便测试,简单搭建一个认证中心,后续会逐渐完善。

1、案例架构

陈某使用的是Spring Boot + Spring Cloud Alibaba 作为基础搭建,新建一个oauth2-auth-server-in-memory模块作为认证中心,目录如下:

案例源码已经上传GitHub,关注公众号:码猿技术专栏,回复关键词 9529 获取。

2、添加依赖

Spring Boot 和 Spring Cloud 的相关依赖这里陈某就不再说了,直接上Spring Security和OAuth2的依赖,如下:

<!--spring security的依赖-->
<dependency>
   <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!--OAuth2的依赖-->
<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

3、Spring Security安全配置

这里主要涉及到Spring Security的配置。

SecurityConfig这个配置类中主要设置有4块内容,如下:

1、加密方式

采用BCryptPasswordEncoder加密,如下:

2、配置用户

这里为了方便测试,直接将用户信息存储在内存中,后续完善,代码如下:

上述代码配置了两个用户,如下:

  • 用户名admin,密码123,角色admin
  • 用户名user,密码123,角色user

3、注入认证�