学习 OAuth 认证之前先回顾一下通过 sessionid 的会话过程
关于 session 与 cookie 的请戳: https://www.cnblogs.com/moran1992/p/10793748.html
那么这种利用 session 的会话方式会引发哪些问题呢
1. 安全问题
常见保存会话方式: cookie,session 以及 token 等等, 这里我们将这三种方式的安全性能都简单的分析一下嘿嘿~
cookie: 我们了解 cookie 的安全问题首先从客户端颁发 cookie 开始, 然后通过 set-cookie 响应给客户端, 客户端保存之后下次通信会以默认的方式携带 cookie, 这种东西是程序员不可控的, 这时候会发生一个什么问题, 当网页中的一段 link 代码发生 XSS 攻击时, 会获取到本
地 cookie. 那么问题来了. 为啥? 因为 cookie 默认写到 http 的 headers 里, 没法控制. 那么好, 我们退出登录, 这时候服务器对 cookie 怎么操作的, 通过 set-cookie 的方式清空 cookie 并返回到客户端. 这样导致一个问题, 已经被劫持了的 cookie 还能用, 这就是为啥 cookie 不
安全的原因.
session: 首先我们要知道, session 到底是个啥, 我的理解是 session 就是 cookie 的另一种形式, 同样 session 是服务器颁发给客户端的, 同样通过响应中的 set-cookie, 但是与此同时服务器还会保存这个 session id, 本质上都是通过 cookie 传递, 本质上都可以被上诉攻击方式攻击, 但是好处在哪, 当退出登录的时候会服务器对 session id 会有个删除操作. 这样被劫持的 session id 其实就没用了, 但是不管怎样, 承担的风险方式与 cookie 是一样的.
token:session 与 cookie 都存在风险, 那么更好的解决方案是什么呢, 就是 token, 放在后面说.
2. 服务器压力过大
session 的特点之一就是存在服务器端, 请求过多的时候 session 也会增多, 这就会导致服务器的压力过大.
3. 分布式 session 管理困难
现在几乎所有的 web 应用程序都会采用分布式的处理方案, 那么每次一起请求不一定发送到哪个服务器, 比如这次请求发送的 Server A 上并且保存了 session, 下一个请求我发 Server B 上了, Server B 没有啊, 怎么校验, 这就会引发另一个问题, 分布式下如何共享 session. 当然现在有好多的方式比如: Session Replication 方式(session 复制), 缓存集中方式管理(将 Session 集中放在一个服务器上, 我们也可以称其 session 服务器), 基于 Redis 进行 session 共享等等, 很明显服务器压力很大, 而且并不好管理.
4. 跨域问题
首先啥叫跨域? 域名不同, 协议不同, 或者端口号不同通通称为跨域, 跨域产生于浏览器的同源策略, 请求发出去了, 但是响应被拦截了. 携带 headers 里面的 cookie 是不可以跨域的, 但是 authorization 可以啊, token 存在哪? token 就存在 authorization 里啊, emmmm~ 好处显而易见.
说了这么多 session 的缺点, 时代总是向前发展的, 技术也一样呀, 于是乎 token 诞生了~
什么是 token
token 可以理解成一个带有 User 基本信息的令牌, 例如每一次会话调用服务器的 API 时, 服务器可以通过 token 判断是否有权限.
token 流程
token 种类
- id token(不知道干啥用的, 没用过, 见过存 session id 的)
- access token(用来做权限认证, 生命周期相对比较短)
- refresh token(用来生成新的 access token, 生命周期相对比较长)
token 特点有哪些
1. 安全性能更好
之前列举了 cookie 和 session 的安全性能问题, 那么 token 为啥就安全了? 首先 token 是程序员自己写入 http headers 里的, 并不是像 cookie 一样自动写入 http 的 headers 里的, 如果没必要的我们不写入, XSS 攻击方式是获取不到 token 的.
2. 无状态(多个服务共享, 减轻服务器的鸭梨)
token 是无状态的, 也就是说 token 并不存入服务器中 (如果想存, 请便) 当服务器认证过后, 会生成一个 token 返回给用户, 并且存入浏览器缓存里(localstorage 等), 接下来的请求会我们会获取这个 token 并放入 authorization 内, 服务器接收到请求, 并解析 token 来判断当前请求是否有权限调用 API, 这里引出另一个概念鉴权.
3. 跨程序调用(避开同源策略)
当 token 变成无状态, 只要一个 token, 就可以在任何一个服务器上认证 (解析方式必须一致, 其实都是代码层面的东西) 于是我们可以采用一种设计方式叫, 分离认证服务与业务任务. 当我们可以通过认证服务器来获取 token, 然后发送给业务服务器的时候校验 token, 这时会出现两种方式, 第一种是在业务服务器校验 token, 另一种是毎一次请求通过中间层再发送给认证服务器进行校验, 两种设计方式各有优缺点, 不管怎样都是实现了跨程序共享 token, 咦没有跨域问题哦 (当然需要服务器配合嘤嘤嘤~) 到这里就引出另一个概念, OAuth 认证.
什么是 OAuth 认证
OAuth 是一个关于授权 (authorization) 的开放网络标准, 目前的版本是 2.0 版, 即 OAuth2.0
OAuth 的应用场景
一个公司往往会有很多系统, 比如 HR 系统, 你的请假吧, 比如公司的内部员工网站, 得有业余活动吧, 再比如公司好多产品, 但是会有个产品是可以登录所有网站的吧, 总不能好多产品分成好多认证系统吧, 能用就一个会节约多少人力物力呢~ 这里会引出一个概念 SSO(单点登录)SSO 其实是一种解决方案, 俺们公司的叫 AOS 系统. 这个通过一个系统登录定向到各个产品的授权过程就是 OAuth 认证.
OAuth 认证设计方式(这里列两种, 高大上的我也不知道):
业务服务器校验 token
优点: 相对于第二种不用每一次请求都要通过鉴权服务器, 可以保证 token 的新鲜度.
缺点: 效率低.
认证服务器校验 token
优点: 效率高
缺点: 如果是个第三方的鉴权服务 token 信息更新不及时
整个鉴权过程都是通过 access token, 由于有效时间比较短, 如果我想要半个月登录一次呢, 也就说半个月之内不需要重新登录, 怎么办 refresh token 的作用就产生了.
refresh token 做了些什么事儿呢
通过获取 access token 的有效时间, 判断当 access token 马上过期的时候, 这时候我可以通过 refresh token 从权限服务器获取新的 access token, 这都可以偷摸的做了, 反正使用者不会知道.
1. 客户端发送 refresh token 请求
2. 服务器发送 refresh token
token 的实现方式
现有的 token 解析方式其实并不唯一, 但是有个很出名的那就是 jwt(JSON Web token)
什么是 jwt
可以理解 jwt 是 token 的一种规则
jwt 的组成
1.Header 2.Payload 3.Signature
jwt 的特点(来自阮一峰)
JWT 默认是不加密, 但也是可以加密的. 生成原始 Token 以后, 可以用密钥再加密一次.
JWT 不加密的情况下, 不能将秘密数据写入 JWT.
JWT 不仅可以用于认证, 也可以用于交换信息. 有效使用 JWT, 可以降低服务器查询数据库的次数.
JWT 的最大缺点是, 由于服务器不保存 session 状态, 因此无法在使用过程中废止某个 token, 或者更改 token 的权限. 也就是说, 一旦 JWT 签发了, 在到期之前就会始终有效, 除非服务器部署额外的逻辑.
JWT 本身包含了认证信息, 一旦泄露, 任何人都可以获得该令牌的所有权限. 为了减少盗用, JWT 的有效期应该设置得比较短. 对于一些比较重要的权限, 使用时应该再次对用户进行认证.
为了减少盗用, JWT 不应该使用 HTTP 协议明码传输, 要使用 HTTPS 协议传输.
栗子(GitHub 登录本地站点)
技术栈: Node.JS
获取 GitHub 的 token 过程
1. 注册 GitHub 应用
找到 settings
找到 OAuths Apps
注册成功
2. 获取 GitHub 授权码
这里我们需要将注册过得 client id 跟回调函数当做参数传入
- handleGitHubLogin() {
- let path = `https://github.com/login/oauth/authorize?response_type=code&redirect_uri=${CommonUtil.Config.github.redirect_uri}&scope=user,repo&client_id=${CommonUtil.Config.github.client_id}`;
- location.href = path;
- }
这时候会跳到 GitHub 的授权页面
授权成功后会返回一个 code(授权码)
通过 code clientId clientSecret 获取 accessToken
- router.post("/oAuthValidate", (req, res) => {
- let { clientId, clientSecret, code } = req.body;
- axios({
- method: "post",
- url:
- "https://github.com/login/oauth/access_token?" +
- `client_id=${clientId}&` +
- `client_secret=${clientSecret}&` +
- `code=${code}`,
- headers: {
- accept: "application/json"
- }
- })
- .then(tokenResponse => {
- let accessToken = tokenResponse.data.access_token;
- getGitHubToken(accessToken, res);
- })
- .catch(e => {
- console.log(e);
- });
- });
由于整个应用程序采用 SAP, 所以整个流程通过前台获取最合理, 但前台获取 token 必然会出现一个问题就是跨域, 那么如何解决前端跨域资源共享问题呢, 这里提供一个解决方案就是 Gatekeeper,GitHub 自己去找吧, 使用方式之后补上.
通过 accessToken 获取 User 信息
- function getGitHubToken(accessToken, res) {
- axios({
- method: "get",
- url: `https://api.github.com/user`,
- headers: {
- accept: "application/json",
- Authorization: `token ${accessToken}`
- }
- })
- .then(result => {
- let token = jwt.sign(result.data, "my_token", { expiresIn: "1h" });
- util.responseClient(res, 200, 0, "获取 github token 成功", {
- profileInfo: result.data,
- accessToken: token
- });
- })
- .catch(e => {
- util.responseClient(res, 500, 0, "get github token failed.", {
- message: e
- });
- });
- }
这里为了使用 jwt, 我并没有使用 GitHub 的 accessToken, 而是在自己的应用程序里使用了 jwt, 可以自行选择.
返回给浏览器并且保存到缓存里
再次请求的时候进行 token 鉴权
- App.use(
- expressjwt({
- secret: "my_token",
- credentialsRequired: true, // 如果 false 则 authoriaztion 为空时也通过.
- getToken: function fromHeaderOrQuerystring(req) {
- if (
- req.headers.authorization &&
- req.headers.authorization.split("")[0] ==="Bearer"
- ) {
- var token = req.headers.authorization.split(" ")[1];
- return token;
- } else if (req.query && req.query.token) {
- return req.query.token;
- }
- return null;
- }
- }).unless({
- path: util.whiteList
- })
- );
- function checkPromisition(req, res, next) {
- if (1) {
- return next();
- } else {
- util.responseClient(res, 500, 0, "delete github token failed.", {
- log: "delete github token failed with http code 401."
- });
- }
- }
- App.use(function (err, req, res, next) {
- if (err.name === "UnauthorizedError") {
- util.responseClient(res, 403, 0, "invalid token...", {});
- }
- });
- App.use("/leavemessage",checkPromisition,require("./leavemessage"));
jwt 使用方式
引入 jwt 中间件
const jwt = require("jsonwebtoken");
jwt 生成
let token = jwt.sign(result.data, "my_token", { expiresIn: "1h" });
鉴权部分
引入中间件
const expressjwt = require("express-jwt");
具体细节看上面代码吧
这里有些问题首先 GitHub 并没有给我 refresh token, 这里是我没找到吗, 请知道的大佬指点一二, 其次 GitHub 没有暴露鉴权的接口吗
撤销 GitHub 的 accesstoken 方式
1. 清缓存
2. 手动自己上去清吧
时间不早了, 先写到这里吧, 如果哪里理解错的欢迎指正, 我会很感激不尽的.
你的关注是对我最大的支持~ 蟹蟹~
来源: https://www.cnblogs.com/moran1992/p/11419836.html