最近在重构公司以前产品的前端代码,摈弃了以前的session-cookie鉴权方式,采用token鉴权,忙里偷闲觉得有必要对几种常见的鉴权方式整理一下。
目前我们常用的鉴权有四种:
HTTP Basic Authentication
这种授权方式是浏览器遵守http协议实现的基本授权方式,HTTP协议进行通信的过程中,HTTP协议定义了基本认证认证允许HTTP服务器对客户端进行用户身份证的方法。
1.客户端向服务器请求数据,请求的内容可能是一个网页或者是一个ajax异步请求,此时,假设客户端尚未被验证,则客户端提供如下请求至服务器:
Get /index.html HTTP/1.0 Host:www.google.com
2.服务器向客户端发送验证请求代码401,(
WWW-Authenticate: Basic realm=”google.com”
这句话是关键,如果没有客户端不会弹出用户名和密码输入界面)服务器返回的数据大抵如下:
HTTP/1.0 401 Unauthorised Server: SokEvo/1.0 WWW-Authenticate: Basic realm=”google.com” Content-Type: text/html Content-Length: xxx
3.当符合http1.0或1.1规范的客户端(如IE,
FIREFOX
)收到401返回值时,将自动弹出一个登录窗口,要求用户输入用户名和密码。
4.用户输入用户名和密码后,将用户名及密码以
BASE64
加密方式加密,并将密文放入前一条请求信息中,则客户端发送的第一条请求信息则变成如下内容:
Get /index.html HTTP/1.0 Host:www.google.com Authorization: Basic d2FuZzp3YW5n
注:
d2FuZzp3YW5n
表示加密后的用户名及密码(用户名:密码 然后通过base64加密,加密过程是浏览器默认的行为,不需要我们人为加密,我们只需要输入用户名密码即可)
5.服务器收到上述请求信息后,将
Authorization
字段后的用户信息取出、解密,将解密后的用户名及密码与用户数据库进行比较验证,如用户名及密码正确,服务器则根据请求,将所请求资源发送给客户端
客户端未未认证的时候,会弹出用户名密码输入框,这个时候请求时属于pending状态,这个时候其实服务当用户输入用户名密码的时候客户端会再次发送带
Authentication
头的请求。
认证成功:
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
html ><html > <head
> <meta charset ="UTF-8" > <title > HTTP Basic Authenticationtitle > 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 >
当然有登陆就有注销,我们会发现当我们认证成功后每次请求请求头都会带上
Authentication
及里面的内容,那么如何做到让这次登陆失效的?
网上查了半天,目前最有效的方式就是在注销操作的时候,专门在服务器设置一个专门的注销账号,当接收到的
Authentication
信息为注销用户名密码的时候纠就带便注销成功了,而客户端在注销操作的时候,手动的的去修改请求头重的
Authentication
,将他设置未服务器默认的注销账号和密码。
通过上面的简单讲解 其实我们已经可以返现这种验证方式的缺陷加密方式简单,仅仅是base64加密,这种加密方式是可逆的。同时在每个请求的头上都会附带上用户名和密码信息,这样在外网是很容易被嗅探器探测到的。
正式因为这样,这种加密方式一般多被用在内部安全性要求不高的的系统上,只是相对的多,总的来说现在使用这种鉴权比较少了。如果项目需要部署在公网上,这种方式不推荐,当然你也可以和SSL来加密传输,这样会好一点,这个如果我后面有时间来研究一下。
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro
视频教程:https://doc.iocoder.cn/video/
第二种这个方式是利用服务器端的session(会话)和浏览器端的cookie来实现前后端的认证,由于http请求时是无状态的,服务器正常情况下是不知道当前请求之前有没有来过,这个时候我们如果要记录状态,就需要在服务器端创建一个会话(seesion),将同一个客户端的请求都维护在各自得会会话中,每当请求到达服务器端的时候,先去查一下该客户端有没有在服务器端创建seesion,如果有则已经认证成功了,否则就没有认证。
session-cookie认证主要分四步:
服务器在接受客户端首次访问时在服务器端创建seesion,然后保存seesion(我们可以将seesion保存在内存中,也可以保存在redis中,推荐使用后者),然后给这个session生成一个唯一的标识字符串,然后在响应头中种下这个唯一标识字符串。
签名。这一步只是对sid进行加密处理,服务端会根据这个secret密钥进行解密。(非必需步骤)
浏览器中收到请求响应的时候会解析响应头,然后将sid保存在本地cookie中,浏览器在下次http请求de 请求头中会带上该域名下的cookie信息,
服务器在接受客户端请求时会去解析请求头cookie中的sid,然后根据这个sid去找服务器端保存的该客户端的session,然后判断该请求是否合法。
server.js(nodejs+express+seesion+redis)
var express = require('express' );var RedisStore = require('connect-redis' )(express.session);var app = express();var secret = "wang839305939" // 设置 Cookie app.use(express.cookieParser(secret));// 设置 Session app.use(express.session({ store: new RedisStore({ host: "127.0.0.1" , port: 6379 , db: "session_db" }), secret: secret })) app.get("/" , function(req, res) { var session = req.session; session.time= session.time|| 0 ; var n = session.time++; res.send('hello, session id:' + session.id + ' count:' + n); }); app.listen(9080 );
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://gitee.com/zhijiantianya/yudao-cloud
视频教程:https://doc.iocoder.cn/video/
使用基于 Token 的身份验证方法,大概的流程是这样的:
验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
总的来说就是客户端在首次登陆以后,服务端再次接收http请求的时候,就只认token了,请求只要每次把token带上就行了,服务器端会拦截所有的请求,然后校验token的合法性,合法就放行,不合法就返回401(鉴权失败)。
乍的一看好像和前面的
seesion-cookie
有点像,
seesion-cookie
是通过seesionid来作为浏览器和服务端的链接桥梁,而token验证方式貌似是token来起到
seesionid
的角色。其实这两者差别是很大的。
sessionid 他只是一个唯一标识的字符串,服务端是根据这个字符串,来查询在服务器端保持的seesion,这里面才保存着用户的登陆状态。但是token本身就是一种登陆成功凭证,他是在登陆成功后根据某种规则生成的一种信息凭证,他里面本身就保存着用户的登陆状态。服务器端只需要根据定义的规则校验这个token是否合法就行。
session-cookie是需要cookie配合的,居然要cookie,那么在http代理客户端的选择上就是只有浏览器了,因为只有浏览器才会去解析请求响应头里面的cookie,然后每次请求再默认带上该域名下的cookie。但是我们知道http代理客户端不只有浏览器,还有原生APP等等,这个时候cookie是不起作用的,或者浏览器端是可以禁止cookie的(虽然可以,但是这基本上是属于吃饱没事干的人干的事)…,但是token 就不一样,他是登陆请求在登陆成功后再请求响应体中返回的信息,客户端在收到响应的时候,可以把他存在本地的cookie,storage,或者内存中,然后再下一次请求的请求头重带上这个token就行了。简单点来说cookie-session机制他限制了客户端的类型,而token验证机制丰富了客户端类型。
时效性。session-cookie的sessionid实在登陆的时候生成的而且在登出事时一直不变的,在一定程度上安全就会低,而token是可以在一段时间内动态改变的。
可扩展性。token验证本身是比较灵活的,一是token的解决方案有许多,常用的是JWT,二来我们可以基于token验证机制,专门做一个鉴权服务,用它向多个服务的请求进行统一鉴权。
JWT是Auth0提出的通过对JSON进行加密签名来实现授权验证的方案,就是登陆成功后将相关信息组成json对象,然后对这个对象进行某中方式的加密,返回给客户端,客户端在下次请求时带上这个token,服务端再收到请求时校验token合法性,其实也就是在校验请求的合法性。
1.Headers:包括类别(typ)、加密算法(alg)
{ "alg" : "HS256" , "typ" : "JWT" }
2.Claims :包括需要传递的用户信息
{ "sub" : "1234567890" , "name" : "John Doe" , "admin" : true }
3.Signature:根据alg算法与私有秘钥进行加密得到的签名字串, 这一段是最重要的敏感信息,只能在服务端解密;
HMACSHA256( base64UrlEncode(Headers) + "." + base64UrlEncode(Claims), SECREATE_KEY )
编码之后的JWT看起来是这样的一串字符:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
nodejs+express+jwt-simple
auth.js
let jwt = require('jwt-simple' ); let secret = "wangyy" ; let time = 10 ; module .exports = { /* *检验token合法性 */ validate:function(req,res,next){ let token = req.body.token||req.headers["xssToken" ]; if (token){ let decodeToken = null ; try { //防止假冒token解析報錯 decodeToken = jwt.decode(token,secret,'HS256' ); } catch (err) { res.status(401 ).send("非法访问" ); return ; } let exp = decodeToken.exp; if (!exp){ res.status(401 ).send("非法访问" ); } let now = new Date().getTime(); if (exp>(now+time*60 *1000 )){ res.send({code:'002' ,"errorMsg" :"授权超时" }) } next(); }else { res.status(401 ).send("非法访问" ); } }, /* 生成token*/ makeToken(){ let Token = null ; let payload = { time:new Date().getTime(), exp:this .makeExp(time) } Token = jwt.encode(payload,secret,HS256) return Token; }, /*生成token过期时间*/ makeExp:function(time){ let stam = time601000; } }
server.js
let express = require("express" ); let app = express(); let bodyParser = require('body-parser' ); let auth = require('./lib/auth.js' ); let chalk = require('chalk' ); app.use(bodyParser.json()); app.post('/login' ,function(req,res,next){ let Token = auth.makeToken(); res.json({result:"success" ,token:Token},200 ) }); app.use('*' ,[auth.validate],function(req,res,next){ res.send('success' ); }); app.listen('9999' )
上面只是一个简单的token生成和校验,如果有需要可以根据实际需要进行逻辑处理
OAuth(开放授权)是一个开放标准,允许用户授权第三方网站访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方网站或分享他们数据的所有内容,为了保护用户数据的安全和隐私,第三方网站访问用户数据前都需要显式的向用户征求授权。我们常见的提供OAuth认证服务的厂商有支付宝,QQ,微信。
OAuth协议又有1.0和2.0两个版本。相比较1.0版,2.0版整个授权验证流程更简单更安全,也是目前最主要的用户身份验证和授权方式。
下面是一张auth2.0的流程图:
从图中我们可以看出,auth2.0流程分为六布(我们就以csdn登陆为例):
第一步.
向用户请求授权,现在很多的网站在登陆的时候都有第三方登陆的入口,当我们点击等第三方入口时,第三方授权服务会引导我们进入第三方登陆授权页面。