在业务系统开发中,用户身份验证是保障系统服务安全性的基石。无论是社交网络、电商平台还是企业级应用,都需要确保用户的访问权限与行为与其真实身份相符。为了达到这一目的,我们通常采用了一系列技术手段来管理用户会话并验证其身份,其中最常见的是Cookie和Session机制。但是,在近年来随着微服务以及API驱动架构的发展,JSON Web Tokens(JWT)作为一种轻量级的身份验证方案得到了广泛的关注和应用。
那么,JWT究竟是什么?它的原理是如何运作的?又为何在已有Cookie和Session的基础上,仍需要引入JWT作为身份验证的一种新方法呢?
Cookie是Web开发中一种关键的客户端存储技术,它是由Web服务器在用户访问时生成并发送至用户浏览器的微型数据包,以文本文件的形式储存在用户的设备上。浏览器根据HTTP协议的规定,在之后对同一服务器的所有请求中自动附带这些Cookie信息。
服务器通过在HTTP响应头中设置Set-Cookie字段,来指示浏览器保存具有特定名称、值和其他配置参数(例如有效期、安全选项、路径限制、域名限制等)的Cookie。这样,每一个Cookie实质上就是一对键值对,帮助服务器识别和区分不同的用户及其在网站上的交互历史。
Cookie的一个核心应用是在用户身份验证和会话管理方面,它可存储用户的会话ID,使得用户在不同页面间切换或重新打开网站时,服务器能够基于这个ID重建用户的会话状态,实现无缝登录和个性化体验。此外,Cookie还广泛应用于记录用户喜好、统计分析用户行为、广告定向投放等方面,以提升用户体验和服务质量。但是,由于Cookie可能涉及用户隐私,现代浏览器和相关法规越来越重视对Cookie使用的透明度和用户控制权。
Session作为一种服务器端机制,旨在管理和维护用户在其交互期间的状态信息,这些信息被安全地存储在服务器内存或者持久化存储(如数据库)中,与仅在客户端存储的Cookie形成了鲜明对比。每当用户开始与服务器建立联系时,服务器会初始化一个新的Session对象,并将其状态数据保存在服务器内部资源中。
一旦Session建立,服务器会产生一个独一无二的Session标识符——Session ID,该ID随后会通过HTTP响应中的Cookie(通常命名规则包括但不限于JSESSIONID)下发至客户端浏览器进行存储。此后,用户的每一次请求都将携带着这个Session ID,服务器通过解析请求中的Session ID,能够在自身的Session存储区域内检索到相对应的Session对象,从而实时获取并更新用户会话的具体状态。
Session机制广泛应用在诸如认证授权场景中,用来持久化登录用户的凭证和权限等敏感信息,确保用户在浏览网站的不同页面时仍能维持登录状态和个性化体验。此外,Session还能用于暂存用户会话过程中的临时数据,比如购物车的内容、网页表单填写的中间状态等,直至会话自然终止(如超时)、用户主动登出或清理Session时,这些数据才会失效。
但是虽然Cookie和Session在会话管理中一直使用,但它们存在一些缺点如下:
为解决上述会话管理中的缺点,以及对无状态服务、更好的扩展性和安全性要求,JWT这样的新型身份验证技术就这样诞生了。JWT通过将用户信息加密打包成Token,让客户端自行携带认证信息,从而实现了服务器的无状态化。
JSON Web Tokens (JWT) 是一种开放标准(RFC 7519),定义了一种紧凑、自包含的方式来安全地在各方之间传输信息。JWT主要由三个部分组成,即Header(头部)、Payload(载荷)和Signature(签名),这三部分之间通过.分隔。
JWT的组成
JWT的内容采用Base64编码,可以直接嵌入到HTTP请求头或者URL查询参数中,因其具有可读性、自包含和防篡改的特点而广泛应用在身份验证和授权场景中。
描述了所使用的JWT类型(通常为JWT)以及签名算法(如HS256、RS256等)。
• typ: 表明这是一个JWT(固定为 "JWT")。
• alg: 指定用于签署JWT的算法,如 "HS256"(HMAC SHA-256)或 "RS256"(RSA SHA-256)等。
{ "alg": "HS256", "typ": "JWT"}
包含了实际要传递的数据,可以是任意的JSON对象,包含一组称为声明(claims)的数据。一般为用户身份信息(如用户ID、角色、权限等)和其他自定义声明,还包含一个exp(Expiration Time)字段来设置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:<namespace>:<name>)进行命名,避免冲突。
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部分用于确保JWT在传输过程中没有被篡改,它是通过对前两部分(Header和Payload编码后的字符串)使用Header中指定的加密算法以及一个共享或私有的密钥进行签名计算得到的。签名确保了只有知道该密钥的实体才能创建有效的JWT,并且任何人都可以验证JWT的完整性和来源的真实性。
生成签名的计算公式如下:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
算出签名以后,把Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,这个字符串就是JWT 。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwianRpIjoiMXBteXNldGd1amNvZGVuIiwiZXhwIjoxNTE2MjQyNjIyLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXX0.eW91cmUtYXV0aG9yaXphdGlvbi1zaWduYXR1cmU=
对于这段字符串,我们在JWT官网中,使用它的解码器进行解码,就可以得到Header、Payload、Signature这三部分。
图片
服务器首先构建JWT的Header和Payload,并分别进行Base64编码。使用Header中指定的签名算法(比如HMAC SHA-256或RSA)对编码后的Header和Payload进行签名,生成Signature。将Header、Payload和Signature连接在一起,形成完整的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 <JWT>
这种做法允许在无状态的RESTful API中方便地进行身份验证,服务器可以根据请求头中的JWT来验证请求发起者的身份和权限。同时,这种方式也便于实现JWT的刷新与撤销。
在后续的请求中,客户端将JWT随HTTP请求一起发送给服务器。服务器接收到JWT后,对其进行解码和验证Signature,如果Signature正确,则说明JWT未被篡改,并且仍处于有效期内。根据Payload中的信息,服务器可以确定用户身份以及相关权限,从而做出相应处理。
JWT有一定的生命周期,过了指定的exp字段时间后,服务器不再接受该JWT。对于长时间交互的场景,可以设计Refresh Token机制,当JWT即将过期时,客户端使用Refresh Token去服务器申请新的JWT,以延长用户会话的有效期。
图片
JWT通过签名(Signature)来防止被篡改。数字签名的机制确保了JWT的内容一旦被篡改,服务端就能检测出来,从而拒绝非法的请求。
当客户端带着JWT向服务器发送请求时,服务器会先将接收到的JWT按.分割成Header、Payload和Signature三部分。服务器使用与生成签名时相同的密钥和算法,根据接收到的Header和Payload重新计算签名。如果重新计算出的签名与接收到的Signature一致,那么可以认为JWT在传输过程中未被篡改;如果不一致,则说明JWT已被篡改。
当然,JWT通过这种方式防止被篡改的一个大前提就是,秘钥(Secret Key)是安全的,如果秘钥泄漏,那就可以更改Header、Payload,然后利用这个秘钥生成一个Signature就可以了,那么就不安全了。所以,我们一定要确保服务端使用的密钥是安全的,并且严格保密,不对外泄露。
作为使用者,防止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是无状态的,这样服务器不需要存储会话状态信息,减轻了服务器端的存储负担,在分布式环境下使用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中的声明,决定用户是否有权访问特定资源或执行某些操作。
• 单点登录(SSO):由于JWT可以跨域使用,因此在多个子系统间轻松实现SSO功能,用户在一个系统登录后,其JWT可在多个系统间传递,实现无缝登录体验。
在高并发、分布式、跨域及移动端场景下,JWT常因其实现的简洁性和高效性而得到青睐,而在注重数据安全性、复杂会话管理和易控制的场景下,Session可能会是更好的选择。同时,很多现代应用采用了混合策略,结合两者的优势来优化用户体验和系统安全性。
在实际开发中,我们可以使用Spring Boot框架结合JWT来实现用户身份验证。我们来实现一个简单的示例。
我们引入JWT的依赖包:
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.12.4</version></dependency>
创建一个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: <p></p > * @author: 码农Academy * @create: 2024/4/18 14:39 */@Componentpublic 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<SecretKey, SecretKey> 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<Claims> claimsJws = parseJWT(authToken); claimsJws.getPayload(); return true; } catch (MalformedJwtException | ExpiredJwtException | UnsupportedJwtException | IllegalArgumentException e) { return false; } } /** * 解析jwt * @param authToken 登陆token * @return */ public Jws<Claims> 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<Claims> claimsJws = parseJWT(token); return (String) claimsJws.getPayload().get("username", String.class); }}
创建一个自定义的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: <p></p > * @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<GrantedAuthority> 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以支持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: <p></p > * @author: 码农Academy * @create: 2024/4/18 16:11 */@Configuration@EnableWebSecuritypublic 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: <p></p > * @author: 码农Academy * @create: 2024/4/18 16:22 */@Servicepublic 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<? extends GrantedAuthority> getAuthorities(List<RoleEntity> roles) { return roles.stream() .map(role -> new SimpleGrantedAuthority(role.getRoleCode())) .collect(Collectors.toList()); }}
上述使用Spring Security,如果有疑问的东西可以去查一下Spring Security的工作原理。
我们在定义一个登陆接口,登陆成功后返回jwt生成的token。
import com.springboot.base.jwt.request.LoginRequest;import com.springboot.base.jwt.response.JwtResponse;import com.springboot.base.jwt.util.JwtTokenUtil;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.authentication.BadCredentialsException;import org.springframework.security.authentication.DisabledException;import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;import org.springframework.security.core.Authentication;import org.springframework.security.core.AuthenticationException;import org.springframework.security.core.context.SecurityContextHolder;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/** * @version 1.0 * @description: <p></p > * @author: 码农Academy * @create: 2024/4/18 17:31 */@RestController@RequestMapping("/user")public class LoginController { @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private AuthenticationManager authenticationManager; @PostMapping("/login") public ResponseEntity<?> 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: <p></p > * @author: 码农Academy * @create: 2024/4/18 18:59 */@Datapublic 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方案时,应充分考虑其适用范围,并配合合适的刷新策略、黑名单机制以及其他安全措施,确保系统的整体安全性和用户体验。
本文链接://www.dmpip.com//www.dmpip.com/showinfo-26-84909-0.html快手二面:有了Cookie和Session 为什么还要JWT ?你说一下JWT的原理?
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com