目前咱们经常使用的鉴权有四种:javascript
这种受权方式是浏览器遵照http协议实现的基本受权方式,HTTP协议进行通讯的过程当中,HTTP协议定义了基本认证容许HTTP服务器对客户端进行用户身份证的方法。html
认证过程:前端
一、 客户端向服务器请求数据,请求的内容多是一个网页或者是一个ajax异步请求,此时,假设客户端还没有被验证,则客户端提供以下请求至服务器:java
Get /index.html HTTP/1.0
Host:www.google.com
复制代码
二、 服务器向客户端发送验证请求代码401,(WWW-Authenticate: Basic realm=”google.com”这句话是关键,若是没有客户端不会弹出用户名和密码输入界面)服务器返回的数据大抵以下:node
HTTP/1.0 401 Unauthorised
Server: SokEvo/1.0
WWW-Authenticate: Basic realm=”google.com”
Content-Type: text/html
Content-Length: xxx
复制代码
三、 当符合http1.0或1.1规范的客户端(如IE,FIREFOX)收到401返回值时,将自动弹出一个登陆窗口,要求用户输入用户名和密码。jquery
四、 用户输入用户名和密码后,将用户名及密码以BASE64加密方式加密,并将密文放入前一条请求信息中,则客户端发送的第一条请求信息则变成以下内容:ios
Get /index.html HTTP/1.0
Host:www.google.com
Authorization: Basic d2FuZzp3YW5n
复制代码
注:d2FuZzp3YW5n
表示加密后的用户名及密码(用户名:密码 而后经过base64加密,加密过程是浏览器默认的行为,不须要咱们人为加密,咱们只须要输入用户名密码便可)git
五、 服务器收到上述请求信息后,将 Authorization
字段后的用户信息取出、解密,将解密后的用户名及密码与用户数据库进行比较验证,如用户名及密码正确,服务器则根据请求,将所请求资源发送给客户端程序员
效果: 客户端未未认证的时候,会弹出用户名密码输入框,这个时候请求时属于 pending
状态,当用户输入用户名密码的时候客户端会再次发送带 Authentication
头的请求。github
server.js
let express = require("express");
let app = express();
app.use(express.static(__dirname+'/public'));
app.get("/Authentication_base",function(req,res){
console.log('req.headers.authorization:',req.headers)
if(!req.headers.authorization){
res.set({
'WWW-Authenticate':'Basic realm="wang"'
});
res.status(401).end();
}else{
let base64 = req.headers.authorization.split(" ")[1];
let userPass = new Buffer(base64, 'base64').toString().split(":");
let user = userPass[0];
let pass = userPass[1];
if(user=="wang"&&pass="wang"){
res.end("OK");
}else{
res.status(401).end();
}
}
})
app.listen(9090)
复制代码
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>HTTP Basic Authentication</title>
</head>
<body>
<div></div>
<script src="js/jquery-3.2.1.js"></script>
<script> $(function(){ send('./Authentication_base'); }) var send = function(url){ $.ajax({ url : url, method : 'GET', }); } </script>
</body>
</html>
复制代码
优势: 基本认证的一个优势是基本上全部流行的网页浏览器都支持基本认证。基本认证不多在可公开访问的互联网网站上使用,有时候会在小的私有系统中使用(如路由器网页管理接口)。后来的机制HTTP摘要认证是为替代基本认证而开发的,容许密钥以相对安全的方式在不安全的通道上传输。 程序员和系统管理员有时会在可信网络环境中使用基本认证,使用Telnet或其余明文网络协议工具手动地测试Web服务器。这是一个麻烦的过程,可是网络上传输的内容是人可读的,以便进行诊断。
缺点: 虽然基本认证很是容易实现,但该方案建立在如下的假设的基础上,即:客户端和服务器主机之间的链接是安全可信的。特别是,若是没有使用SSL/TLS
这样的传输层安全的协议,那么以明文传输的密钥和口令很容易被拦截。该方案也一样没有对服务器返回的信息提供保护。 现存的浏览器保存认证信息直到标签页或浏览器被关闭,或者用户清除历史记录。HTTP没有为服务器提供一种方法指示客户端丢弃这些被缓存的密钥。这意味着服务器端在用户不关闭浏览器的状况下,并无一种有效的方法来让用户注销。
Http协议是一个无状态的协议,服务器不会知道究竟是哪一台浏览器访问了它,所以须要一个标识用来让服务器区分不一样的浏览器。cookie
就是这个管理服务器与客户端之间状态的标识。
cookie
的原理是,浏览器第一次向服务器发送请求时,服务器在 response
头部设置 Set-Cookie
字段,浏览器收到响应就会设置 cookie
并存储,在下一次该浏览器向服务器发送请求时,就会在 request
头部自动带上 Cookie
字段,服务器端收到该 cookie
用以区分不一样的浏览器。固然,这个 cookie
与某个用户的对应关系应该在第一次访问时就存在服务器端,这时就须要 session
了。
const http = require('http')
http.createServer((req, res) => {
if (req.url === '/favicon.ico') {
return
} else {
res.setHeader('Set-Cookie', 'name=zhunny')
res.end('Hello Cookie')
}
}).listen(3000)
复制代码
session
是会话的意思,浏览器第一次访问服务端,服务端就会建立一次会话,在会话中保存标识该浏览器的信息。它与 cookie
的区别就是 session
是缓存在服务端的,cookie
则是缓存在客户端,他们都由服务端生成,为了弥补 Http
协议无状态的缺陷。
const http = require('http')
//此时session存在内存中
const session = {}
http.createServer((req, res) => {
const sessionKey = 'sid'
if (req.url === '/favicon.ico') {
return
} else {
const cookie = req.headers.cookie
//再次访问,对sid请求进行认证
if (cookie && cookie.indexOf(sessionKey) > -1) {
res.end('Come Back')
}
//首次访问,生成sid,保存在服务器端
else {
const sid = (Math.random() * 9999999).toFixed()
res.setHeader('Set-Cookie', `${sessionKey}=${sid}`)
session[sid] = { name: 'zhunny' }
res.end('Hello Cookie')
}
}
}).listen(3000)
复制代码
redis是一个键值服务器,能够专门放session的键值对。如何在koa中使用session:
const koa = require('koa')
const app = new koa()
const session = require('koa-session')
const redisStore = require('koa-redis')
const redis = require('redis')
const redisClient = redis.createClient(6379, 'localhost')
const wrapper = require('co-redis')
const client = wrapper(redisClient)
//加密sessionid
app.keys = ['session secret']
const SESS_CONFIG = {
key: 'kbb:sess',
//此时让session存储在redis中
store: redisStore({ client })
}
app.use(session(SESS_CONFIG, app))
app.use(ctx => {
//查看redis中的内容
redisClient.keys('*', (errr, keys) => {
console.log('keys:', keys)
keys.forEach(key => {
redisClient.get(key, (err, val) => {
console.log(val)
})
})
})
if (ctx.path === '/favicon.ico') return
let n = ctx.session.count || 0
ctx.session.count = ++n
ctx.body = `第${n}次访问`
})
app.listen(3000)
复制代码
使用session-cookie作登陆认证时,登陆时存储session,退出登陆时删除session,而其余的须要登陆后才能操做的接口须要提早验证是否存在session,存在才能跳转页面,不存在则回到登陆页面。
在koa中作一个验证的中间件,在须要验证的接口中使用该中间件。
//前端代码
async login() {
await axios.post('/login', {
username: this.username,
password: this.password
})
},
async logout() {
await axios.post('/logout')
},
async getUser() {
await axios.get('/getUser')
}
复制代码
//中间件 auth.js
module.exports = async (ctx, next) => {
if (!ctx.session.userinfo) {
ctx.body = {
ok: 0,
message: "用户未登陆" };
} else {
await next();
} };
//须要验证的接口
router.get('/getUser', require('auth'), async (ctx) => {
ctx.body = {
message: "获取数据成功",
userinfo: ctx.session.userinfo
}
})
//登陆
router.post('/login', async (ctx) => {
const {
body
} = ctx.request
console.log('body', body)
//设置session
ctx.session.userinfo = body.username;
ctx.body = {
message: "登陆成功"
}
})
//登出
router.post('/logout', async (ctx) => {
//设置session
delete ctx.session.userinfo
ctx.body = {
message: "登出系统"
}
})
复制代码
token
是一个令牌,浏览器第一次访问服务端时会签发一张令牌,以后浏览器每次携带这张令牌访问服务端就会认证该令牌是否有效,只要服务端能够解密该令牌,就说明请求是合法的,令牌中包含的用户信息还能够区分不一样身份的用户。通常 token
由用户信息、时间戳和由 hash
算法加密的签名构成。
Token
,再把这个 Token
发送给客户端Token
之后能够把它存储起来,好比放在 Cookie
里或者Local Storage
里Token
Token
(request头部添加Authorization),若是验证成功,就向客户端返回请求的数据 ,若是不成功返回401错误码,鉴权失败。session-cookie
的缺点: (1)认证方式局限于在浏览器中使用,cookie
是浏览器端的机制,若是在app端就没法使用 cookie
。 (2)为了知足全局一致性,咱们最好把 session
存储在 redis
中作持久化,而在分布式环境下,咱们可能须要在每一个服务器上都备份,占用了大量的存储空间。 (3)在不是 Https
协议下使用 cookie
,容易受到 CSRF 跨站点请求伪造攻击。
token的缺点: (1)加密解密消耗使得 token
认证比 session-cookie
更消耗性能。 (2)token
比 sessionId
大,更占带宽。
二者对比,它们的区别显而易见: (1)token
认证不局限于 cookie
,这样就使得这种认证方式能够支持多种客户端,而不只是浏览器。且不受同源策略的影响。 (2)不使用 cookie
就能够规避CSRF攻击。 (3)token
不须要存储,token
中已包含了用户信息,服务器端变成无状态,服务器端只须要根据定义的规则校验这个 token
是否合法就行。这也使得 token
的可扩展性更强。
基于 token
的解决方案有许多,经常使用的是JWT
,JWT
的原理是,服务器认证之后,生成一个 JSON
对象,这个 JSON
对象确定不能裸传给用户,那谁均可以篡改这个对象发送请求。所以这个 JSON
对象会被服务器端签名加密后返回给用户,返回的内容就是一张令牌,之后用户每次访问服务器端就带着这张令牌。
这个 JSON
对象可能包含的内容就是用户的信息,用户的身份以及令牌的过时时间。
在该网站JWT,能够解码或编码一个JWT。一个JWT形如:
它由三部分组成:Header(头部)、Payload(负载)、Signature(签名)。
//前端代码
//axios的请求拦截器,在每一个request请求头上加JWT认证信息
axios.interceptors.request.use(
config => {
const token = window.localStorage.getItem("token");
if (token) {
// 判断是否存在token,若是存在的话,则每一个http header都加上token
// Bearer是JWT的认证头部信息
config.headers.common["Authorization"] = "Bearer " + token;
}
return config;
},
err => {
return Promise.reject(err);
}
);
//登陆方法:在将后端返回的JWT存入localStorage
async login() {
const res = await axios.post("/login-token", {
username: this.username,
password: this.password
});
localStorage.setItem("token", res.data.token);
},
//登出方法:删除JWT
async logout() {
localStorage.removeItem("token");
},
async getUser() {
await axios.get("/getUser-token");
}
复制代码
//后端代码
const jwt = require("jsonwebtoken");
const jwtAuth = require("koa-jwt");
//用来签名的密钥
const secret = "it's a secret";
router.post("/login-token", async ctx => {
const { body } = ctx.request;
//登陆逻辑,略,即查找数据库,若该用户和密码合法,即将其信息生成一个JWT令牌传给用户
const userinfo = body.username;
ctx.body = {
message: "登陆成功",
user: userinfo,
// 生成 token 返回给客户端
token: jwt.sign(
{
data: userinfo,
// 设置 token 过时时间,一小时后,秒为单位
exp: Math.floor(Date.now() / 1000) + 60 * 60
},
secret
)
};
});
//jwtAuth这个中间件会拿着密钥解析JWT是否合法。
//而且把JWT中的payload的信息解析后放到state中,ctx.state用于中间件的传值。
router.get(
"/getUser-token",
jwtAuth({
secret
}),
async ctx => {
// 验证经过,state.user
console.log(ctx.state.user);
ctx.body = {
message: "获取数据成功",
userinfo: ctx.state.user.data
};
}
)
//这种密码学的方式使得token不须要存储,只要服务端能拿着密钥解析出用户信息,就说明该用户是合法的。
//若要更进一步的权限验证,须要判断解析出的用户身份是管理员仍是普通用户。
复制代码
OAuth(Open Authorization)是一个开放标准,容许用户受权第三方网站访问他们存储在另外的服务提供者上的信息,而不须要将用户名和密码提供给第三方网站或分享他们数据的全部内容,为了保护用户数据的安全和隐私,第三方网站访问用户数据前都须要显式的向用户征求受权。咱们常见的提供OAuth认证服务的厂商有支付宝,QQ,微信。
OAuth协议又有1.0和2.0两个版本。相比较1.0版,2.0版整个受权验证流程更简单更安全,也是目前最主要的用户身份验证和受权方式。
关于OAuth相关文章,能够查看 OAuth 2.0 的一个简单解释、理解OAuth 2.0、OAuth 2.0 的四种方式
OAuth就是一种受权机制。数据的全部者告诉系统,赞成受权第三方应用进入系统,获取这些数据。系统从而产生一个短时间的进入令牌(token),用来代替密码,供第三方应用使用。
OAuth有四种获取令牌的方式,无论哪种受权方式,第三方应用申请令牌以前,都必须先到系统备案,说明本身的身份,而后会拿到两个身份识别码:客户端 ID(client ID)和客户端密钥(client secret)。这是为了防止令牌被滥用,没有备案过的第三方应用,是不会拿到令牌的。
在先后端分离的情境下,咱们常使用受权码方式,指的是第三方应用先申请一个受权码,而后再用该码获取令牌。
咱们用例子来理清受权码方式的流程。
在github-settings-developer settings
中建立一个OAuth App。并填写相关内容。填写完成后Github会给你一个客户端ID和客户端密钥。
const config = {
client_id: '28926186082164bbea8f',
client_secret: '07c4fdae1d5ca458dae3345b6d77a0add5a785ca'
}
router.get('/github/login', async (ctx) => {
var dataStr = (new Date()).valueOf();
//重定向到认证接口,并配置参数
var path = "https://github.com/login/oauth/authorize";
path += '?client_id=' + config.client_id;
//转发到受权服务器
ctx.redirect(path);
})
复制代码
router.get('/github/callback', async (ctx) => {
console.log('callback..')
const code = ctx.query.code;
const params = {
client_id: config.client_id,
client_secret: config.client_secret,
code: code
}
let res = await axios.post('https://github.com/login/oauth/access_token', params)
const access_token = querystring.parse(res.data).access_token
res = await axios.get('https://api.github.com/user?access_token=' + access_token)
console.log('userAccess:', res.data)
ctx.body = ` <h1>Hello ${res.data.login}</h1> <img src="${res.data.avatar_url}" alt=""/> `
})
复制代码