0


Oauth2.0四种授权模式适用场景和授权流程介绍以及个人的一些思考

Oauth

协议为用户资源的授权提供了一个安全的、开放而又简易的标准,先前曾经了解过在

spring-security-oauth2

Oauth

四种模式的实现,也通过

Shiro

实现了

Oauth

的授权流程。

目前

spring-security-oauth2

已经被逐步废弃,

Spring

也提供了新的框架

spring-authorization-server

,整个框架基于

Oauth 2.1

开发。目前重新整理项目代码,借此机会详细梳理一遍

Oauth2.0

授权模式的适用场景和授权流程,后续用于和

2.1

对比参照。

一、四种授权模式

1.1 客户端凭证模式

该模式针对客户端而言,对用户是透明的,不需要用户参与,非用户层面授权。客户端向授权服务器发送自己的的

client_id

client_secrect

请求

access_token

,用户中心仅校验客户端应用身份 。客户端通过授权后可以获得授权范围内所有用户的信息,对客户端应用需要极高的信任。

模式特点: 针对客户端层面进行授权,而非对单独用户进行授权的场景,用户中心仅校验客户端应用的身份。

适用场景: 适用于的自家产品、微服务中,需要从接口层面发起请求的场景。

当前模式仅生成

access_token

,不生成

refresh_token

1.2 密码模式

用户直接提供用户名与密码给客户端应用,客户端使用用户的账号和密码、自己的

client_id

client_secrect

向授权服务器请求

token 

。用户中心校验用户和客户端应用身份,响应

access_token

refresh_token

模式特点: 用户的账号和密码直接暴露给客户端,安全性低。

适用场景: 适用于自家的产品、微服务中,需要从用户层面发起请求的场景。

该模式已经被

Oauth2.1

废弃,直接将用户的账号和密码明文交给客户端应用是一个传统的方案,其本身没有校验意义,需要逐步过渡到通过

token

凭证这种授权方式。

换而言之,客户端都有密码了,通过

Oauth2.0

流程要一个临时的

access_token

干嘛?

1.3 授权码模式

客户端应用引导用户携带着

client_id

redirect_url

前往认证服务器认证,认证通过后认证服务会附带上

code

参数重定向到

redirect_url

地址(客户端应用提供的接收授权码的地址)。客户端应用的服务端携带自己的

client_id

client_secrect

code

请求认证服务器获取

access_token

refresh_token

返回到用户。

模式特点: 四种模式中最安全、最常见的一种模式。

适用场景: 适用于有服务端服务的客户端应用。

网上说,这种方式避免了

access_token

直接在公网传输,黑客截获到

code

也无法获得最终的

access_token

,所以这种方案非常安全。

不是非常认同,如果

code

会被截获,那么用户登录时的

token

是否也可以直接被截获到?

Oauth

token

是否在公网在公网上传输已经不重要了,因为用户登录的

token

已经在公网上传输了。

个人观点认为,安全主要体现在这种模式同时校验了用户和客户端应用的

client_secrect

,与密码模式不同,当前模式用户和客户端应用双方都不知道对方的密码明文。

1.4 隐式授权(简易模式)

客户端应用引导用户携带

client_id

前往授权服务器认证,认证通过后认证服务器直接返回

access_token

模式特点: 不需要与客户端应用的服务端进行交互,没有校验

client_secrect

适用场景: 适用于仅有前端页面,没有后端服务的客户端应用。

通过

#

锚点链接的方式返回

access_token

,避免

token

被携带传输到

web

前端文件托管的服务器上。

该模式已经被

Oauth2.1

废弃,这种方式容易泄露

token

,不安全。

二、授权流程

2.1 客户端凭证模式

授权流程:

客户端启动时向授权服务器的

POST /oauth/token

接口发起获取

access_token

的请求,在

access_token

即将过期时再次请求重新获取

access_token


参数名必填说明grant_type是客户端凭证模式填

client_credentials

client_id是客户端IDclient_secret是客户端秘钥scopes是指定授权范围,多个权限用

,

分隔,默认为客户端可申请的所有权限
请求示例:

通过客户端应用的 ID 和 Secret 获取

access_token

,该

access_token

具有对所有用户的权限:

curl --location --request POST 'http://127.0.0.1:4540/oauth/token?grant_type=client_credentials&client_id=gongyi&client_secret=32c21505c0d0422c99fd158d0eaa5880&scopes=USER_INFO,GET_SECURITY'>>> 获取token
{"error": false,
    "code":200,
    "data":{"scopes":["USER_INFO",
            "GET_SECURITY"],
        "access_token":"59f117422bf945d5a021e4911312a602",
        "expires_in":43200}}

2.2 密码模式

授权流程:

  1. 客户端提供一个登录接口,直接接收用户的登录账号和密码。
  2. 客户端应用的服务端接收到用户的账号和密码,向授权服务器的 POST /oauth/token 接口发起获取 access_tokenrefresh_token 的请求,并传入如下几个参数:参数名必填说明grant_type是密码模式填 passwordclient_id是客户端IDclient_secret是客户端秘钥username是用户登录账号password是用户登录密码scopes是指定授权范围,多个权限用 , 分隔,默认为客户端可申请的所有权限授权服务器将 access_tokenrefresh_token 响应给客户端应用的服务端。

请求示例:

通过用户的账号密码和客户端应用的 ID 和 Secret 获取

token

curl --location --request POST 'http://127.0.0.1:4540/oauth/token?grant_type=password&username=12312312312&password=123123&client_id=gongyi&client_secret=32c21505c0d0422c99fd158d0eaa5880&scopes=USER_INFO,GET_SECURITY'>>> 获取token
{"error": false,
    "code":200,
    "data":{"scopes":["USER_INFO",
            "GET_SECURITY"],
        "access_token":"4df53c0d1a4446e28fe55e281b5b6693",
        "expires_in":43200,
        "refresh_token":"3f447d939fcc41db907551052a96950f"}}

2.3 授权码模式

授权流程:

  1. 打开授权页面 GET /oauth/authorize,并传入如下几个参数:参数名必填说明response_type是授权码模式填 codeclient_id是客户端IDredirect_uri是授权通过后重定向跳转的URLscopes否指定授权范围,多个权限用 , 分隔,默认为客户端可申请的所有权限state否请求状态,用于防重放
  2. 用户进行登录 POST /api/public/user/login,认证用户身份。
  3. (用户无须操作,登录即授权)前端接受到登录成功的响应,自动请求授权接口 POST /oauth/authorize,并传入如下几个参数:> 不需要再传 > > response_type> > 和 > > client_id> > 这些信息了,这些信息已经通过 > > session> > 关联。参数名必填说明user_oauth_approval是用户是否通过授权填 truescope.{权限名}是填 true 为指定权限通过授权请求通过后,将携带生成的 code 和打开授权界面时传送的 state 参数,跳转到 redirect_uri 地址。
  4. 客户端应用的服务端接收到请求,向授权服务器的 POST /oauth/token 接口发起请求获取 access_tokenrefresh_token 的请求,并传入如下几个参数:参数名必填说明grant_type是授权码模式填 authorization_codecode是授权码client_id是客户端IDclient_secret是客户端秘钥最后授权服务器将 access_tokenrefresh_token 响应给客户端应用的服务端。

请求示例:

  1. 打开授权界面curl --location --request GET 'http://127.0.0.1:4540/oauth/authorize?response_type=code&client_id=gongyi&redirect_uri=https://blog.nineya.com/&scopes=USER_INFO,GET_SECURITY&state=123'>>> 授权界面html
  2. 发起获取授权码的请求curl --location --request POST 'http://127.0.0.1:4540/oauth/authorize?user_oauth_approval=true&scope.USER_INFO=true'\--header 'User-Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjE2NzE0NTcxNjg4OTU3MzMsImlzcyI6IlVTRVIiLCJpYXQiOjE2NzE1NDMwMzd9.b4q7qn0t_LOX84P5Huf9sV1XFNLyIp972hdL7ng4m4o'>>> 得到的授权码为x24lgN{"error": false, "code":200, "data":"https://blog.nineya.com/?code=x24lgN&state=123"}
  3. 获取 access_tokenrefresh_token``````curl --location --request POST 'http://127.0.0.1:4540/oauth/token?grant_type=authorization_code&code=x24lgN&client_id=gongyi&client_secret=32c21505c0d0422c99fd158d0eaa5880'>>> 获取token{"error": false, "code":200, "data":{"scopes":["USER_INFO"], "access_token":"b8ee3da722794fb0b1c9f236de7f33a9", "expires_in":43200, "refresh_token":"f676fdd4bbe34e2d8ac195a00b8f3fa9"}}

2.4 隐式授权(简易模式)

授权流程:

  1. 打开授权页面 GET /oauth/authorize,并传入如下几个参数:参数名必填说明response_type是token 模式填 tokenclient_id是客户端IDredirect_uri是授权通过后重定向跳转的URLscopes否指定授权范围,多个权限用 , 分隔,默认为客户端可申请的所有权限state否请求状态,用于防重放
  2. 用户进行登录 POST /api/public/user/login,认证用户身份。
  3. (用户无须操作,登录即授权)前端接受到登录成功的响应,自动请求授权接口 POST /oauth/authorize,并传入如下几个参数:> 不需要再传 > > response_type> > 和 > > client_id> > 这些信息了,这些信息已经通过 > > session> > 关联。参数名必填说明state否请求状态,用于防重放access_token是获取的 tokenexpires_in是token 失效时间scopes是获取的权限列表请求通过后,将携带生成的 token 相关信息作为锚点参数(不生成 refresh_token),将打开授权界面时传送的 state 作为参数,跳转到 redirect_uri 地址。

请求示例:

  1. 打开授权界面curl --location --request GET 'http://127.0.0.1:4540/oauth/authorize?response_type=code&client_id=gongyi&redirect_uri=https://blog.nineya.com/&scopes=USER_INFO,GET_SECURITY&state=123'>>> 授权界面html
  2. 获取 access_token``````curl --location --request POST 'http://127.0.0.1:4540/oauth/authorize?user_oauth_approval=true&scope.USER_INFO=true'\--header 'User-Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjE2NzE0NTcxNjg4OTU3MzMsImlzcyI6IlVTRVIiLCJpYXQiOjE2NzE1NDMwMzd9.b4q7qn0t_LOX84P5Huf9sV1XFNLyIp972hdL7ng4m4o'>>> 通过锚点参数响应access_token{"error": false, "code":200, "data":"https://blog.nineya.com/?state=123#access_token=6fea088dfbb54c1da370ebe6b8e6ccf7&expires_in=43200&scopes=USER_INFO"}

2.5 刷新 token

access_token

即将过期时,调用

POST /oauth/token

,并传入如下几个参数进行

access_token

刷新。
参数名必填说明grant_type是刷新

token

refresh_token

client_id是客户端IDclient_secret是客户端秘钥refresh_token否refresh_token
如果刷新成功,则响应新的

access_token

refresh_token

,原

access_token

refresh_token

失效。

2.6 校验 token

调用

GET/oauth/check_token

,并通过

token

参数传入

access_token

进行校验,校验通过返回用户id、客户端 ID 和授权范围信息。

curl --location --request GET 'http://127.0.0.1:4540/oauth/check_token?token=70b5e4c4084843f2babe1c3146b1f088'>>>{"error": false,
    "code":200,
    "data":{"uid": -1,
        "clientId":"gongyi",
        "scopes":["USER_INFO",
            "GET_SECURITY"]}}

三、Oauth授权的思考

本文的四种授权模式的实现基本上参考于

spring-cloud-starter-oauth2:2.2.4

,但是一些授权细节上有调整,具体如下:

  1. 允许用户未登录就打开授权界面,在授权界面中可一键登录并授权(参考的 QQ 的授权界面);
  2. 允许设置必选权限、可选权限(也是参考的QQ的授权界面);
  3. 将获取授权码和 token 时的跳转改为响应跳转 url,在 JavaScript 中进行跳转;
  4. 也许还有其他一些细微的调整?忘了。

3.1 凭证模式与密码模式的意义

目前,在使用中还未实际用到客户端凭证模式和密码模式,仅仅是一个思考。

对于内部产品:

现在流行的是微服务架构,我们将产品都集成在了同一个微服务集群中,这样做方便管理,也方便通过

feign

进行服务间互访。

微服务间调用单独的开放一套接口,这些接口对公网的访问屏蔽,对微服务的调用请求没有进行任何权限校验。因为已经集成在同一个微服务集群上的应用之间本身就具有极高的信任程度,一般还是在同一个局域网之中,所以通过客户端凭证的方式进行校验失去了意义。

对于不可信的产品:

外部的可行度较低的服务通过凭证模式接入是一个比较个人推崇的方案,客户端凭证再辅加网络白名单策略或代理策略个人认为完全能保证通信的安全。但是这有一个前提,就是该应用允许具有对该接口完全的访问权限,否则就还需要在接口中额外的开发对客户端应用的身份和访问权限进行校验的逻辑。而密码模式,让客户端应用直接存储用户的账户和密码明文,这种方式是不建议的。

在授权框架层面上,是否能有一种只开放指定数据访问权限,且不需要用户显式操作登录的方案?

3.2 用户授权的方式

除了客户端凭证授权方式,另外三种授权方式用户都参与到了授权流程中。

spring-security-oauth2

的实现上看,其中授权码模式和隐式授权模式都和应用前端和

session

具有强关联关系,主要体现在:

  1. 通过 session 判断当前用户是否打开过授权界面,如果未打开过则不允许访问授权接口;
  2. 获取授权码或 token 后使用了链接跳转和重定向功能。

这样会存在一些问题,

session

的方案不适合微服务的架构,除非只部署一个

Oauth

认证服务器,授权流程依赖于那个前端,对于

App

、小程序或者

Vue

单页面应用这类场景,需要再进行适配。

3.3 关于 JWT

Oauth

中,

token

是由授权服务器颁发,访问资源服务器的数据的模式,资源服务器需要前往授权服务器校验

token

的有效性,也就是需要调用

GET/oauth/check_token

接口。

JWT

是一种将用户可公开的部分数据存放在

token

中的方案,对用户数据进行签名,将签名算法、用户数据和签名后的密文共同组成

token

token

中的用户数据明文可读。

如果采用

JWT

来做

token

,那么资源服务器无须前往授权服务器校验也可知道

token

的有效性,这将提高资源访问的效率。

但这要建立在一个前提上,颁发出去的

token

不能提前失效,因为资源服务器仅靠

token

校验有效性。如果用户的

token

泄露了就无法阻止非法用户访问数据,所以

token

有效期需要尽量短。在授权服务器应可以禁用某个用户或者

token

避免非法用户使用

refresh_token

刷新更换新的

token

可以和客户端协定一个

Jwt

签名公私钥,如果有大量用户的

token

泄露则更换签名公私钥。

本文采用

UUID

作为

token

,状态数据存储在

redis

上,如果需要

token

提前过期,删除

redis

中对应的缓存即可。

标签: java spring 前端

本文转载自: https://blog.csdn.net/nineya_com/article/details/129416660
版权归原作者 玖涯菜菜子 所有, 如有侵权,请联系我们删除。

“Oauth2.0四种授权模式适用场景和授权流程介绍以及个人的一些思考”的评论:

还没有评论