什么是鉴权
鉴权也叫身份认证,指验证用户是否有系统的访问权限。就很像我们经常乘坐动车的票据(对应的标识,一定的时间范围)。
认证方式
接下来介绍几种我们工作中通常用到的认证方式。
Session-Cookie 认证
利用服务端的 Session(会话)和浏览器(客户端)的 Cookie 来实现的前后端通信认证模式。
来源
由于 HTTP 请求时是无状态的,服务端正常情况下无法得知请求发送者的身份。这个时候如果我们要记录状态,就需要在服务端创建会话,将相同客户端的请求都维护在各自的会话记录中,每当请求到达服务端时,先校验请求中的用户标识是否存在于 Session 中,如果有则表示已经认证成功,否则表示认证失败。
流程
实践
boss(我们的一个产品) 这边 Session ID 存在数据库里面,在 Memcached 里面做缓存。客户端每次调用接口的时候会通过 response headers 里面的 Set-Cookie 更新过期时间(boss 这边设置的是 6 个小时),这样做的作用是防止你在做一些复杂操作的时候,cookie 突然过期。
⚠️整个过程是比较重的,因为每次的接口调用都得更新过期时间。
优缺点
优点:
- 简单易用,浏览器会自动带上
缺点:
- 脱离浏览器没法用,比如原生应用
关于 Cookie 的安全问题
Cookie 属性:
提高安全性的办法
- Expires/Max-Age 设置合理过期时间
- HttpOnly 设置为 true
- Secure 设置为 true(使用 https)
Token 认证
来源
负载均衡多服务器的情况,不好确认当前用户是否登录,因为多服务器不共享 Session。这个问题也可以将 Session 存在一个服务器中来解决,但是就不能完全达到负载均衡的效果。
Token 和 Session-Cookie 认证方式中的 Session ID 不同,并非只是一个标识符。Token 一般会包含用户的相关信息,通过验证 Token 不仅可以完成身份校验,还可以获取预设的信息。
客户端可以将 token 存放于 localStroage 等容器中。客户端每次访问都传递 token,服务端解密 token,服务端就不需要存储 Session 占用存储空间,就很好的解决负载均衡多服务器的问题了。
流程
实践
平常用的最多的就是 JSON Web Token(JWT),也是目前最流行的跨域身份验证解决方案。
JWT 组成:头部. 载荷. 签名
头部和载荷用 base64 编码
签名计算:
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload) , secret)
使用方法:
Authorization: Bearer
但是 JWT 有个大缺点是服务器不保存会话状态,所以在使用期间不可能取消令牌或更改令牌的权限。也就是说,一旦 JWT 签发,在有效期内将会一直有效。
⚠️载荷的内容任何人都可以读到,不要放入敏感信息
jwt 存储位置的争论:我觉得如果存储信息多,天然防止 csrf 的话,放到 localStorage 或者 sessionStoraged 都行。
除了 JWT 可以提升 token 的安全性,Refresh token 也可以。
业务接口用来鉴权的 token,我们称之为 access token。越是权限敏感的业务,我们越希望 access token 有效期足够短,以避免被盗用。但是过短的有效期会造成 access token 经常过期,过期后怎么办呢?
一种办法是,让用户重新登录获取新 token,显然不够友好,要知道有的 access token 过期时间可能只有几分钟。
另外一种办法是,再来一个 token,一个专门生成 access token 的 token,我们称为 refresh token。
refresh token 的过期时间一般比较长,比如 6 个小时,access token 的过期时间比较短,比如 10 分钟。我们在实际业务中,api 调用时只传递 access token 进行鉴权。如果 access token 过期,则使用 refresh token 去授权服务器更新 access token。最终 refresh token 也过期了,这时候用户就得重新登陆了。
优缺点
优点:
- 轻量,服务端不用存储,移动端可用
缺点:
- 一旦派发出去,失效之前都是有效的(虽然可以解决,但是就类似于 Session 机制了)
单点登录
来源
但当我们业务线越来越多,就会有更多业务系统分散到不同域名下,就需要「一次登录,全线通用」的能力,叫做「单点登录」。
流程
对浏览器来说,SSO 域下返回的数据要怎么存,才能在访问 A 的时候带上?这就需要也只能由 A 提供 A 域下存储凭证的能力。
实践
OIDC
- OIDC 登陆点击,重定向到登录的 OpenID 网站
- 输入用户名密码,如果验证成功。则会重定向到登陆回调(之前设置好的地址)
- 回调地址里面有个 code 参数,code 验证正确后,下发 sk,boss 系统登陆成功
- 前端通过添加 iframe 的方式轮询 authing 链接实现单点登出
关于 OIDC
OIDC 是一个 OAuth2 上层的简单身份层协议。它允许客户端验证用户的身份并获取基本的用户配置信息。OIDC 使用 JSON Web Token(JWT)作为信息返回,通过符合 OAuth2 的流程来获取。
关于 OAuth2
OAuth2 最终目的是为第三方应用颁发一个有时效性的令牌 token。使得第三方应用能够通过该令牌获取相关的资源。当你想要登录某个论坛,但没有账号,而这个论坛接入了如 QQ、Facebook 等登录功能,在你使用 QQ 登录的过程中就使用的 OAuth 2.0 协议。
- Client 请求 Resource Owner 的授权。授权请求可以直接向 Resource Owner 请求,也可以通过 Authorization Server 间接的进行。
- Client 获得授权许可。
- Client 向 Authorization Server 请求访问令牌。
- Authorization Server 验证授权许可,如果有效则颁发访问令牌。
- Client 通过访问令牌从 Resource Server 请求受保护资源。
- Resource Server 验证访问令牌,有效则响应请求。
关于 LDAP
LDAP (Light Directory Access Portocol),中文名轻量目录访问协议,是一个开放、广泛被使用的工业标准。比如我们的 Jira、Confluence、Yapi。
但是 LDAP 并不能做到单点登录 SSO,只是可以用同样的用户名和密码可以登陆不同的系统,但达不到一次登陆之后可以访问多个系统。
Others 认证方式
2FA(双因素认证)
线上的 boss 必须开启二次认证,会生成一个二维码,那个二维码就是一个 SecretKey,通过 CryptoJS.HmacSHA1(默认算法),每次会计算出一个 6 位(默认长度)随机数。计算公式为
⚠️因为默认是 30s 内有效,所以用户手机时间要比较准确
Google 验证器
密钥二维码
http://otpauth//totp/ 青云 QingCloud 云计算管理平台:[email protected]?secret=xxx&issuer = 青云 QingCloud 云计算管理平台
xxx: 大写的字母数字 16 位
使用 base32 的解码密钥
密钥必须大写没有空格
获取 Unix 时间戳
let epoch = Math.round(new Date().getTime() / 1000.0);if (localStorage.offset) {
epoch = epoch + Number(localStorage.offset);
}
counter = Math.floor(epoch / period); // period 一般为30 这个失效实现的想法太棒了
计算签名
const time = this.leftpad(this.dec2hex(counter), 16, "0");const key = this.base32tohex(secret)
CryptoJS.HmacSHA1(
CryptoJS.enc.Hex.parse(time),
CryptoJS.enc.Hex.parse(key)
)
得出结果
const len = 6;const result = otp.substr(otp.length - len, len).toString() // 默认取的最后6位
作者
Dean 青云科技高级工程师
本文由博客一文多发平台 OpenWrite 发布!