oauth2.0开发心得
产品诉求:需要开发一套oauth2.0系统给ezone以及某大客户使用,因为疫情在家办公,需求沟通不畅,所以踩了不少坑,项目完结,特此记录。
摘要:ksyun需要使用一套oauth2流程,保证与外商账号打通。目前正与海致对接(后期可能有更多的服务商接入)。所以需要提供一套既保证功能,又有足够安全的oauth体系。目前我们采用标准oauth2流程(https://tools.ietf.org/html/draft-ietf-oauth-v2-18),实现Authorization Code和Implicit两种授权方案。具体实现交互如下:

1.授权码授权模式流程说明如下:
1.客户端携带 clientId, scope, redirectUri, state 等信息请求授权服务器下发 code
2.授权服务器验证客户端身份,通过则询问用户是否同意授权(此时会跳转到用户能够直观看到的授权页面,等待用户点击确认授权)
3.假设用户同意授权,此时授权服务器会将 code 和 state 拼接在 redirectUri 后面,并以 302 形式下发 code
4.客户端携带 code, redirectUri, 以及 clientSecret 请求授权服务器下发 access_token
5.授权服务器验证客户端身份,同时验证 code,以及 redirectUri 是否与第一步相同,通过则下发 access_token,并选择性下发 refresh_token
2.隐式授权模式(Implicit Grant)
隐式授权模式流程说明如下:
1.客户端携带 clientId, scope, redirectUri, state 等信息请求授权服务器下发 access_token
2.授权服务器验证客户端身份,通过则询问用户是否同意授权(此时会跳转到用户能够直观看到的授权页面,等待用户点击确认授权)
3.假设用户同意授权,此时授权服务器会将 access_token 和 state 等信息以 URI Fragment 形式拼接在 redirectUri 后面,并以 302 形式下发
4.客户端利用脚本解析获取 access_token
1.新建两个模块
ksyun-oauth (认证模块)
ksyun-oauth-api(项目名称暂定,受限资源模块)
其中ksyun-oauth负责授权过程以及授权验证。ksyun-oauth-api负责外部调取的资源接口。
具体流程如下(假设根据用户id获取用户信息为例)

api请求接收&返回流程:
1.假定用户已经获取access_token,用户请求{host}/1.0/profile/v2/basicInfo?accessToken=xxxxxxx
2.将获取到的accessToken发送至auth模块进行用户&权限鉴定,若accessToken不存在或不合法将抛出错误提示,并记录log。
3.openApi模块根据用户请求,拆分出1.0/profile/v2/basicInfo,进行{api_verson}/{server_name}/{path_version}/{api_path}进行结构拆分,验证其结构是否符合规范;若验证通过,将透传到相应的openApi模块。(目前采用配置文件,将请求发送到localhost的模块下)
4.openApi接收到请求后,将具体请求发送底层模块,获取结构后,统一进行封装过滤,最终将结果发送至客户端。
ksyun-oauth模块交互方案
1.route创建3个路由,分别是:
$app->group('/oauth', function () {
$this->map(['GET', 'POST'],'/authorize', OAuth::class . ':authorize');//用于授权code获取及提交
$this->post('/token', OAuth::class . ':token');//接收code,获取access_token及refresh_token$this->post('/resources', OAuth::class . ':tokenResources');//校验access_token信息
}
2.当用户第一次授权时,请求GET:/oauth/authorize,参数response_type=code&client_id=haizhi&state=test&redirect_uri=http://www.haizhi.com/oauth。
3.authorize方法中对get过来的参数进行验证,与oauth_clients表中的信息进行比对,如果client_id,redirect_uri参数不对,抛出异常。验证成功,即可进行授权交互。
3.当用户点击同意授权时,请求POST:/oauth/authorize,返回一个临时code,过期时间1min,存放在Redis中。
4.当客户端收到code后,需要在1min内立即请求一次获取access_token的方法,POST:/oauth/token,携带参数:grant_type:authorization_code,code:96ff12116211d4c79baad2bd2cc9ec78dbf390be,client_id:haizhi,redirect_uri:http://www.haizhi.com/oauth,client_secret:123456。token接口会对提交过来的数据进行合法性校对,如果校验通过,返回access_token,存入oauth_authorization_codes
5.客户端拿到access_token和refresh_tokens后,即可自行存入cookie或其他存储方案。
ksyun-oauth-api模块交互方案
1.将access_token的校验写入中间件,此模块下所有的routes均必须走此中间件,我们将此中间件命名为authorizeMiddleware;
2.以post {host}/1.0/profile/v2/basic/info?accessToken=xxxxxxx,当用户请求发送至api代理层时,中间件会先截取到access_token,RPC请求/oauth/tokenRes,此时根据access_token信息,此时验证access_token是否合法,若合法则取出用户数据及可以访问的资源模块,返回结果。若通过验证,则进一步对url进行解析:解析出/{version}/{server_name}/{server_version}/{api_path}/{api_path},分别对以上参数进行合法性验证,任意一项不匹配,则将抛出异常,返回错误信息。
3.验证server_name是否存在配置文件,若存在,读取配置文件,进一步进行解析版本号以访问路径。全部验证通过后携带accessToken,请求ID,主账号ID,子用户ID,第三方真实IP去请求api模块。api层负责传递参数和过滤响应。最终,api层会带着请求的参数再去请求profile模块。当请求到数据后,封装及过滤敏感字段后,最终返回给请求方。
4.各个模块配置文件相互独立,包含path_prefix,apiList,host以及domain。这样做的好处是之后进行模块迁移,只需要改动配置文件参数即可,无需再做任何系统层面的改动。