# oauth2

# 什么是OAuth2

OAuthOpen Authorization,开放授权)是为用户资源的授权定义了一个安全、开放及简单的标准,第三方无需知道用户的账号及密码,就可获取到用户的授权信息。

OAuth2.0OAuth协议的延续版本,但不向后兼容OAuth 1.0,即完全废止了OAuth1.0

OAuth2对比OAuth1,主要改变有下面几点:

  1. 取消繁琐的签名,全部改用HTTPS

  2. ATOKaccess_token)从原来的永久令牌变为临时令牌,增加RefreshToken

  3. 取消获取RequestToken的步骤

  4. 提供了多种场景的授权流程

oauth2

# 步骤说明

以我们一个应用【小墙】为例,作为平台的子系统,账户需要使用gitlab的。

  1. 用户访问小墙,小墙需要用户登录验证

  2. 小墙发起gitlab授权请求

https://XX.com/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&state=STATE

其中,STATE是一个随机的字符串,存储到session中,用来在回调api中校验是否来自gitlab授权后重定向的真实请求。这是为了防止CSRFCross-site request forgery跨站点请求伪造)攻击。

  1. 跳转到小墙服务器的api(上一步的REDIRECT_URI,例如http://localhost:3000/api/login_callback?code=XXX),urlcode是上一步gitlab返回的参数。 根据它和相关信息,请求得到access_token
https://XX.com/oauth/token?appid=APPID&secret=SECRET&code=XXX&grant_type=authorization_code
  1. 根据access_tokengitlab获取用户信息
https://XX.com/oauth/userinfo?access_token=ACCESS_TOKEN
  1. 将得到的用户信息与access_token一起加密,生成一个唯一的token值返回给前台,每次接口请求都携带这个token值,后台解密校验是否正确
const info = {
    userId: user.id,
    token: access_token,
    email: user.email,
    expires: expireTime(7) //默认7天过期
};
const token = encode(info);

解密使用decode。这两个方法, 一般使用非对称加密的方式:

const NodeRSA = require('node-rsa');
const fs = require('fs').promises;
let privatePem; // 私钥文件缓存
let publicPem; // 公钥文件缓存
const getPrivatePem = async function () {
  if (!privatePem) {
    privatePem = await fs.readFile('./config/pem/private.pem');
  }
  return privatePem;
};

const getPublicPem = async function () {
  if (!publicPem) {
    publicPem = await fs.readFile('./config/pem/public.pem');
  }
  return publicPem;
};

/**
 * 加密
 */
export const encode = async function (code: any): Promise<string> {
  const data = await getPrivatePem();
  const key = new NodeRSA(data);
  return key.encryptPrivate(code, 'base64');
};

/**
 * 解密
 */
export const decode = async function (code: string): Promise<any> {
  let result: any = false;
  try {
    const data = await getPublicPem();
    const key = new NodeRSA(data);
    result = key.decryptPublic(code, 'utf8'); //如果编译失败会报错
    return JSON.parse(result);
  } catch (e) {
    return result;
  }
};

参考: