OAuth2.0授权详解

  • 2019 年 10 月 3 日
  • 笔记

学习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登录本地站点)

技术栈:nodejs

获取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%2Crepo&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.手动自己上去清吧

时间不早了,先写到这里吧,如果哪里理解错的欢迎指正,我会很感激不尽的。

你的关注是对我最大的支持~蟹蟹~