前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >常见登录认证 DEMO

常见登录认证 DEMO

作者头像
JS菌
发布2019-07-30 10:03:00
2.8K0
发布2019-07-30 10:03:00
举报
文章被收录于专栏:JS菌JS菌

常见登录认证 DEMO

⭐️ 更多前端技术和知识点,搜索订阅号 JS 菌 订阅

basic auth

basic auth 是最简单的一种,将用户名和密码通过 form 表单提交的方式在 Http 的 Authorization 字段设置好并发送给后端验证

要点:

  • 不要通过 form 提交表单的默认方式发送请求,转而使用 fetch 或 ajax
  • 客户端注意设置 Authorization 字段的值为 'Basic xxx',通过该 Http 字段传递用户名密码
  • base64 的方法在客户端要注意兼容性 btoa ,建议使用现成的库如 'js-base64' 等,NodeJS 方面使用全局的 Buffer
  • 服务端验证失败后,注意返回 401,但不用返回 'WWW-Authenticate: Basic realm="…"' 避免浏览器出现弹窗
代码语言:javascript
复制
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>AMD</title>
</head>

<body>
  <script defer async="true" src="js/require.js" data-main="js/main"></script>
  <!-- BasicAuth -->
  <div>
    <form id="form" action="">
      <input type="text" name="username" id="username">
      <input type="password" name="password" id="password">
      <button id="login">login</button>
    </form>
  </div>
</body>

</html>
代码语言:javascript
复制
require.config({
  baseUrl: 'js/libs',
  paths: {
    'zepto': 'zepto.min',
  },
  shim: {
    'zepto': 'zepto',
  }
});

define(['zepto'], function ($) {
  let $form = $('#form')
  $form.on('submit', (e) => {
    e.preventDefault()
    $.ajax({
      // ajax 发送验证请求
      type: 'POST',
      url: '/login',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Authorization': 'Basic ' + btoa($('#username').val() + ':' + $('#password').val()),
        // 通过 Authorization 传递 base64 编码后的用户名密码
      },
      success: function (data) {
        console.dir(data) // 回调
      }
    })
  })
});

(忽略上述 ajax 加 requirejs 古老的写法 ? )

代码语言:javascript
复制
const Koa = require('koa')
const static = require('koa-static')
const router = require('koa-better-router')().loadMethods()
const koaBody = require('koa-body')

const app = new Koa()
app.use(koaBody())
app.use(router.middleware())
app.use(static('public'))
app.listen(8080)

router.post('/login', (ctx, next) => {
  // 省略从数据库中提取用户密码
  if (ctx.get('Authorization') === 'Basic ' + Buffer('fdsa:fdsa').toString('base64')) {
    // 获取 Authorization 字段 比对 base64 用户名密码
    ctx.body = 'secret'
    ctx.type = 'text/html'
    ctx.status = 200 // 匹配成功
  } else {
    ctx.status = 401 // 匹配失败
  }
  next()
})

cookie auth

这种登录方式实际上就是验证用户信息后,将验证 session 存放在 session cookie 内。一旦过期就需要用户重新登录

要点:

  • session cookie 用户信息容易被截取,需要设置 https
  • session 的会话时间内 cookie 有效,如需要长时生效需要设置过期时间 Max-age, Expires 等
代码语言:javascript
复制
const Koa = require('koa')
const static = require('koa-static')
const router = require('koa-better-router')().loadMethods()
const koaBody = require('koa-body')
const fs = require('fs')

const app = new Koa()
app.listen(8080)
app.use(koaBody())
app.use(router.middleware())
app.use(static('public'))

router.post('/login', (ctx, next) => {
  // 省略从数据库中提取用户密码
  let auth = ctx.request.body
  if (auth.username === 'fdsa', auth.password === 'fdsa') {
    // session cookie验证的用户名和密码属于明文传输,需要 https
    ctx.cookies.set('auth', auth.username) // 没有设置过期时间,属于Session Cookie
    // Koa 服务端默认设置的 cookie 是 session cookie
    ctx.status = 200
    ctx.type = 'application/json'
    ctx.body = { data: 1 }
    next()
  } else {
    ctx.status = 401
    next()
  }
})

router.get('/admin', (ctx, next) => {
  if (ctx.cookies.get('auth')) {
    ctx.body = 'secret'
    ctx.status = 200
    next()
  }
})

SessionSigned Cookie Auth

目前常用的方法,针对 cookie Auth 的改进

要点:

  • 经过签名的 Cookie 安全性提高,要注意加强对签名的密钥的保护
  • 可通过每次访问受权限限制的页面刷新 SessionCookie
  • Koa 建议使用 koa-session 库
代码语言:javascript
复制
const Koa = require('koa')
const static = require('koa-static')
const router = require('koa-better-router')().loadMethods()
const koaBody = require('koa-body')
const session = require('koa-session'); // session

const app = new Koa()
app.listen(8080)
app.use(koaBody())
app.use(router.middleware())
app.use(static('public'))
app.keys = ['session key'] // 签名
app.use(session({
  key: '_session',
  signed: true, // 签名,经过签名的 cookie 安全性比普通 cookie 高
  maxAge: 'session' // 设置过期时间 session 表示当前会话有效
}, app))

router.post('/login', (ctx, next) => {
  // 省略从数据库中提取用户密码
  let auth = ctx.request.body
  if (auth.username === 'fdsa', auth.password === 'fdsa') {
    // 登陆成功,username 结合签名放入到 session cookie 中用于将来鉴别身份
    ctx.session.user = auth.username
    ctx.status = 200
    ctx.type = 'application/json'
    ctx.body = { data: 1 }
    next()
  } else {
    ctx.status = 401
    next()
  }
})

router.get('/admin', (ctx, next) => {
  if (ctx.session.user === 'fdsa') {
    let count = ctx.session.count || 0
    // 每次都将刷新 session cookie 存在客户端的 session cookie 会随着刷新动作而变化
    ctx.session.count = ++count
    ctx.body = 'visit count: ' + count
    ctx.status = 200
    next()
  } else {
    ctx.status = 401
    next()
  }
})

JWT token auth

此种令牌登录方式比较主流,用户输入登录信息,发送给服务器验证,通过后返回 token,token 可以存储在前端任何地方。随后用户请求需要验证的资源,发送 http 请求的同时将 token 放置在请求头中,后端解析 JWT 并判断令牌是否新鲜并有效

要点:

  • 用户输入其登录信息
  • 服务器验证信息是否正确,并返回已签名的token
  • token储在客户端,常见的是存储在local storage中,但也可以存储在session或cookie中
  • 之后的HTTP请求都将token添加到请求头里
  • 服务器解码JWT,并且如果令牌有效,则接受请求
  • 一旦用户注销,令牌将在客户端被销毁,不需要与服务器进行交互一个关键是,令牌是无状态的。后端服务器不需要保存令牌或当前session的记录。

1. 基本介绍

认证流程 https://jothy1023.github.io/2016/11/04/server-authentication-using-jwt/

首先,拥有某网站账号的某 client 使用自己的账号密码发送 post 请求 login,由于这是首次接触,server 会校验账号与密码是否合法,如果一致,则根据密钥生成一个 token 并返回,client 收到这个 token 并保存在本地的 localStorage。在这之后,需要访问一个受保护的路由或资源时,而只要附加上你保存在本地的 token(通常使用 Bearer 属性放在 Header 的 Authorization 属性中),server 会检查这个 token 是否仍有效,以及其中的校验信息是否正确,再做出相应的响应。

优点是自包含不需要服务端储存、无状态客户端销毁即可实现用户注销,以及跨域、易于实现CDN,比cookie更支持原生移动端应用

JWT 的三个部分:header头, payload载荷, signature签名,即:xxx.yyy.zzz

header部分(base64之前):

代码语言:javascript
复制
{
    "alg": "SHA256", // algorithm 哈希算法主要有 HMAC、SHA256、RSA等等
    "typ": "JWT" // type 令牌类型,应当设置为 JWT
}

payload部分(base64之前):

三种payload声明类型:registered, public, private,其中,registered 还包括 iss(issuer),sub(subject),aud(audience),exp(expiration time),nbf(not before),iat(issued at),jti(JWT ID)

代码语言:javascript
复制
{
    "sub": "subject id",
    "exp": "1300819380",
    "role": "admin"
}

signature部分

如果使用 HMACSHA256 方式:

代码语言:javascript
复制
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

这三个部分之间加入.即完成了JWT的构造

需要注意,header部分和payload部分只是经过了base64的编码,并未加密,不能在载荷部分保存涉及安全的东西

JWT 令牌通常通过 HTTP 的 Authorization: Bearer <token> 来传输,并存储在 session cookie, localStorage 等地方

2. 例子

代码语言:javascript
复制
<!-- JWT Token SessionCookie Auth -->
  <div>
    <form id="form" action="">
      <input type="text" name="username" id="username">
      <input type="password" name="password" id="password">
      <button id="login">login</button>
    </form>
  </div>
  <!-- JWT Token LocalStorage Auth -->
  <div>
    <pre id="pre"></pre>
    <button id="getData">getData</button>
  </div>

server:

代码语言:javascript
复制
const Koa = require('koa')
const static = require('koa-static')
const router = require('koa-better-router')().loadMethods()
const koaBody = require('koa-body')
const jwt = require('jsonwebtoken')
const fs = require('fs')

const app = new Koa()
app.listen(8080)
app.use(koaBody())
app.use(router.middleware())
app.use(static('public'))
app.keys = ['private key']

router.post('/login', (ctx, next) => {
  // 省略从数据库中提取用户密码
  if (ctx.request.body) {
    if (ctx.request.body.username === 'fdsa', ctx.request.body.password === 'fdsa') {
      // 生成 jwt token
      let token = jwt.sign({ username: 'fdsa', role: 'admin' }, app.keys[0], { algorithm: 'HS256' })
      ctx.cookies.set('koa:token', token)
      ctx.body = { data: 1, token }
      ctx.status = 200
    } else {
      ctx.body = { data: 0, err: 'error' }
      ctx.status = 401
    }
  } else {
    ctx.status = 401
  }
  next()
})

// 通过 session cookie 验证令牌
router.get('/admin', (ctx, next) => {
  let token = ctx.cookies.get('koa:token')
  if (token) {
    // 验证 jwt 令牌
    jwt.verify(token, app.keys[0], function (err, decoded) {
      if (err) {
        ctx.status = 401
        console.log(err)
      } else {
        ctx.body = `welcome ${decoded.role}, ${decoded.username}`
        ctx.type = 'text/html'
        ctx.status = 200
      }
    });
  } else {
    ctx.status = 401
  }
})

// 通过 Authorization 验证令牌
router.get('/secret.json', (ctx, next) => {
  let token = ctx.get('Authorization').split(' ')[1]
  if (token) {
    jwt.verify(token, app.keys[0], function (err, decoded) {
      if (err) {
        ctx.status = 401
        console.log(err)
      } else {
        if (decoded.role === 'admin') {
          let msg = fs.readFileSync('./secret.json', 'utf-8')
          ctx.body = { data: 1, msg }
          ctx.status = 200
        } else {
          ctx.status = 401
        }
      }
    })
  } else {
    ctx.status = 401
  }
})

client:

代码语言:javascript
复制
require.config({
  baseUrl: 'js/libs',
  paths: {
    'zepto': 'zepto.min',
  },
  shim: {
    'zepto': 'zepto',
  }
});

(在此忽略此前写的古老的 requireJS ?)

define(['zepto'], function ($) {
  let $form = $('#form')
  $form.on('submit', (e) => {
    e.preventDefault()
    $.ajax({
      // ajax 发送验证请求
      type: 'POST',
      url: '/login',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      data: {
        username: $('#username').val(),
        password: $('#password').val()
      },
      success: function (data) {
        if (data.data === 1) {
          // 返回的token用于发起请求受限资源
          window.localStorage.setItem('koa:token', data.token)
          location.replace('./admin')
        }
      }
    })
  })

  $('#getData').on('click', (e) => {
    e.preventDefault()
    $.ajax({
      type: 'GET',
      url: '/secret.json',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Authorization': 'Bearer ' + window.localStorage.getItem('koa:token')
        // 客户端设置 Authorization Token 令牌
      },
      success: function (data) {
        if (data.data === 1) {
          // 令牌认证后的操作
          $('#pre').text(JSON.parse(data.msg).key)
        }
      }
    })
  })
});

OAuth

OAuth 是目前用的最多的登录认证方式,用户首先确认授权登录,通过一连串方法获取 access token,最后通过 token 请求各种受限的资源

阮一峰老哥的文章清除讲解了这种方法的工作方式:

原理:理解OAuth 2.0 http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html

要点:

  • 用户首先确认授权
  • 再获取 code 临时凭证
  • 通过 code 临时凭证,换取 access token
  • 最后由 token 再获取受限的资源

下面封装了一个基于微博的 OAuth 认证:

代码语言:javascript
复制
let axios = require('axios');

const Koa = require('koa')
const static = require('koa-static')
const router = require('koa-better-router')().loadMethods()
const koaBody = require('koa-body')
const jwt = require('jsonwebtoken')
const fs = require('fs')

const app = new Koa()
app.listen(8080)
app.use(koaBody())
app.use(router.middleware())
app.use(static('public'))

app.keys = ['appid', 'secretid']

class WeiboApi {
  // 获取 code 临时兑换券
  constructor(query) {
    this.code = query.code
  }
  // 根据 code 获取 token
  getToken() {
    return new Promise((resolve, reject) => {
      axios({
        method: 'POST',
        url: `https://api.weibo.com/oauth2/access_token?client_id=${app.keys[0]}&client_secret=${app.keys[1]}&grant_type=authorization_code&redirect_uri=http://127.0.0.1:8080/auth&code=${this.code}`
      }).then(d => { resolve(d) }).catch(e => { reject(e) })
    })
  }
  // 根据 token 获取 相关的用户信息
  getUserInfo(token) {
    return new Promise((resolve, reject) => {
      axios({
        method: 'GET',
        url: `https://api.weibo.com/2/users/show.json?access_token=${token.data.access_token}&uid=${token.data.uid}`
      }).then(d => { resolve(d) }).catch(e => { reject(e) })
    })
  }
  // 根据 token 获取 用户的关注人列表
  getUserFriends(token) {
    return new Promise((resolve, reject) => {
      axios({
        method: 'GET',
        url: `https://api.weibo.com/2/friendships/friends.json?access_token=${token.data.access_token}&uid=${token.data.uid}`
      }).then(d => { resolve(d) }).catch(e => { reject(e) })
    })
  }
}

router.get('/auth', async (ctx, next) => {
  if (ctx.query.code) {
    let weiboApi = new WeiboApi(ctx.request.query)
    let token = await weiboApi.getToken()
    let userInfo = await weiboApi.getUserInfo(token)
    let userFriends = await weiboApi.getUserFriends(token)
    // 根据用户信息,查询数据库,登录逻辑
    ctx.body = { userInfo: userInfo.data, userFriends: userFriends.data }
  } else {
    ctx.status = 401
  }
})
代码语言:javascript
复制
<!-- OAuth2.0 Weibo -->
  <a href="https://api.weibo.com/oauth2/authorize?client_id=HEREISYOURAPPID&response_type=code&redirect_uri=http://127.0.0.1:8080/auth">微博登录</a>
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-07-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 JS菌 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 常见登录认证 DEMO
    • basic auth
      • cookie auth
        • SessionSigned Cookie Auth
          • JWT token auth
            • 1. 基本介绍
            • 2. 例子
          • OAuth
          相关产品与服务
          访问管理
          访问管理(Cloud Access Management,CAM)可以帮助您安全、便捷地管理对腾讯云服务和资源的访问。您可以使用CAM创建子用户、用户组和角色,并通过策略控制其访问范围。CAM支持用户和角色SSO能力,您可以根据具体管理场景针对性设置企业内用户和腾讯云的互通能力。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档