Cookie、Session、Token(JWT)
三者的区别与用途!如何进行身份认证,保持用户登录状态?
Cookie、Session 和 Token 都是在 Web 开发中用于管理用户状态和进行身份认证的技术,它们之间有以下区别和用途:
一、Cookie
-
含义:Cookie 是存储在用户浏览器中的一小段数据。它由服务器发送到客户端,客户端在后续请求中会将 Cookie 发送回服务器。
-
用途:
-
存储用户偏好设置:例如,用户选择的语言、主题等可以存储在 Cookie 中,以便在用户下次访问时恢复这些设置。
-
跟踪用户会话:通过设置一个会话 ID 的 Cookie,可以在多个请求之间跟踪用户的会话。
-
-
特点:
-
存储在客户端:这意味着用户可以查看和修改 Cookie 的内容(虽然通常不建议这样做)。
-
有大小限制:一般来说,Cookie 的大小不能超过 4KB。
-
二、Session
-
含义:Session 是在服务器端存储的用户会话数据。当用户登录或进行其他需要身份认证的操作时,服务器会创建一个 Session,并为其分配一个唯一的 Session ID。
-
用途:
-
存储用户登录状态:服务器可以在 Session 中存储用户的登录信息,以便在后续请求中验证用户的身份。
-
保存用户特定的数据:例如,在购物网站中,服务器可以在 Session 中存储用户的购物车内容。
-
-
特点:
-
存储在服务器端:相比 Cookie,Session 数据更加安全,因为用户无法直接访问和修改服务器端的数据。
-
依赖 Cookie 或 URL 重写:通常,服务器会通过在 Cookie 中存储 Session ID 来跟踪用户的 Session。如果客户端不支持 Cookie,也可以使用 URL 重写的方式将 Session ID 附加到 URL 中。
-
占用服务器资源,扩张性差(分布式集群,登录时只存储到了一台服务器,负载均衡时,访问到其他服务器就没有该session显示未登录),依然需要依赖cookie跨域限制
-
三、Token
-
含义:Token 是一种包含用户身份信息的加密字符串。它由服务器生成并发送给客户端,客户端在后续请求中携带 Token 以证明自己的身份。
-
用途:
-
无状态身份认证:Token 可以在无状态的环境中进行身份认证,例如 RESTful API。服务器不需要存储 Session 数据,只需要验证 Token 的有效性即可。
-
跨域身份认证:Token 可以在不同的域之间进行身份认证,因为它是独立于特定的服务器会话的。
-
-
特点:
-
自包含:Token 中包含了用户的身份信息,因此服务器不需要在其他地方存储用户的状态。
-
可扩展性:Token 可以很容易地与第三方服务集成,因为它们不依赖于特定的服务器会话。
-
四、JWT
JSON Web Token,就是通过JSON来进行传递的加密后字符串。
JSON Web Token(JWT)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地传输信息作为 JSON 对象。JWT 由三部分组成,分别用点(.)分隔,这三部分分别是:
一、头部(Header)
-
组成内容:通常由两部分组成,令牌的类型(即 “JWT”)和所使用的签名算法,例如 HMAC SHA256 或 RSA。
-
示例:
{"typ":"JWT","alg":"HS256"}
。这表示是一个 JWT,并且使用 HMAC SHA256 算法进行签名。 -
编码方式:这个 JSON 对象会使用 Base64Url 编码方式进行编码,生成 JWT 的第一部分。
二、载荷(Payload)
-
包含信息:载荷部分包含声明(claims),声明是关于实体(通常是用户)和其他数据的声明。声明有三种类型:注册声明、公开声明和私有声明。
-
注册声明:预定义的声明,如 “iss”(issuer,签发者)、“exp”(expiration time,过期时间)、“sub”(subject,主题)、“aud”(audience,受众)等。
-
公开声明:可以由使用 JWT 的各方定义的自定义声明。但为了避免冲突,应该在一个命名空间中定义它们,如 “https://example.com/claims”。
-
私有声明:在同意使用它们的各方之间共享的自定义声明,通常用于在特定的应用程序上下文中传递特定的信息。
-
-
示例:
{"sub":"1234567890","name":"John Doe","admin":true}
。在这个例子中,“sub” 是主题,可能是用户的唯一标识符;“name” 是用户的名字;“admin” 是一个自定义的声明,表示用户是否是管理员。 -
编码方式:同样使用 Base64Url 编码方式进行编码,生成 JWT 的第二部分。前端也就可以通过截取第二部分信息,通过Base64编码解析获得相关信息
三、签名(Signature)
-
生成方式:签名是对头部和载荷的签名,用于验证消息的完整性和真实性,并防止篡改。签名是使用头部(header)中指定的签名算法,对头部和载荷进行签名(自己知道的私钥,通过算法加密)生成的。例如,如果使用 HMAC SHA256 算法,签名是通过将头部和载荷的 Base64Url 编码后的字符串连接起来,使用一个密钥进行 HMAC SHA256 哈希生成的。
-
示例:假设使用密钥 “secret” 和 HMAC SHA256 算法,对前面的头部和载荷进行签名,生成的签名可能是 “eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.signature_here” 中的 “signature_here” 部分。
-
作用:接收方可以使用相同的密钥和算法对头部和载荷进行签名验证,如果生成的签名与接收到的签名一致,则说明消息没有被篡改。
例如,一个完整的 JWT 可能看起来像这样:
eyJhbGci0iJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtzSI6I1h1c2h1IiwicmVhbG5hbWui0iLlvpDlurYifQ.SBKBBM9fBaBEZ71SbzRxn91xfvtvKq6C-XNezgoZBWw
总之,JWT 的三部分组成提供了一种安全、紧凑的方式来在各方之间传输信息,并且可以通过签名验证消息的完整性和真实性。
五、如何进行身份认证和保持用户登录状态
-
使用Cookie:
-
先发送请求到服务端
-
服务端响应中set-cookie参数上添加上用户登录信息
-
在每次请求中携带上cookie信息
-
使用 Cookie 和 Session:
-
用户登录时,服务器验证用户的凭据,如果验证成功,服务器创建一个 Session,并在 Session 中存储用户的登录信息。然后,服务器将 Session ID 作为一个 Cookie 返回给客户端。
-
在后续请求中,客户端会自动将 Cookie 发送回服务器,服务器通过 Session ID 找到对应的 Session,从而验证用户的身份并获取用户的状态信息。
-
为了保持用户登录状态,服务器可以设置 Cookie 的过期时间,或者在用户进行某些操作时刷新 Cookie 的过期时间。
-
使用 Token:
-
用户登录时,服务器验证用户的凭据,如果验证成功,服务器生成一个 Token,并将其返回给客户端。
-
在后续请求中,客户端将 Token 作为一个请求头或参数发送给服务器。服务器验证 Token 的有效性,如果有效,则认为用户已经登录。
-
为了保持用户登录状态,服务器可以在 Token 中设置过期时间,或者在 Token 即将过期时生成一个新的 Token 并返回给客户端。
例如,在一个基于 Spring Boot 的 Web 应用中,可以使用 Spring Security 来实现基于 Cookie 和 Session 的身份认证,也可以使用 JWT(JSON Web Token)来实现基于 Token 的身份认证。以下是一个使用 JWT 实现身份认证的示例:
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Date; public class JwtAuthenticationFilter extends OncePerRequestFilter { private final UserDetailsService userDetailsService; private final String secret; public JwtAuthenticationFilter(UserDetailsService userDetailsService, String secret) { this.userDetailsService = userDetailsService; this.secret = secret; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = getTokenFromRequest(request); if (token!= null &&!token.isEmpty()) { try { String username = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getSubject(); UserDetails userDetails = userDetailsService.loadUserByUsername(username); Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); } catch (Exception e) { // Token 无效,不进行身份认证 } } filterChain.doFilter(request, response); } private String getTokenFromRequest(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); if (bearerToken!= null && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; } public String generateToken(String username) { Date expirationDate = new Date(System.currentTimeMillis() + 86400000); // 1 day expiration return Jwts.builder() .setSubject(username) .setExpiration(expirationDate) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } }
在这个示例中,JwtAuthenticationFilter
类实现了一个 Spring Security 的过滤器,用于从请求中提取 JWT Token,并进行身份认证。如果 Token 有效,过滤器会创建一个Authentication
对象,并将其设置到SecurityContextHolder
中,以便后续的请求可以获取用户的身份信息。
总之,Cookie、Session 和 Token 都有各自的特点和用途,可以根据具体的应用场景选择合适的技术来进行身份认证和保持用户登录状态。