前端登陆实现
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端登陆实现相关的知识,希望对你有一定的参考价值。
参考技术A四种方式
Cookie 出现的原因: HTTP 协议是无状态的,每次请求都会建立一个新的链接,请求结束就会断开链接,优点就是可以节省链接资源,缺点就是无法保存用户状态。Cookie 的出现就是为了解决这个问题。
Cookie 是存储在浏览器中的,可以通过 Js 和 set-cookie 这个响应字段来进行设置。
cookie 的限制:
有了 cookie 之后,服务端就可以从客户端获取到信息,如果需要对信息进行验证,那么还需要 session
服务端在收到客户端的请求之后,会在服务器中开辟一片内存空间来存放 session
第一次登陆之后,下次再访问的时候就会携带这个 cookie,服务端就可以根据 sessionId 进行验证用户是否登陆(判断这个 sessionId 和服务端保存的 sessionId 是否一致,是否有这个 sessionId 的记录或者记录是否有效)
客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是 Session。客户端浏览器再次访问时只需要从该 Session 中查找该客户的状态就可以了。
Token 是 服务器 生成的一个字符串,作为客户端请求的一个令牌。第一次登陆之后,服务器会生成一个 Token 返回给客户端,客户端后续访问的时候,只需带上这个 Token 进行身份认证
缺点
JWT(Json Web Token)
服务端不需要存储 Token 那么服务端是怎么验证客户端传递过来的 Token 是否有效的呢?
答案:
Token 并不是杂乱无章的字符串,而是通过多种算法拼接而成的字符串
header 部分指定了这个 Token 所使用的签名算法
payload 部分表明了这个 JWT 的意图
signature 部分为 JWT 的签名,主要是为了让 JWT 不被随意的篡改
签名的部分有两个步骤
一:
二:
最后的 Token 计算如下:
单点登陆指的是公司会搭建一个公共的认证中心,公司里的所有产品的认证都可以在这个认证中心中完成,一个产品在认证中心认证之后,再去访问其他产品时就不需要再次认证
这个时候,由于 a.com 存在已登录的 Cookie 信息,所以服务器端直接认证成功。
这个时候由于认证中心存在之前登陆过的 cookie,所以不需要再输入账号密码,直接从第四步开始执行
目前我们已经完成了单点登录,在同一套认证中心的管理下,多个产品可以共享登录态。现在我们需要考虑退出了,即:在一个产品中退出了登录,怎么让其他的产品也都退出登录?
原理也不难,其实就是在携带 ticket 去请求认证中心的时候,再去请求一下认证中心的退出登陆的 api 即可
当某个产品 c.com 退出登陆时
sso 就是一个集中地验证系统。你项目内请求时,向 sso 发一个请求,他给你个 token 你扔到游览器缓存里,请求的时候放在请求头里带着。和其他验证接口一样。 他好就好在,一个账号在不同系统里都可以登录,因为不同项目可以共用这个 token。并且通过 sso 集中管理一些用户信息,你可以方便的拿用户信息。
以微信为例子
前端登陆之cookie篇
一、cookie
上一个文章介绍了实用node写了一个服务,以及调用api去操作数据库的一个前后端分离的demo,这次重点记录一下前后端登陆操作的一个部分。登陆认证是一个项目中比较重要的部分,接下来我将使用cookie的模式来实现登陆认证!
cookie的组成
cookie是浏览器中特有的一个概念,它就像浏览器的专属卡包,管理着各个网站的身份信息。
每个cookie就相当于是属于某个网站的一个卡片,它记录了下面的信息:
key:键,比如「身份编号」
value:值,比如张博的身份编号「14563D1550F2F76D69ECBF4DD54ABC95」,这有点像卡片的条形码,当然,它可以是任何信息
path:路径,表达这个cookie是属于该网站的哪个基路径的,就好比是同一家公司不同部门会颁发不同的出入证。比如/news,表示这个cookie属于/news这个路径的。(后续详细解释)
secure:是否使用安全传输
expire:过期时间,表示该cookie在什么时候过期
当浏览器向服务器发送一个请求的时候,它会瞄一眼自己的卡包,看看哪些卡片适合附带捎给服务器
如果一个cookie同时满足以下条件,则这个cookie会被附带到请求中
cookie没有过期
cookie中的域和这次请求的域是匹配的
比如cookie中的域是123.com,则可以匹配的请求域是123.com、www.123.com、234.123.com等等
比如cookie中的域是www.123.com,则只能匹配www.123.com这样的请求域
cookie是不在乎端口的,只要域匹配即可
cookie中的path和这次请求的path是匹配的
比如cookie中的path是/news,则可以匹配的请求路径可以是/news、/news/detail、/news/a/b/c等等,但不能匹配/blogs
如果cookie的path是/,可以想象,能够匹配所有的路径
验证cookie的安全传输
如果cookie的secure属性是true,则请求协议必须是https,否则不会发送该cookie
如果cookie的secure属性是false,则请求协议可以是http,也可以是https
如果一个cookie满足了上述的所有条件,则浏览器会把它自动加入到这次请求中
具体加入的方式是,浏览器会将符合条件的cookie,自动放置到请求头中。
如果把它用于登录场景,就是如下的流程:
登录请求
浏览器发送请求到服务器,附带账号密码
服务器验证账号密码是否正确,如果不正确,响应错误,如果正确,在响应头中设置cookie,附带登录认证信息(至于登录认证信息是设么样的,如何设计,要考虑哪些问题,就是另一个话题了,可以百度 jwt)
客户端收到cookie,浏览器自动记录下来
后续请求
浏览器发送请求到服务器,希望添加一个管理员,并将cookie自动附带到请求中
服务器先获取cookie,验证cookie中的信息是否正确,如果不正确,不予以操作,如果正确,完成正常的业务流程
二、处理登陆APi接口
接下来就是处理登陆这个api接口:
const express = require("express")
const router = express.Router()
const user = require("../../serveAPI/userService")
const asyncHandler = require("../getResult")
const cryptor = require("../../util/crypt")
router.post("/login",asyncHandler(
async (req,res,next) =>
const result = await user.login(req.body.loginId,req.body.loginPwd)
if (result)
//如果result存在则登录成功
//设置cookie
//给浏览器用cookie
//cookie-parse给了一个默认加密,其中有默认密钥可以设置
const value = result.id
cryptor.encrypt(value)
res.cookie("token",result.id,
path:"/",
domain:"localhost",
maxAge:3600*24*10000,
// signed:true,
)
//给其他应用 如手机端
res.header("authorization",value)
return result;
))
当我们调用这个接口之后,就会从数据库中访问这个用户(根据用户名和密码),这个密码时使用md5进行加密,然后解密的:
当返回这个结果的时候,我们就使用cooke方法进行给res请求结果中设置cookies,将token设为用户的id,剩下的domain,还有path,maxAge都可以视情况而定,但是这里的id就可以在浏览器端使用document.cookie获取到,然后提取出来id,我们在服务端,也可以设置让js不能获取cookie,防止跨站脚本攻击,但是还是不安全,因为这个数据是明文的,可以在浏览器中的cookie中看到数据,而cookie-parse里面提供一个自动加密的属性,就是signed为true,就会给这个cookie里的token值自动加密,之后取到也可以自动解密,但是有个很严重的问题,这里cookie是浏览器特有的一个属性,在其他端虽然可以使用但是没有像浏览器这样专门设置这个cookie模块,来处理请求,所以我们在这个请求头中,留一手,给请求头中再设置一个authorization的值为用户id,这样不管是pad端,手机端都可以处理这个请求内容,所以当设置这个authorization这个键之后,cookie-parse只能去处理这个cookie中的token而不能处理authorization,所以加密这个事情还得我们来做,这里使用node里面提供的这个crypto模块,来进行加密:
const secret = Buffer.from("mm7h3ck87ugk9l4a");
const crypto = require("crypto");
const iv = Buffer.from("jxkvxz97409u3m8c");
//加密一个字符串
exports.encrypt = function (str)
const cry = crypto.createCipheriv("aes-128-cbc", secret, iv);
let result = cry.update(str, "utf-8", "hex");
result += cry.final("hex");
return result;
;
//解密一个字符串
exports.decrypt = function (str)
const decry = crypto.createDecipheriv("aes-128-cbc", secret, iv);
let result = decry.update(str, "hex", "utf-8");
result += decry.final("utf-8");
return result;
;
上面是一个js文件导出两个函数,一个用于加密,一个用语解密;
使用对称加密算法:aes算法 128位密钥(16个字节的字符串),其中mm7h3ck87ugk9l4a是用随机数转字符串拼接获取Math.random(),toString(36).slice(-8)+Math.random(),toString(36).slice(-8)
Buffer是node里面的一个类,JS里的String对象,存储的是字符串,而且是Unicode编码的。
我们使用node内置库crypto,crypto.getCiphers()可以看到crypto所有的加密函数,而我们使用第一个aes-128-cbc,接下来是准备一个iv,随机向量 ,一般来说密钥固定,向量是不固定的,这里我为了简便实用固定的。而这个密钥是存储在服务端的,所以很难进行解密,这样就会让数据有安全保障。第一层保障是这个密钥,如果这个密钥泄漏,下一个保障就是iv向量,这里写的固定的,当然也可以写那种不固定的,使用到js中的闭包;
module.exports = function()
//准备一个iv
const iv = Buffer.from(
Math.random(),toString(36).slice(-8)+Math.random(),toString(36).slice(-8)
);
return
encrypt(str)
const cry = crypto.createCipheriv("aes-128-cbc", secret, iv);
let result = cry.update(str, "utf-8", "hex");
result += cry.final("hex");
return result;
,
decrypt(str)
const decry = crypto.createDecipheriv("aes-128-cbc", secret, iv);
let result = decry.update(str, "hex", "utf-8");
result += decry.final("utf-8");
return result;
,
这里我们直接导出一个函数,这个函数执行后就会给予一个对象,对象有两个方法,第一个是加密,第二个是解密,而这两个函数都使用到函数里定义的变量iv,当我们调用一次这个函数,就会生成一个iv,所以这个iv是不固定的,也是无法获取的,这样就完善了token加密的一个步骤,让数据更加的安全!
三、使用中间件获取cookie
我们使用cookie-parser插件写入中间件,之后再每一次的请求中都会在req对象中注入cookies属性,用于获取传递过来的cookie,也会在res对象中注入获取cookie的方法,所以操作起来就更加方便。
我们引入自己写的cookieMiddleware中间件
/**
* 用于解析token
*/
const getErr = require("./getResult");
const cryptor = require("../util/crypt")
// 用于解析token
module.exports = (req, res, next) =>
let token = req.cookies.token;
// 获取cokkie
// let token = req.signedCookies.token;
if(!token)
//其他的端获取token
token = req.headers.authorization
if(!token)
handleNonToken(req,res,next)
return;
const userId = cryptor.decrypt(token)
console.log(userId,"认证通过啦~~~");
next()
;
//处理没有认证的情况
function handleNonToken(req, res, next)
res
.status(403)
.send(getErr("不好意思,您没有token令牌无法登陆哦~", 403));
以上可以看出,当我们调用接口的时候,就会进行token认证,首先从req中获取cookie如果没有的话,就会在authorization中获取,如果都没有那么就报错,如果有一个有,就进行解密,然后执行下一个中间件,至此,使用cookie方式登陆的模式完成!
以上是关于前端登陆实现的主要内容,如果未能解决你的问题,请参考以下文章