# cas **Repository Path**: zcxsythenew/cas ## Basic Information - **Project Name**: cas - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-01-14 - **Last Updated**: 2021-06-20 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # casback 独立认证模块的后端部分。 ## 摘要 此认证模块根据 [RFC 6749](https://www.ietf.org/rfc/rfc6749.txt) 规范编写而成,并采用上述规范第 4.1 节所描述的授权码模式进行授权。此模块为 AUGENBLICK 多语言学习网站所用的唯一用户认证与客户端认证模块。目前,此认证模块用于: 1. 多语言学习网站的网页前端项目。目前接受一个 URI,为 [https://augenblick.zcxsythenew.xyz](https://augenblick.zcxsythenew.xyz)。同时也是默认的 URI。 1. (预留)多语言学习网站的 Android 与 iOS 客户端。目前接受一个 URI,为 `augenblick://127.0.0.1`。同时也是默认的 URI。 1. (预留)可能的其它第三方客户端。可接受的 URI 和默认的 URI 待定。 ## 认证过程 认证全过程都应当使用 SSL 协议,以免重要数据(如 `authorization_code`、`access_token`、`refresh_token`)泄漏。 授权码模式的认证过程简述如下。详细过程请查阅 [RFC 6749](https://www.ietf.org/rfc/rfc6749.txt)。 1. 客户端将用户通过浏览器重定向到此认证模块的认证网页。 1. 用户在网页上进行登录。对于摘要一节所述的前两类客户端,自动通过认证;对于后续的其它客户端(若有),则需用户手动确认允许客户端访问数据。 1. 通过认证后,网页将重定向到客户端提供的 URI,在 URI 的 `query` 部分提供授权码。 1. 客户端拿到授权码后,在 5 分钟内向服务器发起 POST 请求,提供授权码以及用于认证客户端身份的 `client_secret`。为了避免 `client_secret` 和授权码泄漏,客户端必须使用基于 SSL 的协议,例如 https。 1. 服务器验证授权码和客户端身份有效,向客户端发送 `access_token` 和 `refresh_token`。`access_token` 的有效期为一天。验证结束后,授权码失效,不可二次使用。 1. `access_token` 过期后,客户端使用 `refresh_token` 和 `client_secret` 向服务器请求新的 `access_token` 和 `refresh_token`。服务器重新验证客户端身份和 `refresh_token` 有效后,发放新的 `access_token` 和 `refresh_token`。原 `refresh_token` 使用后失效,不可二次使用。 1. 在 `access_token` 有效期内,客户端向资源服务器请求数据。该模块尚未编写,并超出了此模块的范围。 ### 重定向至认证模块 认证的第一步是客户端将用户重定向至此认证模块(如果客户端是 Native App,则需要打开系统默认浏览器并转到认证模块的网址)。网址为: ```text https://cas.zcxsythenew.xyz/oauth/authorize?{query} ``` 跳转时需要附加 `query` 部分。该部分描述如下: * `response_type`。必需。该值必须为 `code`。 * `client_id`。必需。对于多语言学习网站的前端项目,此值为 `0`。对于多语言学习网站的 Android 或 iOS App,此值为 `1`。 * `redirect_uri`。可选。如果提供此值,必须和本文摘要部分所述的网址一致,但后续协调过程中可以调整。 * `scope`。可选。默认为 `user_info`,仅允许访问用户基础数据。具体其它类型等待资源服务器的开发。多个值之间用空格隔开。 * `state`。可选。当认证模块进行第二次重定向时,会将 `state` 按原来的值返回给客户端。 具体原理和示例见 [RFC 6749](https://www.ietf.org/rfc/rfc6749.txt)。 ### 认证成功时重定向回客户端 当用户登录成功并同意客户端访问其数据(对于摘要一节所述的前两类客户端,系统默认同意)时,系统将重定向回客户端所提供的 `redirect_uri`,并在 `query` 部分附加数据。如未提供,则通过 HTTP 302 重定向到默认的 URI,如摘要一节中所述。 附加的数据如下: * `code`。授权码。 * `state`。客户端发起请求时所提供的 `state`。如未提供,则没有此字段。 具体原理和示例见 [RFC 6749](https://www.ietf.org/rfc/rfc6749.txt)。 如果客户端为 Native App,此功能需要用户系统默认浏览器允许通过 HTTP 302 跳转到非 HTTP 协议,从而调起应用。经调试,Android Chrome 满足要求。 ### 认证失败时重定向回客户端 当认证失败时,如果 `redirect_uri` 与 `client_id` 匹配,或者未提供 `redirect_uri` 但提供了有效的 `client_id`,则认证模块将重定向回客户端。否则,仅向浏览器返回 HTTP 400 或 HTTP 403 状态码。重定向的 URI 如上一节所述,在 `query` 部分附加数据。 附加的数据如下: * `error`。其值如 [RFC 6749](https://www.ietf.org/rfc/rfc6749.txt) 第 4.1.2.1 节所述。 具体原理和示例见 [RFC 6749](https://www.ietf.org/rfc/rfc6749.txt)。 认证模块在以下情况不进行重定向: 1. `client_id` 无效,或与 `redirect_uri` 不匹配,或未提供 `client_id`。 1. 服务器发生了导致无法继续重定向的错误。 ### 客户端请求 access token 重定向后,客户端应在 5 分钟内,使用获取到的授权码向认证系统请求 `access_token`。请求方式为 POST,地址为: ```text https://cas.zcxsythenew.xyz/oauth/token ``` Egg.js 的 CSRF 防范系统要求,进行 POST 请求时应当提供 CSRF token。因此,在请求 `access_token` 之前,需要先进行 GET 请求获取 CSRF token。地址为: ```text https://cas.zcxsythenew.xyz/oauth/token ``` 进行 GET 请求后,服务器将在 Cookie 内写入 `csrfToken` 的值,同时在响应体也写入同样的值以方便客户端提取。客户端提交 POST 请求时,需要提交此 `csrfToken`,如下文所述。 进行 POST 请求时,请求头应当包括以下字段: * `Authorization`。按照 HTTP Basic Access Authentication([RFC 2617](https://www.ietf.org/rfc/rfc2617.txt))第 2 节的要求,该字段的值为 `"Basic " + base64(client_id + ":" + client_secret)`。 * `x-csrf-token`。该字段的值为之前所描述的 `csrfToken` 的值。 * `cookie`。请求 `csrfToken` 时服务器将写入 `cookie`,进行 POST 请求时需要将 `cookie` 也提交上来。进行 Native App 开发时应当特别注意。 请求体以 "application/x-www-form-urlencoded" 的格式编码,包含以下字段: * `grant_type`。必需。该值必须为 `authorization_code`。 * `code`。必需。该值为认证成功时重定向到客户端所提供的值。 * `redirect_uri`。当该值与默认的 `redirect_uri` 不一致时必需,且与客户端重定向至认证系统时的 `redirect_uri` 一致。 请求体不需要包含 `client_id` 和 `client_secret`,因为已经在请求头中包含了。 具体原理和示例见 [RFC 6749](https://www.ietf.org/rfc/rfc6749.txt)。 ### 认证成功时服务器返回 token 认证成功时,服务器将以 JSON 格式返回 token,例如: ```json { "access_token": "2YotnFZFEjr1zCsicMWpAA", "token_type": "bearer", "expires_in": 86400, "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA", "scope": [ "user_info" ] } ``` 包含以下字段: * `access_token` * `token_type`。此值为 `bearer`。此类型的 `token` 用法见 [RFC 6750](https://www.ietf.org/rfc/rfc6750.txt)。 * `expires_in`。此值一般为 86400,表示 `access_token` 将在24小时后过期。 * `refresh_token` * `scope`。一个数组,表示用户同意客户端访问数据的范围。为客户端请求认证所提供的 `scope` 的子集。对于摘要所述的前两类客户端,自动同意所有请求;对于其它客户端(预留),用户可勾选同意访问的数据范围。 ### 认证失败时服务器返回错误 服务器以 JSON 的格式返回错误信息。例如: ```json { "error": "invalid_request" } ``` 包含以下字段: * `error`。其值如 [RFC 6749](https://www.ietf.org/rfc/rfc6749.txt) 第 5.2 节所述;但当发生标准文档未包含的其它错误时,也可能会返回 `server_error` 等其它值。 ### 使用 refresh_token 刷新 当 `access_token` 没有因为其它原因(例如用户手动撤回了权限等)失效(而仅仅是因为超出有效时间)时,客户端可向认证系统请求 `access_token`。请求方式为 POST,地址为: ```text https://cas.zcxsythenew.xyz/oauth/token ``` Egg.js 的 CSRF 防范系统要求,进行 POST 请求时应当提供 CSRF token。请求方式如前文所述。 请求头应当包括以下字段: * `Authorization`。按照 HTTP Basic Access Authentication([RFC 2617](https://www.ietf.org/rfc/rfc2617.txt))第 2 节的要求,该字段的值为 `"Basic " + base64(client_id + ":" + client_secret)`。 * `x-csrf-token` * `cookie` 请求体以 "application/x-www-form-urlencoded" 的格式编码,包含以下字段: * `grant_type`。必需。该值必须为 `refresh_token`。 * `refresh_token`。必需。 * `scope`。可选。若提供,必须为服务器之前返回的 `scope` 的子集。多个元素中间用空格隔开。若不提供,默认与服务器之前返回的 `scope` 一致。 服务器验证通过后将返回新的 `access_token` 和 `refresh_token`,原来的 `refresh_token` 将失效,不可再次使用。返回格式如前两节所述。 ## 部署 此认证系统部署需要以下步骤: 1. git clone 1. npm install 1. 安装 MySQL Server,设置密码,新建用于此认证系统的数据库。修改 database/config.json 和 config/config.default.js 配置文件的相应内容。 1. 设置环境变量 `NODE_ENV=production` 1. 在数据库内创建表 `npx sequelize db:migrate` 1. 开启服务器 `npm start` ## 测试 此项目编写了测试代码,并参照 [RFC 6749](https://www.ietf.org/rfc/rfc6749.txt) 所述的要求,基本覆盖了正常情况下服务器会遇到的各类情况。 然而,目前有两种情况未经过测试: 1. 认证码超时。SuperTest 框架似乎不支持模拟 mockTime。 1. 各类异常情况下的测试,例如服务器异常等。 ## 相关项目 app/public 目录下是 cas 前端项目(Vue)编译后的代码。[仓库地址](https://gitee.com/zcxsythenew/cas)