# 一.权限认证(登录/注册)
前言
用户通过登录认证,识别相关用户,是一个允许用户访问其权限相应的资源的过程
# 1.Cookie+Session
前言
HTTP 是一种无状态的协议,客户端每次发送请求时,首先要和服务端创建一个连接,在请求完成之后会断开这个连接。这种方式能够节省传输时占用的连接资源,但同时也存在一个问题:每次请求都是独立的,服务器没法判断本次请求和上一次请求是否来自同一个用户,进而也没法判断用户的登录状态。
Cookie 是服务端发送给客户端的一段特殊信息,这些信息以文本的方式存放在客户端,客户端每次向服务端发送请求时都会带上这些特殊信息。
客户端请求服务端,服务端会为此次请求开辟一块内存区域,这就是 Session 对象。
# 1.1 实现流程
Cookie+Session 的登录方式是最经典的一种登录方式,如今仍有大量企业在用。
1.用户首次登录时:
- 1.先访问 a.com/login,输入账号密码登录。
- 2.服务器验证密码无误后,会建立 SessionId,并将它保存起来。
- 3.服务端响应这个 HTTP 请求,并经过 Set-Cookie 头信息,将 SessionId 写入 Cookie 中。
- 4.浏览器会根据 Set-Cookie 中的信息,自动将 SessionId 存储在 cookie 中。
- 5.服务端的 SessionId 可能存放在不少地方,如内存、文件、数据库等。
2.第一次登录完成之后,后续的访问就能够直接使用 Cookie 进行身份验证了:
- 1.用户访问 a.com/page 页面时,会自动带上第一次登录时写入的 Cookie。
- 2.服务端对比 Cookie 中的 SessionId 和保存在服务端的 SessionId 是否一致。
- 3.若是一致,则身份验证成功。
# 1.2 存在的问题
1.因为服务端需要对接大量的客户端,就需要存放大量的 SessionId,会导致服务端压力过大。
2.若服务端是一个集群,需要将 SessionId 同步到每一台机器上,无形中增长了服务端维护成本。
3.因为 SessionId 存放在 Cookie 中,因此没法避免 CSRF 攻击。
- 3.1 CSRF 攻击是一种利用用户在目标网站上已认证的会话执行非预期操作的攻击方式。攻击者通过欺骗用户使其在受信任的网站上执行恶意操作,如转账、修改账户信息等。
- 3.2 常见的 CSRF 攻击方式,如下:
- 1、直接链接方式,攻击者通过诱使用户点击恶意链接,向目标网站发送伪造请求。当用户点击链接时,浏览器会自动发送包含用户认证信息的请求,从而执行攻击者指定的操作。
- 2、图片/资源引用方式,攻击者将恶意请求嵌入到图片、脚本或其他资源引用中,并将其插入到受信任网站的页面。当用户访问页面时,浏览器会自动加载并发送恶意请求。
- 3、表单提交方式,攻击者创建一个包含恶意请求的表单,并将其隐藏在一个看似正常的页面中。当用户在该页面上执行某些操作(如点击按钮)时,浏览器会自动提交表单,从而执行攻击者指定的操作。
在 vuex 中发送请求
export default new Vuex.Store({
state: {
username: "",
},
mutations: {
setUsername(state, username) {
state.username = username
},
},
actions: {
async login({ commit }, username) {
const r = await login(username)
if (r.token) {
commit("setUsername", username)
localStorage.setItem("token", r.token)
} else {
return Promise.reject(r)
}
},
},
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 2.Token
前言
Token 是服务端生成的一串字符串,以作为客户端请求的一个令牌。当第一次登录后,服务端会生成一个 Token 并返回给客户端,客户端后续访问时,只要携带这个 Token 就能完成认证。
# 2.1 实现流程
1.用户首次登录时:
- 用户访问 a.com/login,输入账号密码,并点击登录。
- 服务端验证账号密码无误,建立 Token。
- 服务端将 Token 返回给客户端,由客户端自由保存。
2.后续访问页面时:
- 用户访问 a.com/page 时,带上第一次登录时获取的 Token。
- 服务端验证 Token,有效则身份验证成功。
# 2.2 机制特色
根据上述流程,分析 Token 机制的优缺点:
- 服务端不需要存放 Token,因此不会对服务端形成压力,即便是服务器集群,也不需要增长维护成本。
- Token 能够存放在前端任何地方,能够不保存在 Cookie 中,提高了页面安全性。
- Token 下发之后,只要在生效时间内,就一直生效,若服务端想要收回此 Token 权限,并不容易。
# 2.3 生成方式
最常见的 Token 生成方式是使用 JWT,即 Json Web Token,它采用一种简洁的,自包含的方法,用于通讯双方之间以 JSON 对象的形式安全的传递信息。
之前说到使用 Token 后,服务端不会存储 Token,那么如何判断客户端发过来的 Toekn 是否合法有效呢?答案就在 Toekn 字符串中,其实 Token 并非一段杂乱无章的字符串,是经过多种算法拼接组合而成的字符串,下面分析一下其组成:
- JWT 算法主要分为 3 个部分:header(头信息)、playload(消息体)、signature(签名)
- header 部分指定了该 JWT 使用的签名算法。
- playload 代表了 JWT 的意图。
- signatrue 部分为 JWT 的签名,主要为了让 JWT 不能被随意篡改。
- 有了 Token 之后,登录方式已经变得很高效。
# 2.3.1 jwt
JSON Web Token(JWT)是目前最流行的身份验证解决方案
解决问题:session 不支持分布式架构,无法支持横向扩展,只能通过数据库来保存会话数据实现共享。如果持久层失败会出现认证失败。
优点:服务器不保存任何会话数据,即服务器变为无状态,使其更容易扩展。
JWT 包含了三部分
Header 头部
{ "alg": "HS256", "typ": "JWT" } // algorithm => HMAC SHA256 // type => JWT1
2
3Payload 负荷、载荷
JWT 规定了7个官方字段 iss (issuer):签发人 exp (expiration time):过期时间 sub (subject):主题 aud (audience):受众 nbf (Not Before):生效时间 iat (Issued At):签发时间 jti (JWT ID):编号1
2
3
4
5
6
7
8Signature 签名 对前面两部分的签名,防止数据篡改
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)1JWT 作为一个令牌(token),有些场合可能会放到 URL(比如:api.example.com?token=xxx)。Base64 有三个字符
+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被忽略、+替换成-,/替换成_。这就是 Base64URL 算法。
使用方式
HTTP 请求头信息
Authorization: Bearer <token>1通过 url 传输
http://www.xxx.com/pwa?token=xxxxx1
express 实现服务端返回 token
let express = require('express')
let app = express()
let bodyParse = require('body-parser')
let jwt = require('jsonwebtoken')
app.use((req,res,next)=>{
res.header('Access-Control-Allow-Origin','http://localhost:8080')
res.header('Access-Control-Allow-Methods','GET,HEAD,OPTIONS,POST,PUT')
res.header('Access-Control-Allow-Methods','Origin,X-Requested-With,Content-Type,Accept,Authorization')
if(req.methods.toLowerCase() === 'options'){
return res.end()
}
next()
})
app.use(bodyParse.json())
let secret = 'zfjg'
app.get('/test',(req,res)=>{
res.end({test:'test'})
})
app.post('/login',(req,res)=>{
let {username} = req.body
if(username === 'admin'){
res.json({
code:0.
username:'admin',
token:jwt.sign({username:'admin'},secret,{
expiresIn:20
})
})
}else{
res.json({
code:1,
data:'用户名不存在'
})
}
})
app.get('/validate',(req,res)=>{
let token = req.headers.authorization
jwt.verify(token,secret,(err,decode)=>{
if(err){
return res.json({
code:1,
data:'token失效了'
})
}else{
res.json({
username:decode.username,
code:0,
token:jwt.sign({
username:'admin'
},secret,{
expiresIn:20
})
})
}
})
})
app.listen(3000)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 3.SSO
前言
单点登录指在公司内部搭建的一个公共认证中心,公司下的全部产品登录均可以在认证中心完成,一个产品在认证中心登录之后,再去访问另外一个产品,能够不用再次登录,便获取登录状态。
# 3.1 实现流程
用户首次访问时,需要在认证中心登录:
- 用户访问网站 a.com/pageA 页面。
- 因为没有登录,会重定向到认证中心,并带上回调地址www.sso.com?return_uri=a.com/pageA,以便登录后直接进入对应页面。
- 用户在认证中心输入账号密码,提交登录。 认证中心验证账号密码有效,后重定向到 a.com?ticket=123,带上授权码 ticket,并将认证中心 sso.com 的登录态写入 - Cookie。
- 在 a.com 服务器上,拿着 ticket 向认证中心确认,授权码 ticket 真实有效。
- 验证成功后,服务器将登录信息写入 Cookie(此时客户端有两个 Cookie,分别存有 a.com 和 sso.com 的登录态信息) 认证中心完成登录后,继续访问 a.com 的其余页面,由于此时 a.com 已经存了 Cookie 信息,服务端直接认证成功。 如果要访问认证中心已存的 b.com 的页面,由于认证中心存在以前登录过的 Cookie,也就不用再输入账号密码,直接返回第四步,下发 - ticket 给 b.com 即可。
# 3.2 单点登录退出
完成单点登录后,在同一套认证中心管理下,多个产品能够共享登录状态,但是还有一个问题:在一个产品中退出了登录状态,怎么让其余产品也退出登录?
原理并不难,回过头来看第 5 步,每个产品在向认证中心验证 ticket 时,能够顺带将本身的退出登录 api 发送到认证中心。
当某个产品 c.com 退出登录时:
- 清空 c.com 中的登录态 Cookie。
- 请求认证中心 sso.com 中的退出 api
- 认证中心遍历下发过 ticket 的全部产品,并调用对应的退出 api,完成退出。
# 4.OAuth
前言
除了上述的认证方式,也可以引入第三方厂家提供的登录服务。如微博、微信、QQ 等提供的登录认证接口,均能实现登录过程
← Vue 功能模块 一.权限认证(权限管理) →