Web身份认证 --- Session和JWT Token

打印 上一主题 下一主题

主题 840|帖子 840|积分 2520

方法一: 通过使用Session进行身份认证

   

  • 用户第一次哀求服务器的时间,服务器根据用户提交的相关信息,创建创建对应的 Session
  • 哀求返回时将此 Session 的唯一标识信息 SessionID 返回给欣赏器,欣赏器接收到服务器返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名.
  • 当用户第二次访问服务器的时间,哀求会主动判断此域名下是否存在 Cookie 信息,假如存在主动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,假如没有找到说明用户没有登录或者登录失效,假如找到 Session 证明用户已经登录可执行后面操作.

  • 当流量大时,后端往往需要多台服务器共同来支撑前端用户哀求,那假如用户在 A 服务器登录了,第二次哀求跑到服务 B 就会出现登录失效题目, 此时可以使用共享session
     

  • 将用户的 Session 等信息使用缓存中间件来统一管理 (如Redis),保障分发到每一个服务器的响应效果都一致
    使用SpringBoot实现简朴的session + cookie实现持久登录
   

  • 当用户第一次login时
  1. @PostMapping("login")
  2.     public JsonData login(@RequestBody User user, HttpServletRequest request, HttpServletResponse response) {
  3.         String crypPassword = MD5Utils.MD5(user.getPwd());
  4.         User loggedinUser = userService.login(user.getPhone(),  crypPassword);
  5.         if (loggedinUser != null) {
  6.                 //随机生成一个UUID作为seesion ID
  7.             String sessionID = UUID.randomUUID().toString();
  8.             //在服务器中将sessionID和用户组成 KV pair: Map<sessionID, USER>
  9.             //setAttribute的底层实现是一个hashmap
  10.             request.getSession().setAttribute(sessionID, loggedinUser);
  11.             //生成一个cookie, 并将sessionID加入cookie, 对应的名字是 "sessionId"
  12.             Cookie cookie = new Cookie("sessionId", sessionID);
  13.             cookie.setPath("/");
  14.             cookie.setMaxAge(FIVE_DAYS);
  15.             //在response中给客户端返回cookie
  16.             response.addCookie(cookie);
  17.             return JsonData.buildSuccess(loggedinUser);
  18.         }
  19.         return JsonData.buildError("Password or username invalid");
  20.     }
复制代码
  

  • 在interceptor中判断是否已经登录:
  1. public static int currentUserID = -1;
  2. String sessionId = "Default";
  3. Cookie[] cookies = request.getCookies();
  4. if (cookies == null) {
  5.         System.out.println("Intercepter: No Cookies");
  6.            sendJsonMessage(response, JsonData.buildError("Interceptor: No Cookies, please log in first"));
  7.     currentUserID = -1;
  8.     return false;
  9. }
  10.                
  11. //遍历前端传来的cookie
  12. for (Cookie cookie: cookies) {
  13.         //如果发现 sessionId字段, 则说明发现sessionID
  14.     if (cookie.getName().equals("sessionId")) {
  15.             System.out.println("Interceptor: found a session id in cookie:" +  cookie.getValue());
  16.             //拿到sessionId
  17.         sessionId = cookie.getValue();
  18.         break;
  19.     }
  20. }
  21. //通过sessionID拿到user
  22. User user = (User)request.getSession().getAttribute(sessionId);
  23. if (sessionId.equals("Default") ||  user == null) {
  24.         System.out.println("Interceptor: sessionId not match, cannot preceed");
  25.     sendJsonMessage(response, JsonData.buildError("Interceptor: No record of this sessionID, please log in first"));
  26.     currentUserID = -1;
  27.     return false;
  28. }
  29. //拿到userID, 在别的类中可以通过 int uid = LoginInterceptor.currentUserID; 拿到uid
  30. 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 编码。
  1. {
  2.   "alg": "HS256",
  3.   "typ": "JWT"
  4. }
复制代码
Payload
   

  • JWT 的载荷也称为声明信息,包罗了一些有关实体(通常是用户)的信息以及其他元数据。通常包括以下几种声明:
  • Registered Claims:这些声明是预定义的,包括 iss(发行者)、sub(主题)、aud(受众)、exp(过期时间)、nbf(生效时间)、iat(发布时间)和 jti(JWT ID)等。
  • Public Claims:这些声明可以自定义,但需要注意避免与注册声明的名称辩说。
  • Private Claims:这些声明是保存给特定的应用程序使用的,不会与其他应用程序辩说。
  • 注意:Payload中肯定不要存放敏感或重要信息,如密码等
  1. {
  2.   "sub": "666666",
  3.   "name": "warriors",
  4.   "admin": true
  5. }
复制代码
Signature
   

  • JWT 的签名是由头部、载荷和密钥共同天生的。它用于验证 JWT 的真实性和完整性。一般情况下,签名也会采用 Base64 编码。
    例如,假如要使用 HMAC SHA256 算法,将按以下方式创建签名:
  1. HMACSHA256(
  2.   base64UrlEncode(header) + "." +
  3.   base64UrlEncode(payload),
  4.   secret)
复制代码
JWT完整流程

   

  • 假设你现在有一个应用,需要用户登录后获取访问权限。
  用户登录
   

  • 用户在登录页面输入用户名和密码,点击登录。服务器接收到登录哀求并验证用户的凭证。
  天生JWT
   

  • 假如用户的凭证(账号密码)精确,服务器将天生一个JWT。假如服务器将天生的JWT为:
  • eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
    .SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
  • 让我们详细分解这个JWT的天生过程,一共三个部分,每个部分被点号(.)截断
  Header: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
   

  • base64url解码一下得到
  1. {
  2.         "alg": "HS256",
  3.         "typ": "JWT"
  4. }
复制代码
  

  • 这个头部表现我们将使用HMAC SHA-256算法进行签名,并且这是一个JWT。
  Payload: eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
   

  • base64url解码一下得到
  1. {
  2.         "sub": "1234567890",
  3.         "name": "John Doe",
  4.         "iat": 1516239022
  5. }
复制代码
  

  • 这个负载部分包罗了一些声明,比如用户ID(sub)、用户名(name)和签发时间。
  Signature: SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
   

  • 第三部分签名的天生是由第一部分和第二部分构成的
  • 我们使用密钥(secret)来天生签名。签名是使用以下方法天生的:
  • 将编码后的Header和Payload用点号(.)毗连在一起:
  1. eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
复制代码
  

  • 使用HMAC SHA-256算法和密钥secret对上述字符串进行签名:
  1. header_payload = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
  2. "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企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

小小小幸运

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表