上一篇文章详细介绍了授权服务器需要做的事情---https://www.cnblogs.com/hellowhy/p/15513493.html,本篇来说说客户端服务器吧
一、What?
客户端服务器需要做些什么事呢?请戳下图
在授权码模式中,客户端服务器需要和资源拥有者、授权服务器以及受保护资源服务器三者进行交互,如上图所示,客户端服务器需要做的事有:
- 注册:向授权服务器发起注册请求(参考上一篇)
- 发起授权请求:当资源拥有者委托它去访问受保护资源时,客户端首先要携带自己的注册信息以及state将用户重定向到授权服务器的身份认证页面,当用户完成授权流程后对授权结果进行处理
- 处理授权响应:用户授权成功后,授权服务器携带state和code将用户引导至客户端重定向页面,该页面会请求客户端服务器处理授权结果,即使用授权码获取令牌
- 使用令牌访问资源:当客户端服务器获取到令牌之后,用访问令牌accessToken向资源服务器发送访问资源请求
- 请求刷新令牌:当访问令牌accessToken过期之后,用刷新令牌refreshToken向授权服务器发送刷新访问令牌请求
下面来详细介绍每一步的工作流程。
二、How?
知道了客户端服务器需要做什么,接下来看看,它要怎么做?
2.1 注册
首先,OAuth客户端需要和授权服务器相互了解才能通信,这种了解的方式就是客户端向授权服务器申请注册一个应用,例如使用微信登录之前要先申请一个微信开发者账号,客户端申请的本质上也是一个账号。请求注册时需要告诉授权服务器客户端的一些基本信息,如名称、访问权限、客户端重定向地址等,这些都是授权服务器要求客户端提供的信息,创建成功后授权服务器会返回一个客户端唯一标识clientId和客户端密钥clientSecret,可以认为是授权服务器对客户端分配的用户名和密码。一般的请求格式如下:
curl --location --request POST 'http://authendpoint.com/createClient' \ --header 'Content-Type: application/json' \ --data-raw '{ "clientName": "客户端名称", "redirectUris": [ "http://client.com/callback1", "http://client.com/callback2", "http://client.com/callback3" ], "grantTypes": [ "authorization_code", "implicit", "password", "refresh_token" ], "responseTypes": [ "code", "token" ], "scopes": [ "read", "write" ] }'
每个字段的详细介绍请戳上一篇,本文不再赘述:https://www.cnblogs.com/hellowhy/p/15513493.html
注册成功之后,客户端将保存clientId和clientSecret,并保证clientSecret的保密性,防止泄露。
2.2 获取令牌
当用户(资源拥有者)委托客户端访问某个受保护的资源时,客户端首先需要检查自己是否持有用户访问资源的令牌(这种场景下可以将用户标识与客户端令牌进行关联),不持有的情况下需要将用户302重定向到身份认证页面,用户在授权服务器上完成身份认证之后将授权服务器返回的认证结果即授权码传递给客户端,以便让客户端拿着这个授权码向授权服务器请求令牌。
2.2.1 发送授权请求
curl --location --request GET 'http://authendpoint.com/authorize?client_id=daffdafd3554543dafdaf&redirect_uri=http://client.com/callback1&state=dfdgjudbetrdd&response_type=code&scope=read%20write' \ --header 'Content-Type: application/json'
在发送授权请求之前,客户端需要生成一个状态值state,并将state与本次请求参数进行关联。用户完成身份认证和授权操作之后,授权服务器会302重定向至请求中的redirect_uri,并且追加生成的授权码和请求中的state,重定向后的uri其实是指向客户端的一个请求
curl --location --request GET 'http://client.com/callback?code=dffsfdafda52254&state=daffdafd3554543dafdaf&reditect_uri=http://client.com/callback1'
上面的请求可以是浏览器直接请求客户端,也可以是浏览器请求前端的node层,在node层发起对客户端的请求,如果是前者:请求的redirect_uri就是客户端提供的URL;如果是后者,请求的redirect_uri就是node层提供的URL,但是既然code都能在浏览器中明文传输,经过node层和不经过node层直接请求后端服务是相同的效果。
2.2.2 处理授权响应
客户端在回调函数callback中,需要进行以下验证:
- 检查state是否存在,不存在则认为该请求不是自己发出的,直接返回错误信息给用户
- 根绝state中解析出上下文(生成state时保存对应的请求信息),比较redirect_uri是否一致
当验证通过后向授权服务器的令牌端点请求访问令牌access_token,请求格式如下(授权服务器中也有介绍):
curl --location --request POST 'http://tokenendpoint.com/token' \ --header 'Content-Type: application/json' \ --header 'Authorization: basic BASE64(clientId:clientSecret)' \ --data-raw '{ "grantType":"authorization_code", "code":"dffsfdafda52254", "redirect_uri":"http://client.com/callback1" }'
思考:在客户端处理授权响应的方法中,为什么还要传入redirect_uri呢 书中是像下面这样介绍的:
根据OAuth 规范,如果在授权请求中指定了重定向URI,那么令牌请求中也必须包含该重定向URI。这可以防止攻击者使用被篡改的重定向URI获取受害用户的授权码,让并无恶意的客户端将受害用户的资源访问权限关联到攻击者账户
请求token时,除了传授权码、授权类型和重定向地址之外,还要以HTTP基本认证的方式将客户端ID和客户端密钥一并传递给令牌端点,当授权服务器解析并验证通过这些请求参数之后,就会生成用于访问受保护资源的令牌信息,一般格式如下:
{ "accessToken": "68bjh4543", "tokenType": "Bearer ", "expiration": "2021-11-07 9:33:00", "expiresInSeconds": 3600, "scope": "write read", "state": "teststate", "refreshToken": "68bjh4543fdsfsfrrer", "refreshTokenExpiration": "2022-11-07 9:33:00", "refreshTokenExpiresInSeconds": 86765 }
返回结果中包含了访问令牌及其类型、过期时间、权限;刷新令牌及其过期时间以及客户端的状态信息state,接着客户端就能用accessToken请求访问受保护资源了!!!
2.3 访问资源
2.3.1 使用令牌访问资源
拿到访问令牌之后的操作就很简单了,客户端无需知道令牌的意义,只要持有并使用即可,一般将访问令牌发送给受保护资源的方法有以下三种:
- 使用HTTP Authorization头部;
- 使用表单格式的请求体参数
- 使用URL编码的查询参数
对这三种方式的选择书中是这样介绍的:
由于另外两种方法存在一些局限性,因此建议尽可能使用Authorization头部,在使用查询参数时,访问令牌的值有可能被无意地泄露到服务端日志中,因为查询参数是URI请求的一部分;使用表单的方式,会限制受保护资源只能接受表单格式的输入参数,并且要使用POSt方法;使用Authorization头部是这三总方法中最灵活和最安全的。
请求示例如下:
curl --location --request GET 'https://resourceendpoint.com/getResourceInfo' \ --header 'Authorization: Bearer 68bjh4543'
这样,客户端就替用户完成了访问受保护资源的整个流程。
2.3.2 请求刷新令牌
一般为了安全起见,访问令牌有一个有效期,过了有效期就不能访问受保护资源了,但是如果这个访问受保护资源的请求在客户端的某个定时任务中执行,就无法完成授权流程,因为授权流程需要用户的参与,为此可以使用令牌响应中的刷新令牌refresh_token,客户端在第一次接受到令牌响应之后需要将结果保存起来,当某个访问令牌失效后(是否失效需要在请求资源之前根据过期时间判断),查出该访问令牌对应的刷新令牌,并用刷新令牌向令牌端点请求新的访问令牌,当拿到新的访问令牌后,就能重新访问受保护资源,而且这个过程无论有没有用户参与,对用户来说都是无感知的。刷新访问令牌的请求示例如下:
curl --location --request GET 'http://tokenendpoint.com/refreshToken?refreshToken=68bjh4543fdsfsfrrer&grant_type=refresh_token' \ --header 'Content-Type: application/json' \ --header 'Authorization: basic BASE64(clientId:clientSecret)'
以上就是在OAuth 2.0的授权码许可方式中,客户端需要完成的所有事情。
三、总结
OAuth客户端是OAuth生态系统中使用最广泛的部分:
- 使用授权码许可类型获取令牌需要请求授权、完成授权、使用授权码请求访问令牌几步
- 如果刷新令牌可用,则可以使用获取新的访问令牌,且不需要用户参数
- 使用OAuth 2.0的bearer令牌比获取令牌更简单,只需要将一个简单的HTTP头部添加到所有的HTTP请求中即可
至此,客户端的运作原理全部介绍完毕,下一篇将介绍OAuth 2.0三剑客中的最后一位:受保护资源服务器~~