qidao123.com技术社区-IT企服评测·应用市场
标题:
Web身份认证 --- Session和JWT Token
[打印本页]
作者:
小小小幸运
时间:
2024-12-27 00:39
标题:
Web身份认证 --- Session和JWT Token
方法一: 通过使用Session进行身份认证
用户第一次哀求服务器的时间,服务器根据用户提交的相关信息,创建创建对应的 Session
哀求返回时将此 Session 的唯一标识信息 SessionID 返回给欣赏器,欣赏器接收到服务器返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名.
当用户第二次访问服务器的时间,哀求会主动判断此域名下是否存在 Cookie 信息,假如存在主动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,假如没有找到说明用户没有登录或者登录失效,假如找到 Session 证明用户已经登录可执行后面操作.
当流量大时,后端往往需要多台服务器共同来支撑前端用户哀求,那假如用户在 A 服务器登录了,第二次哀求跑到服务 B 就会出现登录失效题目, 此时可以使用共享session
将用户的 Session 等信息使用缓存中间件来统一管理 (如Redis),保障分发到每一个服务器的响应效果都一致
使用SpringBoot实现简朴的session + cookie实现持久登录
当用户第一次login时
@PostMapping("login")
public JsonData login(@RequestBody User user, HttpServletRequest request, HttpServletResponse response) {
String crypPassword = MD5Utils.MD5(user.getPwd());
User loggedinUser = userService.login(user.getPhone(), crypPassword);
if (loggedinUser != null) {
//随机生成一个UUID作为seesion ID
String sessionID = UUID.randomUUID().toString();
//在服务器中将sessionID和用户组成 KV pair: Map<sessionID, USER>
//setAttribute的底层实现是一个hashmap
request.getSession().setAttribute(sessionID, loggedinUser);
//生成一个cookie, 并将sessionID加入cookie, 对应的名字是 "sessionId"
Cookie cookie = new Cookie("sessionId", sessionID);
cookie.setPath("/");
cookie.setMaxAge(FIVE_DAYS);
//在response中给客户端返回cookie
response.addCookie(cookie);
return JsonData.buildSuccess(loggedinUser);
}
return JsonData.buildError("Password or username invalid");
}
复制代码
在interceptor中判断是否已经登录:
public static int currentUserID = -1;
String sessionId = "Default";
Cookie[] cookies = request.getCookies();
if (cookies == null) {
System.out.println("Intercepter: No Cookies");
sendJsonMessage(response, JsonData.buildError("Interceptor: No Cookies, please log in first"));
currentUserID = -1;
return false;
}
//遍历前端传来的cookie
for (Cookie cookie: cookies) {
//如果发现 sessionId字段, 则说明发现sessionID
if (cookie.getName().equals("sessionId")) {
System.out.println("Interceptor: found a session id in cookie:" + cookie.getValue());
//拿到sessionId
sessionId = cookie.getValue();
break;
}
}
//通过sessionID拿到user
User user = (User)request.getSession().getAttribute(sessionId);
if (sessionId.equals("Default") || user == null) {
System.out.println("Interceptor: sessionId not match, cannot preceed");
sendJsonMessage(response, JsonData.buildError("Interceptor: No record of this sessionID, please log in first"));
currentUserID = -1;
return false;
}
//拿到userID, 在别的类中可以通过 int uid = LoginInterceptor.currentUserID; 拿到uid
currentUserID = user.getId();
复制代码
Session模式有个明显缺点就是在分布式体系中,session缓存可能被频繁调用,形成性能瓶颈甚至发生单点故障,导致体系崩溃.
可以使用JWT进行验证,JWT是无状态的验证方法,不需要缓存
方法二: 通过JWT token进行身份认证
什么是JWT
JWT,全称为JSON Web Token,是一种开放尺度(RFC 7519),用于在网络应用情况间安全地传输信息。
它本质上是一个颠末数字签名的JSON对象,可以或许携带并传递状态信息(如用户身份验证、授权等)
JWT由
Header + Payload + Signature
三部分构成, 并颠末Base64Url编码之后使用 (注意编码不是加密)
Header
JWT 的头部通常由两部分构成,分别是令牌类型(typ)和加密算法(alg)。一般情况下,头部会采用 Base64 编码。
{
"alg": "HS256",
"typ": "JWT"
}
复制代码
Payload
JWT 的载荷也称为声明信息,包罗了一些有关实体(通常是用户)的信息以及其他元数据。通常包括以下几种声明:
Registered Claims:这些声明是预定义的,包括 iss(发行者)、sub(主题)、aud(受众)、exp(过期时间)、nbf(生效时间)、iat(发布时间)和 jti(JWT ID)等。
Public Claims:这些声明可以自定义,但需要注意避免与注册声明的名称辩说。
Private Claims:这些声明是保存给特定的应用程序使用的,不会与其他应用程序辩说。
注意:Payload中肯定不要存放敏感或重要信息,如密码等
{
"sub": "666666",
"name": "warriors",
"admin": true
}
复制代码
Signature
JWT 的签名是由头部、载荷和密钥共同天生的。它用于验证 JWT 的真实性和完整性。一般情况下,签名也会采用 Base64 编码。
例如,假如要使用 HMAC SHA256 算法,将按以下方式创建签名:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
复制代码
JWT完整流程
假设你现在有一个应用,需要用户登录后获取访问权限。
用户登录
用户在登录页面输入用户名和密码,点击登录。服务器接收到登录哀求并验证用户的凭证。
天生JWT
假如用户的凭证(账号密码)精确,服务器将天生一个JWT。假如服务器将天生的JWT为:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
让我们详细分解这个JWT的天生过程,一共三个部分,每个部分被点号(.)截断
Header: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
base64url解码一下得到
{
"alg": "HS256",
"typ": "JWT"
}
复制代码
这个头部表现我们将使用HMAC SHA-256算法进行签名,并且这是一个JWT。
Payload: eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
base64url解码一下得到
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
复制代码
这个负载部分包罗了一些声明,比如用户ID(sub)、用户名(name)和签发时间。
Signature: SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
第三部分签名的天生是由第一部分和第二部分构成的
我们使用密钥(secret)来天生签名。签名是使用以下方法天生的:
将编码后的Header和Payload用点号(.)毗连在一起:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
复制代码
使用HMAC SHA-256算法和密钥secret对上述字符串进行签名:
header_payload = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
"secret = "secret"signature = hmac.new(secret.encode(), header_payload.encode(), hashlib.sha256).digest()signature_base64 = base64.urlsafe_b64encode(signature).rstrip(b'=').decode()print(signature_base64)
复制代码
天生的第三部分签名是:SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
末了,将编码后的Header、Payload和天生的Signature用点号(.)毗连在一起,形成完整的JWT:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
返回JWT
服务器将天生的JWT返回给客户端。客户端可以将JWT存储在本地存储(Local Storage)或Cookie中。
携带JWT进行哀求
在用户登录后的每次哀求中,客户端会在哀求头中携带JWT,例如:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
服务器验证JWT
服务器接收到哀求后,会验证JWT的签名是否精确。验证过程如下:
从JWT中提取出header和payload部分。
使用同样的签名密钥和header中的算法,重新天生签名。
将重新天生的签名与JWT中的签名进行比力,假如类似,则验证通过。
响应哀求
假如JWT有用且未过期,服务器处理哀求并返回响应。
JWT攻防
常见攻击方法
暴力破解签名密钥:攻击者可以使用暴力破解的方法猜测JWT的签名密钥。假如签名密钥比力弱,这种攻击可能会乐成。
算法替换攻击:假如服务器在验证JWT时不严酷检查头部的alg字段,攻击者可以将alg字段改为none,绕过签名验证。
伪造Token:假如服务器使用对称加密算法(如HS256),并且密钥泄露,攻击者可以使用密钥伪造有用的JWT
防御措施
使用强密钥:确保签名密钥足够强,难以被暴力破解。
严酷验证算法:在服务器端严酷验证alg字段,拒绝none算法。
使用非对称加密:使用非对称加密算法(如RS256),即使公钥泄露,攻击者也无法伪造JWT。
定期更换密钥:定期更换签名密钥以增长安全性。
设置得当的过期时间:设置公道的JWT过期时间,减少被盗用的风险。
JWT 怎样退出登录
JWT一旦发出,不能撤回,所以在有用期内会不停有用,常见退出方法有三种
Simply remove the token from the client
Create a token blacklist
Just keep token expiry times short and rotate them often
JWT的续签
JWT在使用中一般遵循OAuth2.0的尺度,OAuth2.0中的续签通常使用“双令牌(access_token, refresh_token) 续签”
注意 access_token和refresh_token是OAuth2规范的内容,不是jwt规范的部分
访问令牌(Access Token)
用途:访问令牌是一个短期的令牌,用于授权第三方应用访问受掩护的资源。每次访问受掩护的资源时,第三方应用需要在哀求头中携带这个令牌。
有用期:访问令牌通常有一个较短的有用期,可能是几分钟到几小时不等。这是为了减少令牌被盗用的风险。
格式:访问令牌通常是一个字符串,可以是 JWT(JSON Web Token)格式,也可以是别的格式。
刷新令牌(Refresh Token)
用途:刷新令牌用于获取新的访问令牌。当访问令牌过期时,客户端可以使用刷新令牌向授权服务器哀求新的访问令牌,而不需要再次通过用户的授权。
有用期:刷新令牌的有用期通常比访问令牌长得多,可以是几天、几周甚至更长时间。
安全性:由于刷新令牌的有用期较长,应该妥善掩护,避免泄露。一般来说,刷新令牌不应该在前端应用中存储,而应存储在服务器端
为什么需要两个token
起首确认一点: token使用的次数越多被盗取概率越大,过期时间越长也就越危险
一个token的续签方法: 假设现在只有一个access token,因为使用次数很多(每次Api调用都会用到)并且token无法被设置为失效, 所以access token的过期时间需要被设置的很短,这样即使被token盗,损失也可以降到最低.
方法一: token过期之后,用户重新输入账号密码拿到新的access token.
缺点:因为access token过期时间很短,用户需要频繁输入账号密码,很影响使用.
方法二:当服务器收到哀求之后,计算token剩余有用时间,假如小于一个阈值,则颁发新access token.
缺点:假如用户在阈值之内没有进行api调用,则还是需要重新输入账号和密码,影响使用
缺点:假如access被盗用,则盗用者还是可以通过被盗用的access token进行续签,拿到新的token.
使用两个token续签:
OAuth2定义了资源服务器和授权服务器,第一次认证向授权服务器提交用户名和密码获得两个token,然后访问资源服务器获取资源使用access_token
当access_token有用期失效的时间,前端使用这个refresh_token哀求授权服务器换取新的acceess_token来包管接口的正常哀求, 这样可以做到用户无感知续签
这样即使access token被盗用,access token的过期时间设置的很短,损失也可以降到最低.
而refresh token使用次数少,保存在数据库中,被盗风险小
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
欢迎光临 qidao123.com技术社区-IT企服评测·应用市场 (https://dis.qidao123.com/)
Powered by Discuz! X3.4