快手二面:有了Cookie和Session 为什么还要JWT ?你说一下JWT的原理?

2024年 4月 23日 86.9k 0

引言

在业务系统开发中,用户身份验证是保障系统服务安全性的基石。无论是社交网络、电商平台还是企业级应用,都需要确保用户的访问权限与行为与其真实身份相符。为了达到这一目的,我们通常采用了一系列技术手段来管理用户会话并验证其身份,其中最常见的是Cookie和Session机制。但是,在近年来随着微服务以及API驱动架构的发展,JSON Web Tokens(JWT)作为一种轻量级的身份验证方案得到了广泛的关注和应用。

那么,JWT究竟是什么?它的原理是如何运作的?又为何在已有Cookie和Session的基础上,仍需要引入JWT作为身份验证的一种新方法呢?

Cookie和Session

Cookie是什么?

Cookie是Web开发中一种关键的客户端存储技术,它是由Web服务器在用户访问时生成并发送至用户浏览器的微型数据包,以文本文件的形式储存在用户的设备上。浏览器根据HTTP协议的规定,在之后对同一服务器的所有请求中自动附带这些Cookie信息。

服务器通过在HTTP响应头中设置Set-Cookie字段,来指示浏览器保存具有特定名称、值和其他配置参数(例如有效期、安全选项、路径限制、域名限制等)的Cookie。这样,每一个Cookie实质上就是一对键值对,帮助服务器识别和区分不同的用户及其在网站上的交互历史。

Cookie的一个核心应用是在用户身份验证和会话管理方面,它可存储用户的会话ID,使得用户在不同页面间切换或重新打开网站时,服务器能够基于这个ID重建用户的会话状态,实现无缝登录和个性化体验。此外,Cookie还广泛应用于记录用户喜好、统计分析用户行为、广告定向投放等方面,以提升用户体验和服务质量。但是,由于Cookie可能涉及用户隐私,现代浏览器和相关法规越来越重视对Cookie使用的透明度和用户控制权。

Seeion是什么?

Session作为一种服务器端机制,旨在管理和维护用户在其交互期间的状态信息,这些信息被安全地存储在服务器内存或者持久化存储(如数据库)中,与仅在客户端存储的Cookie形成了鲜明对比。每当用户开始与服务器建立联系时,服务器会初始化一个新的Session对象,并将其状态数据保存在服务器内部资源中。

一旦Session建立,服务器会产生一个独一无二的Session标识符——Session ID,该ID随后会通过HTTP响应中的Cookie(通常命名规则包括但不限于JSESSIONID)下发至客户端浏览器进行存储。此后,用户的每一次请求都将携带着这个Session ID,服务器通过解析请求中的Session ID,能够在自身的Session存储区域内检索到相对应的Session对象,从而实时获取并更新用户会话的具体状态。

Session机制广泛应用在诸如认证授权场景中,用来持久化登录用户的凭证和权限等敏感信息,确保用户在浏览网站的不同页面时仍能维持登录状态和个性化体验。此外,Session还能用于暂存用户会话过程中的临时数据,比如购物车的内容、网页表单填写的中间状态等,直至会话自然终止(如超时)、用户主动登出或清理Session时,这些数据才会失效。

但是虽然Cookie和Session在会话管理中一直使用,但它们存在一些缺点如下:

  • Cookie数量和大小限制:浏览器对Cookie的数量和大小有限制,可能导致用户数据存储受限。
  • 服务器资源占用:Session需要在服务器端存储大量用户会话信息,随着并发用户数的增长,可能会造成服务器内存资源紧张。
  • 跨域问题:Cookie默认遵循同源策略,不便于跨域共享会话状态。
  • 分布式系统中的Session同步:在分布式环境或集群部署的情况下,Session数据需要在各服务器之间同步,增加了系统的复杂性。

为解决上述会话管理中的缺点,以及对无状态服务、更好的扩展性和安全性要求,JWT这样的新型身份验证技术就这样诞生了。JWT通过将用户信息加密打包成Token,让客户端自行携带认证信息,从而实现了服务器的无状态化。

JWT是什么?

JSON Web Tokens (JWT) 是一种开放标准(RFC 7519),定义了一种紧凑、自包含的方式来安全地在各方之间传输信息。JWT主要由三个部分组成,即Header(头部)、Payload(载荷)和Signature(签名),这三部分之间通过.分隔。

JWT的组成JWT的组成

JWT的内容采用Base64编码,可以直接嵌入到HTTP请求头或者URL查询参数中,因其具有可读性、自包含和防篡改的特点而广泛应用在身份验证和授权场景中。

Header

描述了所使用的JWT类型(通常为JWT)以及签名算法(如HS256、RS256等)。

• typ: 表明这是一个JWT(固定为 "JWT")。

• alg: 指定用于签署JWT的算法,如 "HS256"(HMAC SHA-256)或 "RS256"(RSA SHA-256)等。

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload

包含了实际要传递的数据,可以是任意的JSON对象,包含一组称为声明(claims)的数据。一般为用户身份信息(如用户ID、角色、权限等)和其他自定义声明,还包含一个exp(Expiration Time)字段来设置JWT的有效期以及其他元数据。声明分为三种类型:

  • Registered Claims(注册声明) 这些是预先定义好的标准声明,虽然不是必须的,但在JWT规范中建议使用。它们提供了一套通用的信息,有助于JWT的标准化处理。常见的注册声明包括:
  • • iss(issuer):签发JWT的实体。

    • sub(subject):JWT所面向的用户或主题。

    • aud(audience):预期接收JWT的受众。

    • exp(expiration time):JWT过期时间,在此时间之后JWT应被视为无效。

    • nbf(not before):JWT生效时间之前,不应被接受处理的时间点。

    • iat(issued at):JWT的创建时间。

    • jti(JWT ID):JWT的唯一标识符,可用于防止重放攻击。

    2. Public Claims(公共声明) 公共声明是预留用于行业共识的标准声明,虽然目前并未正式注册到IANA JSON Web Token Registry,但可以在IANA JSON Web Token Registry 查看已注册的声明集。如果没有官方注册,但多个项目间需要共享相同的声明,可以选择将声明名称以特定前缀(如 urn::)进行命名,避免冲突。

    3. Private Claims(私有声明) 私有声明是用于应用内部约定的自定义声明,我们可以根据自己业务需求自由定义。例如,可以包含用户ID (userId)、用户角色 (role)、邮箱地址 (email) 等任何想要在JWT中传递的信息。

    示例:

    {
      "sub": "1234567890",
      "name": "John Doe",
      "admin": true,
      "iat": 1516239022,
      "jti": "1pmysetgujcoden",
      "exp": 1516242622,
      "scope": [
        "read",
        "write"
      ]
    }

    Payload部分默认是不加密的,一定不要将隐私信息存放在 Payload 当中!!!

    Signature

    Signature部分用于确保JWT在传输过程中没有被篡改,它是通过对前两部分(Header和Payload编码后的字符串)使用Header中指定的加密算法以及一个共享或私有的密钥进行签名计算得到的。签名确保了只有知道该密钥的实体才能创建有效的JWT,并且任何人都可以验证JWT的完整性和来源的真实性。

    生成签名的计算公式如下:

    HMACSHA256(
      base64UrlEncode(header) + "." +
      base64UrlEncode(payload),
      secret)

    算出签名以后,把Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,这个字符串就是JWT 。

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwianRpIjoiMXBteXNldGd1amNvZGVuIiwiZXhwIjoxNTE2MjQyNjIyLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXX0.eW91cmUtYXV0aG9yaXphdGlvbi1zaWduYXR1cmU=

    对于这段字符串,我们在JWT官网中,使用它的解码器进行解码,就可以得到Header、Payload、Signature这三部分。

    图片图片

    如何基于JWT进行身份验证?

    JWT生成

    服务器首先构建JWT的Header和Payload,并分别进行Base64编码。使用Header中指定的签名算法(比如HMAC SHA-256或RSA)对编码后的Header和Payload进行签名,生成Signature。将Header、Payload和Signature连接在一起,形成完整的JWT字符串。

    JWT发放

    用户通过用户名/密码登录后,服务器验证用户身份无误,便生成JWT并将其返回给客户端。客户端可以将JWT存储在浏览器的LocalStorage、SessionStorage中,或者作为Bearer Token放在Authorization请求头中。

    这里需要注意,将JWT存储在浏览器的localStorage中,相较于存储在Cookie中,可以降低CSRF(跨站请求伪造)的风险。因为CSRF攻击依赖于浏览器自动附带在请求中的Cookie,而localStorage中的数据不会自动包含在普通的HTTP请求头部。

    至于携带JWT的方式,建议将JWT放在HTTP Header的Authorization字段中,采用Bearer Token的形式:

    Authorization: Bearer 

    这种做法允许在无状态的RESTful API中方便地进行身份验证,服务器可以根据请求头中的JWT来验证请求发起者的身份和权限。同时,这种方式也便于实现JWT的刷新与撤销。

    JWT使用

    在后续的请求中,客户端将JWT随HTTP请求一起发送给服务器。服务器接收到JWT后,对其进行解码和验证Signature,如果Signature正确,则说明JWT未被篡改,并且仍处于有效期内。根据Payload中的信息,服务器可以确定用户身份以及相关权限,从而做出相应处理。

    JWT过期与刷新

    JWT有一定的生命周期,过了指定的exp字段时间后,服务器不再接受该JWT。对于长时间交互的场景,可以设计Refresh Token机制,当JWT即将过期时,客户端使用Refresh Token去服务器申请新的JWT,以延长用户会话的有效期。

    图片图片

    JWT安全性

    JWT如何防止被篡改?

    JWT通过签名(Signature)来防止被篡改。数字签名的机制确保了JWT的内容一旦被篡改,服务端就能检测出来,从而拒绝非法的请求。

    当客户端带着JWT向服务器发送请求时,服务器会先将接收到的JWT按.分割成Header、Payload和Signature三部分。服务器使用与生成签名时相同的密钥和算法,根据接收到的Header和Payload重新计算签名。如果重新计算出的签名与接收到的Signature一致,那么可以认为JWT在传输过程中未被篡改;如果不一致,则说明JWT已被篡改。

    当然,JWT通过这种方式防止被篡改的一个大前提就是,秘钥(Secret Key)是安全的,如果秘钥泄漏,那就可以更改Header、Payload,然后利用这个秘钥生成一个Signature就可以了,那么就不安全了。所以,我们一定要确保服务端使用的密钥是安全的,并且严格保密,不对外泄露。

    如何加强JWT的安全性?

    作为使用者,防止JWT被篡改的主要措施并不直接体现在客户端的操作上,而是依赖于服务端的安全设计和实施。我们可以通过以下方面考虑,加强JWT的安全性:

    1. 服务端安全策略:首先我们必须确保服务端使用的密钥是安全的,并且严格保密,不对外泄露。密钥用于签署和验证JWT,一旦密钥泄露,可能导致JWT被伪造或篡改。并且还要采用安全强度足够高的签名算法,例如HS256、RS256等,这些算法能有效地确保JWT的完整性。

    2. 正确的签名流程:确保所有签发的JWT都经过服务器端签名,并且每次验证JWT时都要使用相同的密钥和算法进行验证。

    3. 设置合理的Token有效期:设置JWT的有效期(exp claim)限制,避免JWT长时间有效,减少恶意攻击者篡改JWT后继续使用的机会。

    4. 严谨的身份验证逻辑:在服务端验证JWT时,除了验证签名之外,还应对Payload中的其他重要声明(如用户ID、权限、过期时间等)进行校验。

    5. 客户端存储方式:尽管JWT被篡改的风险主要集中在网络传输阶段,但在客户端存储JWT时,推荐使用localStorage而非Cookie(尤其是考虑到防止CSRF攻击),同时也可以考虑对敏感信息加密存储。

    JWT相较于Cookie和Session的优缺点

    JWT的优点

    JWT是无状态的,这样服务器不需要存储会话状态信息,减轻了服务器端的存储负担,在分布式环境下使用JWT可以在集群内的任何节点验证,无需保持会话状态同步,JWT还可以轻易地在不同域名的服务之间传递,适用于微服务架构和跨域应用。因为它包含了所有必要的用户信息,所以在服务间跳转时无需再次验证用户身份,每个请求都包含了认证所需要的所有信息,减少了服务器查询数据库或存储服务以验证用户状态的次数,从而提高了效率。

    JWT的缺点

    JWT中的数据默认情况下是Base64编码的,虽然可以加密但并非强制要求,这使得在不加密的情况下,JWT不适合存储敏感信息。相比而言,Session存储在服务器端,安全性更高。如果JWT被盗取,黑客可以在有效期内持续使用,直到token过期或被撤销。另外JWT的体积相对较大,特别是当包含更多信息时。每次HTTP请求都需要携带JWT,可能会增加请求头的大小,尤其是在移动网络环境下,可能会对带宽和流量有较大影响。并且由于JWT自带有效期,当用户登出时,由于JWT无法立即失效,所以只要JWT发放出去,除非在服务器端维护黑名单或使用可撤销的Token机制(如JWT ID Token配合OpenID Connect协议),否则无法立即废弃或更改Token中的权限信息。

    JWT应用场景

    • 身份验证:JWT通常用于用户登录认证,服务器在验证用户凭据成功后,生成一个带有用户特定信息和过期时间的JWT,客户端收到后,在后续请求中将其作为凭证发送给服务器,服务器仅需验证JWT的签名即可确认用户身份,无需持久化存储会话信息。

    • 授权:JWT可以携带权限声明,服务器通过解析JWT中的声明,决定用户是否有权访问特定资源或执行某些操作。

    • 单点登录(SSO):由于JWT可以跨域使用,因此在多个子系统间轻松实现SSO功能,用户在一个系统登录后,其JWT可在多个系统间传递,实现无缝登录体验。

    在高并发、分布式、跨域及移动端场景下,JWT常因其实现的简洁性和高效性而得到青睐,而在注重数据安全性、复杂会话管理和易控制的场景下,Session可能会是更好的选择。同时,很多现代应用采用了混合策略,结合两者的优势来优化用户体验和系统安全性。

    如何使用JWT?

    在实际开发中,我们可以使用Spring Boot框架结合JWT来实现用户身份验证。我们来实现一个简单的示例。

    我们引入JWT的依赖包:

    
        io.jsonwebtoken
        jjwt
        0.12.4
    

    创建JWT工具类

    创建一个JWT工具类,用于生成和验证JWT令牌。这个类通常会包含生成JWT、解析JWT以及提取其中载荷信息的方法。

    import io.jsonwebtoken.*;
    import io.jsonwebtoken.security.Keys;
    import io.jsonwebtoken.security.SecureDigestAlgorithm;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.stereotype.Component;
    
    import javax.crypto.SecretKey;
    import java.util.Date;
    
    /**
     * @version 1.0
     * @description: 

    * @author: 码农Academy * @create: 2024/4/18 14:39 */ @Component public class JwtTokenUtil { @Value("${security.jwt.token.secret-key}") private String secretKey; /**过期时间*/ private static final long VALIDITY_IN_MILLISECONDS = 3600000; // 1h /** * 生成JWT * @param authentication 当前登陆用户信息 * @return JWT */ public String generateToken(Authentication authentication) { User user = (User) authentication.getPrincipal(); Date now = new Date(); // 设置过期时间 Date expiryDate = new Date(now.getTime() + VALIDITY_IN_MILLISECONDS); //指定加密算法 SecureDigestAlgorithm algorithm = Jwts.SIG.HS256; //密钥实例 SecretKey key = Keys.hmacShaKeyFor(secretKey.getBytes()); return Jwts.builder() .subject(user.getUsername()) .signWith(key, algorithm) //设置签名使用的签名算法和签名使用的秘钥 .expiration(expiryDate) //设置过期时间 .claim("username", user.getUsername()) //设置自定义负载信息 .compact(); } /** * 验证JWT * @param authToken * @return */ public boolean validateToken(String authToken) { try { Jws claimsJws = parseJWT(authToken); claimsJws.getPayload(); return true; } catch (MalformedJwtException | ExpiredJwtException | UnsupportedJwtException | IllegalArgumentException e) { return false; } } /** * 解析jwt * @param authToken 登陆token * @return */ public Jws parseJWT(String authToken){ //密钥实例 SecretKey key = Keys.hmacShaKeyFor(secretKey.getBytes()); return Jwts.parser() .verifyWith(key) //设置签名的密钥 .build() .parseSignedClaims(authToken); //设置要解析的jwt } /** * 从JWT中获取用户名 * @param token * @return */ public String getUsernameFromToken(String token) { Jws claimsJws = parseJWT(token); return (String) claimsJws.getPayload().get("username", String.class); } }

    实现JWT过滤器

    创建一个自定义的JWT过滤器,它负责检查每个请求的Authorization header中的Bearer Token,并对其进行验证。验证成功后,创建一个Authentication对象,并将其置于SecurityContext中。

    import com.springboot.base.jwt.util.JwtTokenUtil;
    import org.springframework.security.authentication.AbstractAuthenticationToken;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
    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.List;
    import java.util.stream.Collectors;
    
    /**
     * @version 1.0
     * @description: 

    * @author: 码农Academy * @create: 2024/4/18 16:15 */ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final UserDetailsService userDetailsService; private final JwtTokenUtil jwtTokenUtil; private static final String BEARER_PREFIX = "Bearer "; public JwtAuthenticationFilter(UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil) { this.userDetailsService = userDetailsService; this.jwtTokenUtil = jwtTokenUtil; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String jwtToken = getTokenFromRequest(request); if (jwtToken != null && jwtTokenUtil.validateToken(jwtToken)) { String username = jwtTokenUtil.getUsernameFromToken(jwtToken); UserDetails userDetails = userDetailsService.loadUserByUsername(username); // 用于权限校验 List authorities = userDetails.getAuthorities().stream() .map(authority -> new SimpleGrantedAuthority(authority.getAuthority())) .collect(Collectors.toList()); AbstractAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( userDetails, null, authorities ); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); }else { // 如果出现异常,可能是无效的Token,此处可记录日志或做其他处理 System.out.println("没有登陆。。。。。。"); } filterChain.doFilter(request, response); } private String getTokenFromRequest(HttpServletRequest request) { String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith(BEARER_PREFIX)) { return authHeader.replace(BEARER_PREFIX, ""); } return null; } }

    配置Spring Security

    配置Spring Security以支持JWT身份验证。这通常涉及编写自定义的JWT过滤器,用于拦截请求并在请求头中提取JWT令牌进行验证。过滤器会在验证通过后将用户信息填充到Spring Security的上下文中。

    import com.springboot.base.jwt.filter.JwtAuthenticationFilter;
    import com.springboot.base.jwt.util.JwtTokenUtil;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.config.http.SessionCreationPolicy;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    
    /**
     * @version 1.0
     * @description: 

    * @author: 码农Academy * @create: 2024/4/18 16:11 */ @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private UserDetailsService userDetailsService; @Override public void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests((requests) -> requests .antMatchers("/user/login").permitAll() // 登录接口公开 .anyRequest().authenticated() // 其他接口需要验证 ) .addFilterBefore(new JwtAuthenticationFilter(userDetailsService, jwtTokenUtil), UsernamePasswordAuthenticationFilter.class) // 添加JWT过滤器 .sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); // 关闭Session管理 } @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }

    在实现Spring Security的UserDetailsService的接口,用于登陆时查询用户信息,构造Spring Security的User信息。

    import com.springboot.base.jwt.login.RoleEntity;
    import com.springboot.base.jwt.login.UserEntity;
    import com.springboot.base.jwt.repository.UserRepository;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.stereotype.Service;
    
    import java.util.Collection;
    import java.util.List;
    import java.util.stream.Collectors;
    
    /**
     * @version 1.0
     * @description: 

    * @author: 码农Academy * @create: 2024/4/18 16:22 */ @Service public class UserDetailsServiceImpl implements UserDetailsService { private final UserRepository userRepository; public UserDetailsServiceImpl(UserRepository userRepository) { this.userRepository = userRepository; } @Override public UserDetails loadUserByUsername(String username) { // 根据用户名查询用户实体 UserEntity userEntity = userRepository.findByUserName(username); // 如果用户不存在,则抛出异常 if (userEntity == null) { throw new UsernameNotFoundException("User not found with username: " + username); } // 将用户实体转换为Spring Security可识别的UserDetails对象 User user = new User(userEntity.getUserName(), userEntity.getPassword(), // 注意:此处假设password字段是已经经过加密处理过的 userEntity.getEnabled(), true, true, true, getAuthorities(userEntity.getRoleList()) // 获取用户的角色并转换为GrantedAuthority列表 ); System.out.println(user.getPassword()); return user; } private Collection createAuthenticationToken(@RequestBody LoginRequest loginRequest) throws Exception { // 创建基于用户名密码的Authentication请求对象 UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( loginRequest.getUserName(), loginRequest.getPassword() ); try { // 使用AuthenticationManager进行身份验证 Authentication authentication = authenticationManager.authenticate(authToken); // 如果身份验证成功,SecurityContextHolder会存储该Authentication对象 SecurityContextHolder.getContext().setAuthentication(authentication); // 生成JWT Token或其他形式的授权凭证 String token = jwtTokenUtil.generateToken(authentication); // 返回带有Token的响应 return ResponseEntity.ok(new JwtResponse(token)); } catch (BadCredentialsException ex) { // 处理用户名/密码不正确等错误情况 System.out.println("密码不正确"); return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid credentials"); } catch (DisabledException ex) { // 处理账户被禁用等情况 System.out.println("处理账户被禁用"); return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Account is disabled"); } catch (AuthenticationException e) { System.out.println("Authentication failed"); // 其他身份验证异常 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Authentication failed"); } } }
    import com.fasterxml.jackson.annotation.JsonCreator;
    import lombok.Data;
    
    /**
     * @version 1.0
     * @description: 

    * @author: 码农Academy * @create: 2024/4/18 18:59 */ @Data public class JwtResponse { private String token; private String type = "Bearer"; public JwtResponse(String token) { this.token = token; } @JsonCreator public static JwtResponse of(String token) { return new JwtResponse(token); } }

    我们模拟一下登陆发起请求:

    登陆成功,返回token。

    给一个错误密码,登陆失败:

    我们将token设置到Authorization中,发起其他业务请求:

    接口请求成功。

    我们改变一下token值,在发起请求:

    后台打印token校验失败,即未登录。

    结论

    JWT(JSON Web Tokens)作为一种轻量级且灵活的身份验证和授权机制,在现代web服务及移动应用中得到了广泛应用。它允许服务器端通过加密签名的方式向客户端发放安全的、自包含的令牌,这些令牌可以携带必要的用户身份信息和权限声明,而且由于其无需持久化存储的特性,非常适合于微服务架构下的无状态通信场景。

    总结起来,采用JWT进行身份验证具有以下优点:

    • 安全性: 通过密钥签名保证令牌的安全性,防止篡改。

    • 高效性: 状态less设计减少了服务器端存储负担,提升了系统的可扩展性和响应速度。

    • 跨域友好: JWT可轻松应用于多个域名或子系统间的认证需求。

    • 自包含性: 令牌自身携带了足够的用户信息,减轻了服务器端查询数据库的频率。

    • 有效期可控: 可以在JWT中设置过期时间,从而控制用户的登录会话持续时间。

    不过需要注意的是,JWT并非适用于所有场景,尤其是在敏感数据的处理上,因为一旦令牌被截获,黑客们在有效期内可以持续使用。因此,在实施JWT方案时,应充分考虑其适用范围,并配合合适的刷新策略、黑名单机制以及其他安全措施,确保系统的整体安全性和用户体验。

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论