响应式网站外包,阿里云网站如何做淘宝客,包头企业网站建设公司,ps做网站教程springboot3.x jwtspring security6.x实现用户登录认证
什么是JWT
JWT#xff08;JSON Web Token#xff09;是一种开放标准#xff08;RFC 7519#xff09;#xff0c;它用于在网络应用环境中传递声明。通常#xff0c;JWT用于身份验证和信息交换。JWT的一个典型用法是…springboot3.x jwtspring security6.x实现用户登录认证
什么是JWT
JWTJSON Web Token是一种开放标准RFC 7519它用于在网络应用环境中传递声明。通常JWT用于身份验证和信息交换。JWT的一个典型用法是在Web应用中实现基于Token的身份验证。
JWT 的特点
自包含Self-containedJWT 包含了用户的认证信息和其他相关的数据因此无需在服务器端存储会话信息。它是通过加密或签名的方式来保证数据的完整性和防止篡改。
JWT的结构
JWT由三部分组成
Header头部包含令牌的类型通常是JWT和加密算法如HMAC SHA256或RSA。 Payload负载包含声明Claims。这些声明可以是关于实体通常是用户和附加数据的断言。例如可以包含用户的ID、角色、权限等。 Signature签名用来验证消息的完整性以及发送者的身份。通过Header中的算法和密钥生成签名。
什么是spring Security
Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架通常用于保护 Spring 应用程序。它提供了各种安全功能如
认证Authentication用于确认用户的身份。例如检查用户名和密码。 授权Authorization用于控制用户是否有权限访问某些资源。通常通过角色或权限进行授权。 防护功能提供 CSRF 防护、会话管理、跨站点请求伪造防护等安全功能。
Spring Security 是一个全面的安全框架可以与多种身份验证机制集成如基于表单的登录、OAuth2、LDAP、JWT 等。
有了spring Security为什么还需要JWT
Spring Security 本身并不负责管理用户登录后如何保持会话的状态这部分通常是通过 会话Session以前的方式 或 JWT 来完成的。
JWT 和 spring Security关系
Spring Security 和 JWT 之间并没有直接的关系但是它们可以很好地结合使用。在一个典型的现代 Web 应用中Spring Security 负责保护应用的安全性而 JWT 通常用于实现基于 token 的无状态认证。
Spring Boot 中的安全验证和存储用户信息
在Spring Boot应用中常用的安全验证方式是结合 Spring Security 和 JWT。Spring Security提供了多种身份验证机制而JWT则用于实现无状态的认证方案。JWT通常在用户登录成功后生成并作为后续请求的认证凭证。
一般流程
用户登录用户通过用户名和密码发送请求给后端后端验证用户身份。生成JWT身份验证通过后后端生成JWT并将其发送给前端。JWT通常在HTTP响应的Authorization头中返回。存储JWT前端将JWT存储在本地例如localStorage或sessionStorage或Cookie中。后续请求每次前端向后端发起请求时JWT都会包含在请求头的Authorization字段中后端通过JWT验证用户身份。验证JWT后端通过Spring Security中的过滤器解码JWT并验证签名。如果JWT有效用户身份就会被确认。
为什么说JWT是无状态登录
是因为它的使用方式不需要在服务器端存储会话信息也就是说服务器不需要保存任何关于客户端的状态信息
有状态和无状态
有状态认证Stateful Authentication
在传统的认证机制中服务器会维护一个会话session来记录客户端的身份信息。例如在基于会话的认证中当用户成功登录时服务器会创建一个会话并生成一个唯一的会话 ID。这个会话 ID 会存储在服务器端的内存或数据库中且会在客户端的浏览器中以 Cookie 或其他方式保存例如JSESSIONID。每次客户端发起请求时都会携带会话 ID服务器根据会话 ID 查找会话信息来确认用户身份。
问题服务器需要维护会话状态。每当用户发送请求时服务器需要查询存储的会话数据。这会增加服务器的负担尤其是在分布式系统中需要将会话数据共享或同步到不同的服务器节点。
无状态认证Stateless Authentication
与会话认证不同无状态认证不需要在服务器上存储任何客户端的会话信息。服务器验证身份时通过客户端携带的信息如 JWT来验证请求而不依赖于服务器端存储的会话状态。JWT 本身包含了足够的信息来完成身份验证和授权服务器只需解析和验证这个 token而无需查询任何存储的信息。
为什么不手动封装json存储
数据完整性与安全性
JWT JWT 的一个核心特性是 签名它确保了数据的 完整性和安全性。JWT 中的签名是由服务器的私钥生成的任何人如果修改了 JWT 的内容签名就会变得无效服务器就能发现数据被篡改。 例如如果你将用户信息存储为 JSON前端将其发送给服务器那么攻击者可能会篡改这个 JSON 数据如修改用户角色、权限等而服务器无法判断数据是否被篡改除非你做额外的安全处理比如加密。 自定义 JSON 如果你让前端封装 JSON 并发送到服务器服务器将无法知道数据是否被篡改。没有类似 JWT 签名的机制前端发送的 JSON 数据就很容易被篡改。为了解决这个问题你需要自己实现一些安全机制如加密或自定义签名但这会增加实现的复杂度。
无状态性与有效期管理
JWTJWT 是自包含的它不仅仅包含用户信息还可以包含 过期时间exp等信息。这样服务器可以非常容易地判断一个 JWT 是否过期无需查询数据库或任何状态。
自定义 JSON如果前端仅仅发送一个 JSON 数据包服务器就无法知道该数据是否已经过期。为了实现这一点你需要自己维护一个机制比如在 JSON 中加入时间戳或者在服务器端保存用户的会话过期时间。这会导致服务器变得有状态因为服务器需要管理每个用户的会话生命周期。
服务器性能与分布式架构支持
JWT由于 JWT 是自包含的所有的信息都在 token 中服务器不需要存储任何会话信息这意味着它是 无状态的。无状态性在 分布式架构 中特别有优势因为多个微服务可以独立地验证 JWT 而无需共享会话数据。
自定义 JSON如果你选择自己封装 JSON并由前端存储和发送每次请求时服务器就需要检查和验证这个 JSON 信息。尤其是在分布式环境中你可能需要通过共享会话存储如 Redis来确保各个服务都能正确验证用户身份增加了管理的复杂性。
可扩展性
JWT由于 JWT 是标准化的支持的库和工具非常多你可以很容易地实现不同平台如前端、后端、移动端等之间的认证而且不需要太多的额外工作。JWT 自带的灵活性和规范使得它可以适应多种需求。
自定义 JSON如果你手动封装 JSON 并且实现自己的认证机制虽然在小范围内可以工作但这种方法缺乏标准化和统一性扩展性差。在系统规模扩大或需求变化时可能会遇到很多维护和兼容性问题。
实现
1.引入依赖
引入JWT依赖 !-- JWT library --dependencygroupIdio.jsonwebtoken/groupIdartifactIdjjwt/artifactIdversion0.11.2/version/dependency!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl --dependencygroupIdio.jsonwebtoken/groupIdartifactIdjjwt-impl/artifactIdversion0.11.2/versionscoperuntime/scope/dependencydependencygroupIdio.jsonwebtoken/groupIdartifactIdjjwt-jackson/artifactIdversion0.11.2/versionscoperuntime/scope/dependency引入spring Security依赖 !-- Spring Security for Authentication --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-security/artifactId/dependency2.实现 UserDetailsService接口
UserDetailsService 是 Spring Security 中一个非常重要的接口负责从数据库或其他存储中加载用户的详细信息通常用于身份验证和授权过程。
在 Spring Security 中用户信息通常存储在数据库或其他外部系统中。为了进行认证和授权Spring Security 需要从这些存储中获取用户的详细信息如用户名、密码、角色、权限等。为了实现这一点Spring Security 提供了 UserDetailsService 接口它定义了如何加载这些用户信息。
package com.example.demonew.service.loginService;import com.example.demonew.entity.UserInfo;
import com.example.demonew.mapper.loginMapper.LoginMapper;
import org.springframework.beans.factory.annotation.Autowired;
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.Map;Service
public class CustomUserDetailsService implements UserDetailsService {Autowiredprivate LoginMapper loginMapper;Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {UserInfo user loginMapper.getUserInfo(username);if (user null) {throw new UsernameNotFoundException(User not found);}//将信息添加到User也可以说是UserDetails 对象中return User.withUsername(user.getUsername()).password(user.getPassword()).roles(user.getRole()).build();}}
总结
实现 UserDetailsService 接口的主要目的是将用户的认证信息包括用户名、密码、角色等从数据库等存储中提取出来并转化为 Spring Security 能够理解并使用的 UserDetails 对象。在 Spring Security 的认证和授权流程中UserDetailsService 是用来提供用户信息的核心组件。
当用户尝试登录时Spring Security 会调用 loadUserByUsername 方法来查找用户并使用返回的 UserDetails 来进行身份验证和授权。因此实现 UserDetailsService 是将自定义的用户数据源如数据库与 Spring Security 进行集成的关键步骤。
3.实现JWT工具类
这里工具类实现了生成jwt token解密jwt, 以及通过jwt获取用户名验证令牌等功能方便后续调用
package com.example.demonew.util;import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;import java.security.Key;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;Component
Slf4j
public class JwtTokenProviderUtil {Value(${app.jwt-secret})private String jwtSecret;Value(${app.jwt-expiration-milliseconds})private long jwtExpirationDate;private Key key;// 使用懒加载方式获取密钥private Key getKey() {if (key null) {synchronized (this) {if (key null) {key Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtSecret));}}}return key;}// 生成 JWT tokenpublic String generateToken(Authentication authentication){String username authentication.getName();Date currentDate new Date();Date expireDate new Date(currentDate.getTime() jwtExpirationDate);String token Jwts.builder().setSubject(username).setIssuedAt(new Date()).setExpiration(expireDate).signWith(getKey(),SignatureAlgorithm.HS256).compact();return token;}private Key key(){return Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtSecret));}//解密tokenpublic String resolveToken(HttpServletRequest request) {String bearerToken request.getHeader(Authorization);if (bearerToken ! null bearerToken.startsWith(Bearer )) {return bearerToken.substring(7);}return null;}// 解析 JWT 令牌并返回其主体private Claims getClaimsFromToken(String token) {try {return Jwts.parserBuilder().setSigningKey(getKey()).build().parseClaimsJws(token).getBody();} catch (JwtException e) {log.error(Error parsing JWT token: {}, e.getMessage());throw new RuntimeException(JWT token parsing failed, e);}}// 验证 JWT 令牌public boolean validateToken(String token) {try{Jwts.parserBuilder().setSigningKey(getKey()).build().parseClaimsJws(token);return true;} catch (MalformedJwtException e) {log.error(Invalid JWT token: {}, e.getMessage());} catch (ExpiredJwtException e) {log.error(JWT token is expired: {}, e.getMessage());} catch (UnsupportedJwtException e) {log.error(JWT token is unsupported: {}, e.getMessage());} catch (IllegalArgumentException e) {log.error(JWT claims string is empty: {}, e.getMessage());}return false;}// 获取用户认证信息public Authentication getAuthentication(String token) {String username getUsername(token);ListGrantedAuthority authorities getAuthorities(token);return new UsernamePasswordAuthenticationToken(username, , authorities);}//通过jwt信息获取用户名public String getUsername(String token) {Claims claims getClaimsFromToken(token);String username claims.getSubject();return username;}public ListGrantedAuthority getAuthorities(String token) {Claims claims getClaimsFromToken(token);ListString roles (ListString)claims.get(roles);return roles.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());}}
设置密钥和过期时间
app:jwt-secret: 60fosjWhlsy6bxLjSv5P/8fvmvanEEAjUQm3KLkSuMc # 密钥 不唯一自己设置jwt-expiration-milliseconds: 604800000随机生成密钥 Testvoid jwtSecretGenerator (){SecureRandom random new SecureRandom();byte[] secret new byte[32]; // 256 位random.nextBytes(secret);String jwtSecret Base64.getEncoder().encodeToString(secret); // 将字节数组转换为 Base64 编码字符串System.out.println(jwtSecret);}4.spring Security配置类
Spring Security 配置类主要用于设置安全策略过滤器链以及禁用默认的 CSRF 等。 spring boot3.0废弃了extends WebSecurityConfigurerAdapter的方式所以这里采用添加Bean新方式
SecurityConfig 是 Spring Security 的配置类作用是配置 Spring Security 的安全策略进行身份认证和授权管理
可以看出spring Security就是通过http来做跨域权限等控制
package com.example.demonew.config;import com.example.demonew.service.loginService.CustomUserDetailsService;
import jakarta.annotation.Resource;
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.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
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.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;Configuration
EnableWebSecurity // 确保添加这个注解来启用 Spring Security 配置
public class SecurityConfig {Resourceprivate CustomUserDetailsService userDetailsService;Autowiredprivate JwtAuthenticationFilter jwtAuthenticationFilter;Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {// 配置 HTTP 安全策略http.csrf(csrf - csrf.disable()) // 由于你使用的是前后端分离的架构CSRF 防护通常是不需要的。禁用 CSRF 防护可以避免 Spring Security 对跨站请求伪造攻击的保护从而简化 API 调用。//授权配置.authorizeHttpRequests(authz - authz.requestMatchers(/login).permitAll() // 允许所有用户访问 /user/login 路径下的资源通常是登录接口。其他接口需要认证后访问。.requestMatchers(/test/**).hasAuthority(ROLE_ADMIN) // 仅允许具有 ADMIN 角色的用户访问 /test/** 路径下的资源。.anyRequest().authenticated() //所有其他请求都需要进行身份认证才能访问。)// jwtAuthenticationFilter是一个自定义的过滤器它负责处理请求中的 JWT 认证。此过滤器被配置为在 Spring Security 自带的 UsernamePasswordAuthenticationFilter 之前执行这意味着在 Spring Security 处理用户名和密码认证之前会先进行 JWT 校验。.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class).userDetailsService(userDetailsService) //配置自定义的 UserDetailsService 用于加载用户信息.formLogin(form - form.disable()) // Spring Security 默认启用基于表单的登录认证。如果你使用的是前后端分离的方式通常不会使用传统的表单登录因此禁用它。.logout(logout - logout.permitAll()); // 配置登出功能允许所有用户进行登出。return http.build(); // 返回配置好的 SecurityFilterChain}/*这个方法配置了 Spring Security 的 AuthenticationManager它是进行用户认证的核心组件。配置了一个 DaoAuthenticationProvider它使用 CustomUserDetailsService 来加载用户信息使用 BCryptPasswordEncoder 进行密码验证。DaoAuthenticationProvider是 Spring Security 用于基于数据库的用户认证提供程序它会从 UserDetailsService 中加载用户信息并使用密码编码器对用户密码进行校验。UserDetailsServiceCustomUserDetailsService 实现了 UserDetailsService 接口用于加载用户信息例如用户名、密码、角色等。它通常会从数据库中查询用户信息。PasswordEncoderBCryptPasswordEncoder 用于对用户输入的密码进行加密然后与存储在数据库中的加密密码进行对比。BCrypt 是一种常见的密码加密算法。返回 AuthenticationManager该管理器负责执行认证流程返回经过认证的 Authentication 对象。*/Beanpublic AuthenticationManager authenticationManager(UserDetailsService userDetailsService,PasswordEncoder passwordEncoder) {DaoAuthenticationProvider authenticationProvider new DaoAuthenticationProvider();authenticationProvider.setUserDetailsService(userDetailsService); // 配置 UserDetailsService用于加载用户信息authenticationProvider.setPasswordEncoder(passwordEncoder); // 配置密码编码器用于密码校验return new ProviderManager(authenticationProvider); // 返回一个 ProviderManager管理多个认证提供者}/*该方法返回一个 BCryptPasswordEncoder 实例用于对用户的密码进行加密和验证。BCryptPasswordEncoder 是一种安全的密码加密算法Spring Security 默认推荐使用这种加密方式。*/Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}// Bean
// public SecurityContextRepository securityContextRepository() {
// return new HttpSessionSecurityContextRepository();
// }}
5.创建JWT 过滤器
创建一个过滤器来拦截每个请求提取 JWT并验证它。过滤器会检查请求头是否带有 JWT如果有它会验证 JWT 并通过 Spring Security 认证上下文设置用户身份。
JwtAuthenticationFilter 是一个用于处理 JWTJSON Web Token身份验证的过滤器类继承自 Spring Security 的 OncePerRequestFilter。它的作用是在每次请求时检查请求头中的 JWT token并进行校验如果 token 验证通过则根据 token 中的信息设置 Spring Security 的 Authentication确保用户能够通过 JWT 认证来访问受保护的资源。
package com.example.demonew.config;import com.example.demonew.service.loginService.LoginService;
import com.example.demonew.util.JwtTokenProviderUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
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.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;import java.io.IOException;Component
Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {Autowiredprivate JwtTokenProviderUtil jwtTokenProvider;Autowiredprivate UserDetailsService userDetailsService;/*这是过滤器的核心方法它会在每次 HTTP 请求时被调用。方法中的处理流程如下*/Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {// 通过 getTokenFromRequest(request) 方法尝试从 HTTP 请求的 Authorization 头中提取 JWT。如果 token 存在且以 Bearer 开头则提取出 token 部分。String token getTokenFromRequest(request);/* 校验 token使用 jwtTokenProvider.validateToken(token) 校验 JWT 的合法性。例如检查 token 是否过期、是否篡改等。如果 token 无效或过期认证过程会被跳过后续请求会被拒绝。*/if(StringUtils.hasText(token) jwtTokenProvider.validateToken(token)){try {// 从 token 获取 usernameString username jwtTokenProvider.getUsername(token);// 加载与 token 关联的用户UserDetails userDetails userDetailsService.loadUserByUsername(username);/** 创建一个 UsernamePasswordAuthenticationToken 实例传入 userDetails 和该用户的权限userDetails.getAuthorities()。* UsernamePasswordAuthenticationToken 是 Spring Security 用来封装用户身份信息的一个对象它包含了用户的身份信息和权限。* */UsernamePasswordAuthenticationToken authenticationToken new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));/** 使用 SecurityContextHolder.getContext().setAuthentication(authenticationToken) 将身份认证信息即 authenticationToken存储在 Spring Security 的上下文中。* 这样Spring Security 在处理后续请求时就能基于这个认证信息对请求进行授权控制。* */SecurityContextHolder.getContext().setAuthentication(authenticationToken);}catch (Exception e){// 记录日志并清除认证上下文log.warn(JWT Token validation failed: e.getMessage());SecurityContextHolder.clearContext();}}else {// 无 token 或 token 无效清除认证上下文logger.warn(JWT Token is missing or invalid);SecurityContextHolder.clearContext();}/*最后调用 filterChain.doFilter(request, response) 让请求继续传递给下一个过滤器或最终的目标如 Controller。这一步是确保请求能够正常进入应用的下一阶段*/filterChain.doFilter(request, response);}// 从请求头获取 JWT 格式为:Authorization: Bearer tokenprivate String getTokenFromRequest(HttpServletRequest request) {String bearerToken request.getHeader(Authorization);if (bearerToken ! null bearerToken.startsWith(Bearer )) {return bearerToken.substring(7); // 去掉 Bearer 前缀}return null;}
}
为什么使用 OncePerRequestFilter
OncePerRequestFilter 是 Spring 提供的一个过滤器基类确保每次请求只会调用一次过滤器的 doFilterInternal 方法而不是每个过滤器链都执行一次。这可以有效避免重复执行相同的逻辑保证 JWT 校验只执行一次。
创建登录接口
这样用户先通过用户名密码进行登录然后校验信息返回userDetail对象
package com.example.demonew.controller.login;import com.example.demonew.common.Result;
import com.example.demonew.config.SecurityConfig;
import com.example.demonew.service.loginService.LoginService;
import com.example.demonew.util.JwtTokenProviderUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;Controller
RequestMapping(/user)
public class LoginController {Autowiredprivate LoginService loginService;Autowiredprivate JwtTokenProviderUtil jwtTokenProviderUtil;Autowiredprivate AuthenticationManager authenticationManager;PostMapping(/login)public Result Login(RequestBody LoginRequest loginRequest){loginService.login(loginRequest.getUsername(),loginRequest.getPassword());// 登录授权Authentication authentication authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(),loginRequest.getPassword()));// 将登录用户信息交给SpringSecurity管理SecurityContextHolder.getContext().setAuthentication(authentication);// 利用用户授权信息生成JWT令牌String token jwtTokenProviderUtil.generateToken(authentication);return Result.success(token);};PostMapping(/register)public Result registerUser(RequestBody LoginRequest loginRequest) {BCryptPasswordEncoder bCryptPasswordEncodernew BCryptPasswordEncoder();String encodedPassword bCryptPasswordEncoder.encode(loginRequest.password);String passwordencodedPassword;loginService.register(loginRequest.getUsername(),password);return new Result();}// 登录请求体的封装类public static class LoginRequest {private String username;private String password;// getters and setterspublic String getUsername() {return username;}public void setUsername(String username) {this.username username;}public String getPassword() {return password;}public void setPassword(String password) {this.password password;}}}
准备数据 密码这里先手动生成一个 Testvoid testBCryptPasswordEncoder(){BCryptPasswordEncoder encoder new BCryptPasswordEncoder();String encode encoder.encode(123456);System.out.println(encode);}postman测试
登录
登录不需要带Authorization信息登录后jwt会生成token返回后续请求需要带上
其他接口
没权限 未带认证
带上认证成功访问
错误总结
执行filterChain.doFilter(request, response);后无法进入controller
解决
检查securityFilterChain方法中的requestMatchers方法url是否正确 requestMatchers(“/user/login”).permitAll() // 开放登录接口
Caused by: java.lang.IllegalArgumentException: A UserDetailsService must be set
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration: Unsatisfied dependency expressed through method setFilterChains parameter 0: Error creating bean with name securityFilterChain defined in class path resource [com/example/demonew/config/SecurityConfig.class]: Failed to instantiate [org.springframework.security.web.SecurityFilterChain]: Factory method securityFilterChain threw exception with message: Could not postProcess org.springframework.security.authentication.dao.DaoAuthenticationProvider2a4e939a of type class org.springframework.security.authentication.dao.DaoAuthenticationProviderat org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.resolveMethodArguments(AutowiredAnnotationBeanPostProcessor.java:896)at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:849)at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:146)at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:509)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1441)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:600)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523)at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:336)at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:289)at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:334)at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)at org.springframework.beans.factory.support.DefaultListableBeanFactory.instantiateSingleton(DefaultListableBeanFactory.java:1122)at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingleton(DefaultListableBeanFactory.java:1093)at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:1030)at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:987)at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:627)at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146)at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752)at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439)at org.springframework.boot.SpringApplication.run(SpringApplication.java:318)at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361)at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350)at com.example.demonew.DemoNewApplication.main(DemoNewApplication.java:16)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name securityFilterChain defined in class path resource [com/example/demonew/config/SecurityConfig.class]: Failed to instantiate [org.springframework.security.web.SecurityFilterChain]: Factory method securityFilterChain threw exception with message: Could not postProcess org.springframework.security.authentication.dao.DaoAuthenticationProvider2a4e939a of type class org.springframework.security.authentication.dao.DaoAuthenticationProviderat org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:657)at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:645)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1357)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1187)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:563)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523)at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:336)at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:289)at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:334)at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254)at org.springframework.beans.factory.support.DefaultListableBeanFactory.addCandidateEntry(DefaultListableBeanFactory.java:1883)at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1847)at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveMultipleBeanCollection(DefaultListableBeanFactory.java:1737)at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveMultipleBeans(DefaultListableBeanFactory.java:1705)at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1580)at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1519)at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.resolveMethodArguments(AutowiredAnnotationBeanPostProcessor.java:888)... 22 common frames omitted
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.web.SecurityFilterChain]: Factory method securityFilterChain threw exception with message: Could not postProcess org.springframework.security.authentication.dao.DaoAuthenticationProvider2a4e939a of type class org.springframework.security.authentication.dao.DaoAuthenticationProviderat org.springframework.beans.factory.support.SimpleInstantiationStrategy.lambda$instantiate$0(SimpleInstantiationStrategy.java:199)at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiateWithFactoryMethod(SimpleInstantiationStrategy.java:88)at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:168)at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653)... 39 common frames omitted
Caused by: java.lang.RuntimeException: Could not postProcess org.springframework.security.authentication.dao.DaoAuthenticationProvider2a4e939a of type class org.springframework.security.authentication.dao.DaoAuthenticationProviderat org.springframework.security.config.annotation.configuration.AutowireBeanFactoryObjectPostProcessor.postProcess(AutowireBeanFactoryObjectPostProcessor.java:71)at org.springframework.security.config.annotation.SecurityConfigurerAdapter$CompositeObjectPostProcessor.postProcess(SecurityConfigurerAdapter.java:130)at org.springframework.security.config.annotation.SecurityConfigurerAdapter.postProcess(SecurityConfigurerAdapter.java:82)at org.springframework.security.config.annotation.authentication.configurers.userdetails.AbstractDaoAuthenticationConfigurer.configure(AbstractDaoAuthenticationConfigurer.java:96)at org.springframework.security.config.annotation.authentication.configurers.userdetails.AbstractDaoAuthenticationConfigurer.configure(AbstractDaoAuthenticationConfigurer.java:36)at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.configure(AbstractConfiguredSecurityBuilder.java:398)at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.doBuild(AbstractConfiguredSecurityBuilder.java:352)at org.springframework.security.config.annotation.AbstractSecurityBuilder.build(AbstractSecurityBuilder.java:38)at org.springframework.security.config.annotation.web.builders.HttpSecurity.beforeConfigure(HttpSecurity.java:3301)at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.doBuild(AbstractConfiguredSecurityBuilder.java:351)at org.springframework.security.config.annotation.AbstractSecurityBuilder.build(AbstractSecurityBuilder.java:38)at com.example.demonew.config.SecurityConfig.securityFilterChain(SecurityConfig.java:49)at com.example.demonew.config.SecurityConfig$$SpringCGLIB$$0.CGLIB$securityFilterChain$2(generated)at com.example.demonew.config.SecurityConfig$$SpringCGLIB$$FastClass$$1.invoke(generated)at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:258)at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:348)at com.example.demonew.config.SecurityConfig$$SpringCGLIB$$0.securityFilterChain(generated)at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)at java.base/java.lang.reflect.Method.invoke(Method.java:580)at org.springframework.beans.factory.support.SimpleInstantiationStrategy.lambda$instantiate$0(SimpleInstantiationStrategy.java:171)... 42 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name org.springframework.security.authentication.dao.DaoAuthenticationProvider2a4e939a: A UserDetailsService must be setat org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1808)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:413)at org.springframework.security.config.annotation.configuration.AutowireBeanFactoryObjectPostProcessor.initializeBeanIfNeeded(AutowireBeanFactoryObjectPostProcessor.java:98)at org.springframework.security.config.annotation.configuration.AutowireBeanFactoryObjectPostProcessor.postProcess(AutowireBeanFactoryObjectPostProcessor.java:67)... 61 common frames omitted
Caused by: java.lang.IllegalArgumentException: A UserDetailsService must be setat org.springframework.util.Assert.notNull(Assert.java:181)at org.springframework.security.authentication.dao.DaoAuthenticationProvider.doAfterPropertiesSet(DaoAuthenticationProvider.java:99)at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.afterPropertiesSet(AbstractUserDetailsAuthenticationProvider.java:119)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1855)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1804)... 64 common frames omitted
Disconnected from the target VM, address: 127.0.0.1:59351, transport: socketProcess finished with exit code 1
解决
2025-01-14 11:59:16.898 WARN — [nio-8080-exec-2] o.s.s.c.bcrypt.BCryptPasswordEncoder : Encoded password does not look like BCrypt
解决
密码格式不正确确保数据库中的密码使用 BCrypt 加密 BCryptPasswordEncoder encoder new BCryptPasswordEncoder();String encode encoder.encode(123456);TemplateInputException
详细信息如下
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.thymeleaf.exceptions.TemplateInputException: Error resolving template [user/login], template might not exist or might not be accessible by any of the configured Template Resolvers] with root cause
org.thymeleaf.exceptions.TemplateInputException: Error resolving template [user/login], template might not exist or might not be accessible by any of the configured Template Resolvers在返回return时候报错
解决
在 pom.xml 中删除 Thymeleaf 依赖如果不使用 Thymeleaf
原因
Spring Boot 默认渲染视图
当你使用 Spring Boot 启动项目时如果没有明确声明返回类型Spring Boot 会默认尝试将响应返回为视图view页面而不是直接返回 JSON 或其他格式的数据。 在你的代码中login 方法返回的是 Result.success(token)这个 token 是你生成的 JWT应该作为 API 的响应数据返回而不是试图通过 Thymeleaf 渲染 user/login 模板。 返回的是数据而非视图
如果你的 login 是一个 RESTful 风格的 API应该返回 JSON 数据而不是试图渲染页面。Spring 默认会将没有指定视图的请求作为视图解析导致它去找 user/login.html。