⭐️ 更多前端技術和知識點,搜索訂閱號
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 的技術文章,只談技術不談八卦 😊