⭐️ 更多前端技术和知识点,搜索订阅号
JS 菌
订阅html
basic auth 是最简单的一种,将用户名和密码经过 form 表单提交的方式在 Http 的 Authorization 字段设置好并发送给后端验证前端
要点:ios
<!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>
复制代码
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 古老的写法 😆 )git
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()
})
复制代码
这种登陆方式实际上就是验证用户信息后,将验证 session 存放在 session cookie 内。一旦过时就须要用户从新登陆github
要点:web
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()
}
})
复制代码
目前经常使用的方法,针对 cookie Auth 的改进ajax
要点:算法
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()
}
})
复制代码
此种令牌登陆方式比较主流,用户输入登陆信息,发送给服务器验证,经过后返回 token,token 能够存储在前端任何地方。随后用户请求须要验证的资源,发送 http 请求的同时将 token 放置在请求头中,后端解析 JWT 并判断令牌是否新鲜并有效数据库
要点:json
local storage
中,但也能够存储在session或cookie中认证流程 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以前):
{
"alg": "SHA256", // algorithm 哈希算法主要有 HMAC、SHA25六、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)
{
"sub": "subject id",
"exp": "1300819380",
"role": "admin"
}
复制代码
signature部分
若是使用 HMACSHA256 方式:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
复制代码
这三个部分之间加入.
即完成了JWT的构造
须要注意,header部分和payload部分只是通过了base64的编码,并未加密,不能在载荷部分保存涉及安全的东西
JWT 令牌一般经过 HTTP 的 Authorization: Bearer <token>
来传输,并存储在 session cookie, localStorage 等地方
<!-- 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:
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:
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 是目前用的最多的登陆认证方式,用户首先确认受权登陆,经过一连串方法获取 access token,最后经过 token 请求各类受限的资源
阮一峰老哥的文章清除讲解了这种方法的工做方式:
原理:理解OAuth 2.0 http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html
要点:
下面封装了一个基于微博的 OAuth 认证:
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
}
})
复制代码
<!-- 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>
复制代码
请关注个人订阅号,不按期推送有关 JS 的技术文章,只谈技术不谈八卦 😊