OAuth2基础(一)

简介

OAuth2的设计背景,在于允许用户在不告知第三方自己的帐号密码情况下,通过授权方式,让第三方服务可以获取自己的资源信息。
详细的协议介绍,开发者可以参考RFC 6749

oauth2的几种模式

授权码方式

下面简单说明OAuth2中最经典的Authorization Code模式(对接微信、钉钉、飞书等三方平台都是采用这种方式),流程如下

 

 

流程图中,包含四个角色。

  • ResourceOwner为资源所有者,即为用户
  • User-Agent为浏览器
  • AuthorizationServer为认证服务器,可以理解为用户资源托管方,比如企业微信服务端
  • Client为第三方服务

调用流程为:
A) 用户访问第三方服务,第三方服务通过构造OAuth2链接(参数包括当前第三方服务的身份ID,以及重定向URI、授权类型),将用户引导到认证服务器的授权页

https://b.com/oauth/authorize?
  response_type=code&
  client_id=CLIENT_ID&
  redirect_uri=CALLBACK_URL&
  scope=read

B) 用户选择是否同意授权(认证服务器登录状态,如果未登录需要先登录)
C) 若用户同意授权,则认证服务器将用户重定向到第一步指定的重定向URI,同时附上一个授权码。
D) 第三方服务收到授权码,带上授权码来源的重定向URI,向认证服务器申请凭证。
E) 认证服务器检查授权码和重定向URI的有效性,通过后颁发AccessToken(调用凭证)

D)与E)的调用为后台调用,不通过浏览器进行
我的理解扫码登录也是oauth2  电脑web端构建的oauth2 同时监听登录状态,在用户授权后重定向 修改登录状态为成功,电脑web端监听到后写入用户登录信息

直接下发token方式

 

 

 

有些 Web 应用是纯前端应用,没有后端。这时就不能用上面的方式了,必须将令牌储存在前端。RFC 6749 就规定了第二种方式,允许直接向前端颁发令牌。这种方式没有授权码这个中间步骤,
所以称为(授权码)"隐藏式"(implicit)
A) 用户访问第三方服务,第三方服务通过构造OAuth2链接(参数包括当前第三方服务的身份ID,以及重定向URI、授权类型),将用户引导到认证服务器的授权页
https://b.com/oauth/authorize?
  response_type=token&
  client_id=CLIENT_ID&
  redirect_uri=CALLBACK_URL&
  scope=read

response_type参数为token,表示要求直接返回令牌

B)用户选择是否同意授权(认证服务器登录状态,如果未登录需要先登录)

C)用户授权后,callback_url用户直接收到TOKEN

https://a.com/callback#token=ACCESS_TOKEN

注意,令牌的位置是 URL 锚点(fragment),而不是查询字符串(querystring),这是因为 OAuth 2.0 允许跳转网址是 HTTP 协议,因此存在"中间人攻击"的风险,而浏览器跳转时,锚点不会发到服务器,就减少了泄漏令牌的风险。

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

密码方式

如果你高度信任某个应用,RFC 6749 也允许用户把用户名和密码,直接告诉该应用。该应用就使用你的密码,申请令牌,这种方式称为"密码式"(password)。

A)用户访问第三方网站,第三方网站需要用户提供用户名命名

B) 第三方网站获得用户名和密码直接向认证服务器获取token,认证服务器验证通过后直接返回token

https://oauth.b.com/token?
  grant_type=password&
  username=USERNAME&
  password=PASSWORD&
  client_id=CLIENT_ID

上面 URL 中,grant_type参数是授权方式,这里的password表示"密码式",usernamepassword是 B 的用户名和密码。

这种方式需要用户给出自己的用户名/密码,显然风险很大,因此只适用于其他授权方式都无法采用的情况,而且必须是用户高度信任的应用。

凭证方式

适用于没有前端的命令行应用,即在命令行下请求令牌(比如后端服务的授权验证)

A)应用向认证服务器下发请求,应用直接响应下发token

https://oauth.b.com/token?
  grant_type=client_credentials&
  client_id=CLIENT_ID&
  client_secret=CLIENT_SECRET
&scope=read

B)应用验证通过直接响应下发token

注:以上调用是不安全的,标准的请改为

$(echo -n 'client_1:YOUR_CLIENT_SECRET' | base64) 通过client_id:secret 拼接通过base64加密后传递

通过 URL 传递 client_idclient_secret 是不安全的,因为 URL 可能会被记录在服务器日志或其他地方,增加泄漏风险。最佳做法是通过 Authorization header 使用基本认证传递这些信息。

curl --location --request POST 'http://localhost:8080/oauth/token' \
--header 'Authorization: Basic $(echo -n 'client_1:YOUR_CLIENT_SECRET' | base64)' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'scope=read'

 

token的使用

此时,每个发到 API 的请求,都必须带有令牌。具体做法是在请求的头信息,加上一个Authorization字段,令牌就放在这个字段里面。

curl -H "Authorization: Bearer ACCESS_TOKEN" \
"https://api.b.com"

上面命令中,ACCESS_TOKEN就是拿到的令牌。

Head Authorization几种校验方式

Basic token

Basic token是一种基本的身份验证方式,它使用base64编码的用户名和密码。这种身份验证方式的缺点在于,由于用户名和密码是以bse64编码的形式传递,因此容易被栏截并进行破解,存在风险。

 

Bearer token

直接下发token

比如uuid 这个uuid映射用户信息,在服务器

下发jwtToken

比如下发token的方式,jwtToken,jwt的好处是不用再在服务端存储token映射信息,直接通过jwt存储在本地,服务端只需要根据秘钥校验这个jwt是否是有效的

更多方式

一般token和Bearer

更新令牌

令牌的有效期到了,如果让用户重新走一遍上面的流程,再申请一个新的令牌,很可能体验不好,而且也没有必要。OAuth 2.0 允许用户自动更新令牌。

具体方法是,B 网站颁发令牌的时候,一次性颁发两个令牌,一个用于获取数据,另一个用于获取新的令牌(refresh token 字段)。令牌到期前,用户使用 refresh token 发一个请求,去更新令牌。

如下发token的返回格式

HTTP/1.1 200 OK
Content-Type:application/json

{
  "accessToken" : "token",
  "expireIn" : 7200
}

 以上企业微信调用一次就刷新了token

像钉钉需要传刷新token

{
    "expireIn":7200,
    "accessToken":"6d9999f657b13f2a898aeed3d0dbfec5",
    "refreshToken":"754e08e9b85a3b25a6920c979a63baab"
}

应用场景

其实主要是看哪一方调用哪一方,被调用的那一方需要实现oauth2协议

对外暴露api

比如三方系统需要根据他们业务调用我们创建工单接口,或者获取工单列表展示在他们接口

oauth2使用密码模式

我们是oauth服务器

1.在我们系统创建一个client,配置id和秘钥。三方在他们服务器换取token

相关api需要校验白名单

WEBHOOK方式

我们需要调用三方系统api,当某些场景给三方推送事件,也可以走oauth2,三方是oauth服务器,通过密码模式换取token调用,三方需要校验白名单

三方需要使用我们登录状态实现免登

在我们系统配置了一个菜单直接跳转到三方系统(这个菜单url就是oauth url,重定向地址就是三方url)

因为我们是oauth服务器

1.在我们系统创建一个client,配置id和秘钥。

2.构建oauth url,跳转到我们地址,用户授权后下发token或者code

authurl范例:/oauth2/authorize?response_type=code&client_id=lqclient&scope=user.read&redirect_uri=http://127.0.0.1:18080&state=2&auto_approve=false

3.重定向回第三方系统,三方系统通过token和code获取我们系统的用户信息

4.三方系统先根据我们系统的的当前用户uid匹配是否有绑定关系用户,如果有查找到对应用户,三方系统走他们自己的用户token下发流程实现免登

5.如果没有匹配到,则根据我们系统的手机号或者邮箱进行匹配,如果还匹配不到就创建(创建或者匹配 都需要将三方用户与我们系统的用户uid进行绑定,下次在4阶段就可以直接实现免登)

注意:

1.需要校验对应client_id的重定向地址是否是正确的,防止伪造

2.调用换取token接口需要判断是否支持此ip(应用需要配置ip地址白名单),防止client_id和secret泄露

我们需要使用三方登录状态实现免登

三方系统配置了一个菜单跳转到我们系统

这个时候三方是oauth服务器

参考上面只是流程反着来

关于对接简单方式

1.调用方配置一个秘钥,通过秘钥jwt加密之后传入token,被调用方 只需要通过秘钥校验jwt来源。

接口设计

authorize接口

参数说明

client_id

第三方需要在oauth服务配置client如飞书

 

response_type

授权类型,支持code和token

1.code更安全,需要第三方在服务器通过app_id和secret换取

2.token简化这一步,直接下发token给第三方(不安全 token重定向明文传输)

scope

授权范围,根据授权链接申请权限来控制,比如获取位置信息

redirect_uri

oauth服务器,授权后下发的token或者code重定向地址

auto_approve

是否自动授权,如果自动授权则不需要用户允许,需要根据client_id查询是否支持自动授权

state

第三方表示本次授权state的参数,重定向需要带回去,比如第三方使用这个做扫码登录,重定向到第三方服务端则修改这个state登录状态,扫码轮询扫描到成功获取token则直接走登录成功逻辑

 

授权码模式

第三方构建oauth2地址

get /oauth2/authorize?response_type=code&client_id=lqclient&scope=user.read&redirect_uri=http://127.0.0.1:18080&state=2&auto_approve=false

1.用户点击oauth2链接或者第三方系统构建调用

2.oauth服务根据client_id判断是否支持scope授权类型,判断redirect_url是否支持,如果auto_approve=true,判断是否支持自动授权

3.判断用户是否是登录状态,如果不是需要用户先登录

4.如果response_type=code则表示是授权码模式,用户302跳转到oauth2授权页面,根据scope= 弹出提示用户是否授权获取用户信息(后端需要维护一个key存储用户允许情况)

5.用户拒绝跳转到异常页面

6.(这里应该区分比如根据多加一个key判断是用户授权后跳过来的,避免直接构建oauth2跳过来伪造用户授权)用户允许则再次请求/oauth2/authorize?response_type=code&client_id=lqclient&scope=user.read&redirect_uri=http://127.0.0.1:18080&state=2&auto_approve=false 

7.根据用户允许情况构建授权范围的code,重定向code到redirect_uri

8.第三方根据code调用token接口换取token

token模式

流程和授权码模式都一样 只是response_type=token,重定向过去的带上token

token接口

参数说明

grant_type

使用什么模式换取token ,比如

authorization_code(授权码换取token)、password(使用token和secrit直接换取token)、refresh_token(使用refresh_token刷新token)、client_credentials(客户端模式)

client_id

应用身份

client_secret

应用秘钥

redirect_uri

非必选,如果配置了则302重定向返回,没配置则json返回

授权码模式

注:需要校验client配置的ip白名单,防止client_id和client_secret泄露

注token是第三方服务端调用

get /oauth2/token?grant_type=authorization_code&client_id=lqtest&client_secret=test&scope=user.read&redirect_uri=http://127.0.0.1:18080&state=2&auto_approve=false&code=829c8cd02d9a4c94ab344468b76b2bbd

为什么设计refresh_token 

因为token是会身份认证频繁传递,容易泄露,我们可以把时间设置短一点(比如一分钟),假如出现泄露也是仅仅1分钟泄露。

但是会造成用户频繁的登录认证,体验不好。

我们可以设置一个refresh_token,针对token快超时调用refresh_token重新获取token,

posted @ 2022-12-21 13:59  意犹未尽  阅读(113)  评论(0编辑  收藏  举报