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,

OAuth2和SSO的区别

什么是SSO

SSO是Single Sign On的缩写,OAuth是Open Authority的缩写,这两者都是使用令牌的方式来代替用户密码访问应用。流程上来说他们非常相似,但概念上又十分不同。SSO大家应该比较熟悉,它将登录认证和业务系统分离,使用独立的登录中心,实现了在登录中心登录后,所有相关的业务系统都能免登录访问资源。

SSO的实现有很多框架,比如CAS框架,以下是CAS框架的官方流程图。特别注意:SSO是一种思想,而CAS只是实现这种思想的一种框架而已

于OAuth2的区别

SSO 是一种思想,或者说是一种解决方案,是抽象的,我们要做的就是按照它的这种思想去实现它

其次,OAuth2 是用来允许用户授权第三方应用访问他在另一个服务器上的资源的一种协议,它不是用来做单点登录的,但我们可以利用它来实现单点登录。

在本例实现SSO的过程中,受保护的资源就是用户的信息(包括,用户的基本信息,以及用户所具有的权限),而我们想要访问这这一资源就需要用户登录并授权,OAuth2服务端负责令牌的发放等操作,这令牌的生成我们采用JWT,也就是说JWT是用来承载用户的Access_Token的

 

SSO交互流程

  • 用户输入网址进入业务系统Protected App,系统发现用户未登录,将用户重定向到单点登录系统CAS Server,并带上自身地址service参数
  • 用户浏览器重定向到单点登录系统,系统检查该用户是否登录,这是SSO(这里是CAS)系统的第一个接口,该接口如果用户未登录,则将用户重定向到登录界面,如果已登录,则设置全局session,并重定向到业务系统
  • 用户填写密码后提交登录,注意此时的登录界面是SSO系统提供的,只有SSO系统保存了用户的密码,
  • SSO系统验证密码是否正确,若正确则重定向到业务系统,并带上SSO系统的签发的ticket
  • 浏览器重定向到业务系统的登录接口,这个登录接口是不需要密码的,而是带上SSO的ticket,业务系统拿着ticket请求SSO系统,获取用户信息。并设置局部session,表示登录成功返回给浏览器sessionId(tomcat中叫JSESSIONID)
  • 之后所有的交互用sessionId与业务系统交互即可

 

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