本文加少NodeJS鉴权的五种方案
HTTP Basic Authentication
session-cookie
Token
验证Json Web Token
验证OAuth
(开放授权)这种授权方式是浏览器遵守http
协议实现的基本授权方式,HTTP
协议进行通信的过程中,HTTP
协议定义了基本认证允许HTTP
服务器对客户端进行用户身份认证的方法。
httpGet /index.html HTTP/1.0 Host:www.xianzao.com
WWW-Authenticate: Basic realm="xianzao.com"
这句话是关键,如果没有客户端不会弹出用户名和密码输入界面)服务器返回的数据大抵如下:httpHTTP/1.0 401 Unauthorised WWW-Authenticate: Basic realm=”xianzao.com” Content-Type: text/html Content-Length: xxx
补充说明
http1.0
或1.1
规范的客户端(如FIREFOX
,Chrome
)收到401返回值时,将自动弹出一个登录窗口,要求用户输入用户名和密码。BASE64
加密方式加密,并将密文放入前一条请求信息中,则客户端发送的第一条请求信息则变成如下内容:shellGet /index.html HTTP/1.0 Host:www.xianzao.com Authorization: Basic d2FuZzp3YW5n
注:d2FuZzp3YW5n表示加密后的用户名及密码(用户名:密码 然后通过base64加密,加密过程是浏览器默认的行为,不需要我们人为加密,我们只需要输入用户名密码即可)
javascriptlet 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="xianzao"'
});
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=="xianzao"&&pass="xianzao"){
res.end("OK");
}else{
res.status(401).end();
}
}
})
app.listen(8000)
javascriptasync authenticaition_base() {
await axios.post('/Authentication_base')
}
优点:
缺点:
session
是会话的意思,浏览器第一次访问服务端,服务端就会创建一次会话,在会话中保存标识该浏览器的信息。
cookie
的区别就是 session
是缓存在服务端的,cookie
则是缓存在客户端cookie
的联系 二者一一对应,这样避免了cookie
明文存储用户信息的缺点seesion
,然后保存seesion
(我们可以将seesion
保存在 内存中,也可以保存在redis
中,推荐使用后者),然后给这个session
生成一个唯一的标识字符串,然后在 response header
中种下这个唯一标识字符串;sid
进行签名处理,避免客户端修改sid
;(非必需步骤)sid
保存在本地cookie
中,浏览器在下次http
请求的请求头中会带上该域名下的cookie
信息。cookie
中的sid
,然后根据这个sid
去找服务器端保存的该客户端的session
,然后判断该请求是否合法。javascriptconst 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: 'xianzao' }
res.end('Hello Cookie')
}
}
}).listen(3000)
token 是一个令牌,浏览器第一次访问服务端时会签发一张令牌,之后浏览器每次携带这张令牌访问服务端就会认证该令牌是否有效,只要服务端可以解密该令牌,就说明请求是合法的,令牌中包含的用户信息还可以区分不同身份的用户。一般 token 由用户信息、时间戳和由 hash 算法加密的签名构成。
Token
,再把这个 Token
发送给客户端;Token
以后可以把它存储起来,比如放在 Cookie
里或者Local Storage
里;Token
;Token(request头部添加Authorization)
,如果验证成功,就向客户端返回请求的数据 ,如果不成功返回401
错误码,鉴权失败;token的缺点:
token
认证比 session-cookie
更消耗性能;token
比 sid
大,更占带宽;两者对比,它们的区别显而易见:
token
认证不局限于 cookie
,这样就使得这种认证方式可以支持多种客户端,而不仅是浏览器。且不受同源策略的影响;cookie
就可以规避CSRF攻击;token
不需要存储,token
中已包含了用户信息,服务器端变成无状态,服务器端只需要根据定义的规则校验这个 token
是否合法就行。这也使得 token
的可扩展性更强。token
的解决方案有许多,常用的是JWT,JWT
的原理是,服务器认证以后,生成一个 JSON
对象,JSON
对象肯定不能裸传给用户,那谁都可以篡改这个对象发送请求。因此这个 JSON
对象会被服务器端签名加密后返回给用户,返回的内容就是一张令牌,以后用户每次访问服务器端就带着这张令牌。JSON
对象可能包含的内容就是用户的信息,用户的身份以及令牌的过期时间。在该网站JWT,可以解码或编码一个JWT。一个JWT形如:
它由三部分组成:Header
(头部)、Payload
(负载)、Signature
(签名)
Header
部分是一个JSON对象,描述JWT的元数据。一般描述信息为该Token的加密算法以及Token的类型。{“alg”: “HS256″,”typ”: “JWT”}
的意思就是,该token使用HS256加密,token类型是JWT。这个部分基本相当于明文,它将这个JSON对象做了一个Base64转码,变成一个字符串。Base64编码解码是有算法的,解码过程是可逆的。头部信息默认携带着两个字段;Payload
部分也是一个 JSON 对象,用来存放实际需要传递的数据。一般存放用户名、用户身份以及一些JWT的描述字段。它也只是做了一个Base64编码,因此肯定不能在其中存放秘密信息,比如说登录密码之类的;Signature
是对前面两个部分的签名,防止数据篡改,如果前面两段信息被人修改了发送给服务器端,此时服务器端是可利用签名来验证信息的正确性的。签名需要密钥,密钥是服务器端保存的,用户不知道。算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用”点”(.)分隔,就可以返回给用户;简单说,OAuth 就是一种授权机制。数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。系统从而产生一个短期的进入令牌(token),用来代替密码,供第三方应用使用。
令牌与密码的差异
令牌(token)与密码(password)的作用是一样的,都可以进入系统,但是有三点差异。
上面这些设计,保证了令牌既可以让第三方应用获得权限,同时又随时可控,不会危及系统安全。这就是 OAuth 2.0 的优点。
注意,只要知道了令牌,就能进入系统。系统一般不会再次确认身份,所以令牌必须保密,泄漏令牌与泄漏密码的后果是一样的。 这也是为什么令牌的有效期,一般都设置得很短的原因。
OAuth 2.0 对于如何颁发令牌的细节,规定得非常详细。具体来说,一共分成四种授权类型
在github-settings-developer settings
中创建一个OAuth App
。并填写相关内容。填写完成后Github
会给你一个客户端ID
和客户端密钥。
javascriptconst config = {
client_id: 'XXX',
client_secret: 'XXX'
}
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);
})
Github
,输入Github
的用户名密码,表示用户同意使用Github
身份登录第三方网站。此时就会带着授权码code
跳回第三方网站。跳回的地址在创建该OAuth
时已经设置好了;Github
请求access_token
令牌;Github
收到请求,向第三方网站颁发令牌;Github
一些请求的权限,比如说拿到用户信息,拿到这个用户信息之后就可以构建自己第三方网站的token
,做相关的鉴权操作;javascriptrouter.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=""/>
`
})
补充知识点: 单点登录
要求
登录认证机制
cookie 种在 顶级域名下面, 子应用服务器 通过session共享,如Spring-Session
本文作者:郭敬文
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!