java springboot3.x jwt+spring security6.x实现用户登录认证

springboot3.x jwt+spring security6.x实现用户登录认证

什么是JWT

JWT(JSON Web Token)是一种开放标准(RFC 7519),它用于在网络应用环境中传递声明。通常,JWT用于身份验证和信息交换。JWT的一个典型用法是在Web应用中实现基于Token的身份验证。

JWT 的特点

自包含(Self-contained):JWT 包含了用户的认证信息和其他相关的数据,因此无需在服务器端存储会话信息。它是通过加密或签名的方式来保证数据的完整性和防止篡改。

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通常在用户登录成功后生成,并作为后续请求的认证凭证。

一般流程

  1. 用户登录:用户通过用户名和密码发送请求给后端,后端验证用户身份。
  2. 生成JWT:身份验证通过后,后端生成JWT,并将其发送给前端。JWT通常在HTTP响应的Authorization头中返回。
  3. 存储JWT:前端将JWT存储在本地(例如localStorage或sessionStorage)或Cookie中。
  4. 后续请求:每次前端向后端发起请求时,JWT都会包含在请求头的Authorization字段中,后端通过JWT验证用户身份。
  5. 验证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 数据就很容易被篡改。为了解决这个问题,你需要自己实现一些安全机制(如加密或自定义签名),但这会增加实现的复杂度

无状态性与有效期管理

JWT:JWT 是自包含的,它不仅仅包含用户信息,还可以包含 过期时间(exp)等信息。这样,服务器可以非常容易地判断一个 JWT 是否过期,无需查询数据库或任何状态。

自定义 JSON:如果前端仅仅发送一个 JSON 数据包,服务器就无法知道该数据是否已经过期。为了实现这一点,你需要自己维护一个机制,比如在 JSON 中加入时间戳,或者在服务器端保存用户的会话过期时间。这会导致服务器变得有状态,因为服务器需要管理每个用户的会话生命周期。

服务器性能与分布式架构支持

JWT:由于 JWT 是自包含的,所有的信息都在 token 中,服务器不需要存储任何会话信息,这意味着它是 无状态的。无状态性在 分布式架构 中特别有优势,因为多个微服务可以独立地验证 JWT 而无需共享会话数据。

自定义 JSON:如果你选择自己封装 JSON,并由前端存储和发送,每次请求时,服务器就需要检查和验证这个 JSON 信息。尤其是在分布式环境中,你可能需要通过共享会话存储(如 Redis)来确保各个服务都能正确验证用户身份,增加了管理的复杂性。

可扩展性

JWT:由于 JWT 是标准化的,支持的库和工具非常多,你可以很容易地实现不同平台(如前端、后端、移动端等)之间的认证,而且不需要太多的额外工作。JWT 自带的灵活性和规范使得它可以适应多种需求。

自定义 JSON:如果你手动封装 JSON 并且实现自己的认证机制,虽然在小范围内可以工作,但这种方法缺乏标准化和统一性,扩展性差。在系统规模扩大或需求变化时,可能会遇到很多维护和兼容性问题。

实现

1.引入依赖

引入JWT依赖
        <!-- JWT library --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.11.2</version></dependency><!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.2</version><scope>runtime</scope></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>0.11.2</version><scope>runtime</scope></dependency>
引入spring Security依赖
        <!-- Spring Security for Authentication --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>

2.实现 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);List<GrantedAuthority> 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 List<GrantedAuthority> getAuthorities(String token) {Claims claims = getClaimsFromToken(token);List<String> roles = (List<String>)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 中加载用户信息,并使用密码编码器对用户密码进行校验。UserDetailsService:CustomUserDetailsService 实现了 UserDetailsService 接口,用于加载用户信息(例如,用户名、密码、角色等)。它通常会从数据库中查询用户信息。PasswordEncoder:BCryptPasswordEncoder 用于对用户输入的密码进行加密,然后与存储在数据库中的加密密码进行对比。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 是一个用于处理 JWT(JSON 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 <token>private 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 bCryptPasswordEncoder=new BCryptPasswordEncoder();String encodedPassword = bCryptPasswordEncoder.encode(loginRequest.password);String password=encodedPassword;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.DaoAuthenticationProvider@2a4e939a 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.DaoAuthenticationProvider@2a4e939a 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.DaoAuthenticationProvider@2a4e939a 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.DaoAuthenticationProvider@2a4e939a 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.DaoAuthenticationProvider@2a4e939a': 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: 'socket'Process 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。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/67108.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Socket和ServerSocket自定义协议的实现多端通信、消息发送、群聊发送

通信基础 Socket Socket套接字是实现网络通信的重要接口&#xff0c;Socket绑定的应用进程&#xff0c;目标Ip的端口号&#xff0c;以及数据传输对应的输入输出缓冲区。一个数据包到达一台计算机物理设备后&#xff0c;经过网络协议栈的解析&#xff0c;然后由操作系统调度到对…

【Uniapp-Vue3】Prop校验与prop默认值用法及循环遍历数组对象

一、prop校验 如果我们在想要限制prop的类型&#xff0c;就可以在接收prop的时候对接收类型进行限制&#xff1a; defineProps({ 属性名:{ type:类型 } }) 需要注意类型的首字母大写 但是设置了传入参数类型限制并不能严格限制&#xff0c;只会在后台进行提示&#xff1a; 二、…

Java Stream流操作List全攻略:Filter、Sort、GroupBy、Average、Sum实践

在Java 8及更高版本中&#xff0c;Stream API为集合处理带来了革命性的改变。本文将深入解析如何运用Stream对List进行高效的操作&#xff0c;包括筛选&#xff08;Filter&#xff09;、排序&#xff08;Sort&#xff09;、分组&#xff08;GroupBy&#xff09;、求平均值&…

ExplaineR:集成K-means聚类算法的SHAP可解释性分析 | 可视化混淆矩阵、决策曲线、模型评估与各类SHAP图

集成K-means聚类算法的SHAP可解释性分析 加载数据集并训练机器学习模型 SHAP 分析以提取特征对预测的影响 通过混淆矩阵可视化模型性能 决策曲线分析 模型评估&#xff08;多指标和ROC曲线的目视检查&#xff09; 带注释阈值的 ROC 曲线 加载 SHAP 结果以进行下游分析 与…

【Python通过UDP协议传输视频数据】(界面识别)

提示&#xff1a;界面识别项目 前言 随着网络通信技术的发展&#xff0c;视频数据的实时传输在各种场景中得到了广泛应用。UDP&#xff08;User Datagram Protocol&#xff09;作为一种无连接的协议&#xff0c;凭借其低延迟、高效率的特性&#xff0c;在实时性要求较高的视频…

浅谈云计算03 | 云计算的技术支撑(云使能技术)

云计算的技术支撑 一、定义与内涵1.1 定义与内涵 二、云计算使能技术架构2.1 宽带网络和 Internet 架构2.2 数据中心技术2.3 虚拟化技术2.4 Web 技术2.5 多租户技术2.6 服务技术 一、定义与内涵 1.1 定义与内涵 云计算技术包含一些基础的关键技术&#xff0c;这里称为使能技术…

QGraphicsView QGraphicsScene QGraphicsItem

QGraphicsView && QGraphicsScene &#xff1a; QGraphicsView 是 Qt 中用于显示和交互处理 2D 图形的控件&#xff0c;QGraphicsView 用于显示 QGraphicsScene 中的内容。 .h #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include &…

【大数据】机器学习-----线性模型

一、线性模型基本形式 线性模型旨在通过线性组合输入特征来预测输出。其一般形式为&#xff1a; 其中&#xff1a; x ( x 1 , x 2 , ⋯ , x d ) \mathbf{x}(x_1,x_2,\cdots,x_d) x(x1​,x2​,⋯,xd​) 是输入特征向量&#xff0c;包含 d d d 个特征。 w ( w 1 , w 2 , ⋯ ,…

基于千帆(ERNIE-Functions-8K)Function Calling的简单使用

1.Function Calling是什么&#xff1f; 1.1 概念 Function calling是一种将LLM(大语言模型Large language model)连接外部工具的能力&#xff0c;LLM经过微调后&#xff0c;可以检测出何时需要调用函数以及需要调用函数的方法名及参数&#xff0c;并返回给调用方以便调用外部…

Deep Attentional Guided Image Filtering

This work achieved the first place in the real depth map SR challenge held in ACM ICMR 2021. 目的&#xff1a;从一个target image(如低分辨的深度图)和guidance image(如高分辨的RGB图)&#xff0c;得到输出(如高分辨的深度图)。 主要思想是考虑了target和guidance之间的…

SpringBoot+Lombok项目实体属性名xXxx格式,前端接收不到

问题解析 今天发现后端传给前端的实体类中&#xff0c;有属性为xXxxx格式的&#xff0c;前端也使用相同名称接收&#xff0c;结果却不显示值&#xff01;研究了一会发现接口请求回来后&#xff0c;原xXxxx的属性名&#xff0c;会被转为全小写。具体原因为&#xff1a;使用Lombo…

高级运维:shell练习2

1、需求&#xff1a;判断192.168.1.0/24网络中&#xff0c;当前在线的ip有哪些&#xff0c;并编写脚本打印出来。 vim check.sh #!/bin/bash# 定义网络前缀 network_prefix"192.168.1"# 循环遍历1-254的IP for i in {1..254}; do# 构造完整的IP地址ip"$network_…

为深度学习创建PyTorch张量 - 最佳选项

为深度学习创建PyTorch张量 - 最佳选项 正如我们所看到的&#xff0c;PyTorch张量是torch.Tensor​ PyTorch类的实例。张量的抽象概念与PyTorch张量之间的区别在于&#xff0c;PyTorch张量为我们提供了一个可以在代码中操作的具体实现。 在上一篇文章中&#xff0c;我们看到了…

28.找出字符串中第一个匹配项的下标【力扣】KMP前缀表 ≈ find() 函数、暴力解法

class Solution { public: //得到前缀表void getNext(int *next,string needle){int j0;for(int i1;i<needle.size();i){while(j>0 && needle[j]!needle[i]) jnext[j-1];//**j>0**>j0是出口if(needle[i]needle[j]) j;next[i]j;//若写入if中&#xff0c;则该…

mysql 创建临时表报错

1. 问题描述 5.7.31 版本 mysql 数据库创建临时表报错 -- 报错语句 CREATE TEMPORARY TABLE temp_table_name SELECT * FROM table_name LIMIT 0;报错截图 [HY000][1005] Can’t create table ‘new_tbl’ (errno: 13) 2. 解决方案 步骤一&#xff1a; 查询 linux m…

使用 WPF 和 C# 将纹理应用于三角形

此示例展示了如何将纹理应用于三角形,以使场景比覆盖纯色的场景更逼真。以下是为三角形添加纹理的基本步骤。 创建一个MeshGeometry3D对象。像往常一样定义三角形的点和法线。通过向网格的TextureCoordinates集合添加值来设置三角形的纹理坐标。创建一个使用想要显示的纹理的 …

mac homebrew配置使用

本文介绍mac上homebrew工具的安装、配置过程。homebrew功能类似于centos的yum&#xff0c;用于软件包的管理&#xff0c;使用上有命令的差异。 本次配置过程使用mac&#xff0c;看官方文档&#xff0c;在linux上也可以用&#xff0c;但我没试过&#xff0c;有兴趣的同学可以试试…

OpenCV基础:矩阵的创建、检索与赋值

本文主要是介绍如何使用numpy进行矩阵的创建&#xff0c;以及从矩阵中读取数据&#xff0c;修改矩阵数据。 创建矩阵 import numpy as npa np.array([1,2,3]) b np.array([[1,2,3],[4,5,6]]) #print(a) #print(b)# 创建全0数组 eros矩阵 c np.zeros((8,8), np.uint8) #prin…

Unreal Engine 5 (UE5) Metahuman 的头部材质

在图中&#xff0c;你展示了 Unreal Engine 5 (UE5) Metahuman 的头部材质部分&#xff0c;列出了头部材质的多个元素。以下是对每个部分的解释&#xff1a; 材质解释 Element 0 - MI_HeadSynthesized_Baked 作用&#xff1a; 这是 Metahuman 的主要头部材质&#xff0c;控制整…

鸿蒙中自定义slider实现字体大小变化

ui&#xff1a; import { display, mediaquery, router } from kit.ArkUI import CommonConstants from ./CommonConstants; import PreferencesUtil from ./PreferencesUtil; import StyleConstants from ./StyleConstants;// 字体大小 Entry Component struct FontSize {Sta…