0


SpringBoot3 + SpringSecurity6 前后端分离

  1. 网上能找到的SpringBoot项目一般都是SpringBoot2 + SpringSecurity5,甚至是SSM的项目。这些老版本的教程很多已经不适用了,对于现在大部分的初学者来说,学了可能也是经典白雪。我还是不愿学那些老版本的东西,所以自己摸索了一下新版的SpringBoot项目应该怎么写。学习的过程也是非常折磨人的,看了很多的教程才知道个大概。
  • 导入依赖

SpringSecurity依赖

  1. <!--SpringSecurity起步依赖-->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-security</artifactId>
  5. </dependency>

JWT依赖

  1. <!--jwt令牌-->
  2. <dependency>
  3. <groupId>io.jsonwebtoken</groupId>
  4. <artifactId>jjwt</artifactId>
  5. <version>0.9.1</version>
  6. </dependency>
  • 添加配置类

对Security进行配置,Security中很多的默认配置都可以用自定义的替换。

  1. import lombok.RequiredArgsConstructor;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.http.HttpMethod;
  5. import org.springframework.security.authentication.AuthenticationManager;
  6. import org.springframework.security.authentication.AuthenticationProvider;
  7. import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
  8. import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
  9. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  10. import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
  11. import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
  12. import org.springframework.security.core.userdetails.UserDetailsService;
  13. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  14. import org.springframework.security.web.SecurityFilterChain;
  15. /**
  16. * @Description: SpringSecurity配置类
  17. * @Author: 翰戈.summer
  18. * @Date: 2023/11/17
  19. * @Param:
  20. * @Return:
  21. */
  22. @Configuration
  23. @EnableWebSecurity
  24. @RequiredArgsConstructor
  25. public class SecurityConfig {
  26. private final UserDetailsService userDetailsService;
  27. /**
  28. * 加载用户信息
  29. */
  30. @Bean
  31. public UserDetailsService userDetailsService() {
  32. return userDetailsService;
  33. }
  34. /**
  35. * 密码编码器
  36. */
  37. @Bean
  38. public BCryptPasswordEncoder passwordEncoder() {
  39. return new BCryptPasswordEncoder();
  40. }
  41. /**
  42. * 身份验证管理器
  43. */
  44. @Bean
  45. public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
  46. return configuration.getAuthenticationManager();
  47. }
  48. /**
  49. * 处理身份验证
  50. */
  51. @Bean
  52. public AuthenticationProvider authenticationProvider() {
  53. DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
  54. daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
  55. daoAuthenticationProvider.setUserDetailsService(userDetailsService);
  56. return daoAuthenticationProvider;
  57. }
  58. /**
  59. * @Description: 配置SecurityFilterChain过滤器链
  60. * @Author: 翰戈.summer
  61. * @Date: 2023/11/17
  62. * @Param: HttpSecurity
  63. * @Return: SecurityFilterChain
  64. */
  65. @Bean
  66. public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity httpSecurity) throws Exception {
  67. httpSecurity.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests
  68. .requestMatchers(HttpMethod.POST, "/api/user/login").permitAll() //登录放行
  69. .anyRequest().authenticated()
  70. );
  71. httpSecurity.authenticationProvider(authenticationProvider());
  72. //禁用登录页面
  73. httpSecurity.formLogin(AbstractHttpConfigurer::disable);
  74. //禁用登出页面
  75. httpSecurity.logout(AbstractHttpConfigurer::disable);
  76. //禁用session
  77. httpSecurity.sessionManagement(AbstractHttpConfigurer::disable);
  78. //禁用httpBasic
  79. httpSecurity.httpBasic(AbstractHttpConfigurer::disable);
  80. //禁用csrf保护
  81. httpSecurity.csrf(AbstractHttpConfigurer::disable);
  82. return httpSecurity.build();
  83. }
  84. }
  • 实现UserDetailsService

其中UserMapper、AuthorityMapper需要自己创建,不是重点。这两个Mapper的作用是获取用户信息(用户名、密码、用户权限),封装到User中返回给Security。

  1. import com.demo.mapper.AuthorityMapper;
  2. import com.demo.mapper.UserMapper;
  3. import com.demo.pojo.AuthorityEntity;
  4. import com.demo.pojo.UserEntity;
  5. import lombok.RequiredArgsConstructor;
  6. import org.springframework.security.core.authority.AuthorityUtils;
  7. import org.springframework.security.core.userdetails.User;
  8. import org.springframework.security.core.userdetails.UserDetails;
  9. import org.springframework.security.core.userdetails.UserDetailsService;
  10. import org.springframework.security.core.userdetails.UsernameNotFoundException;
  11. import org.springframework.stereotype.Service;
  12. import java.util.List;
  13. import java.util.StringJoiner;
  14. /**
  15. * @Description: 用户登录
  16. * @Author: 翰戈.summer
  17. * @Date: 2023/11/16
  18. * @Param:
  19. * @Return:
  20. */
  21. @Service
  22. @RequiredArgsConstructor
  23. public class UserLoginDetailsServiceImpl implements UserDetailsService {
  24. private final UserMapper userMapper;
  25. private final AuthorityMapper authorityMapper;
  26. @Override
  27. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  28. UserEntity userEntity = userMapper.selectUserByUsername(username);
  29. List<AuthorityEntity> authorities = authorityMapper.selectAuthorityByUsername(username);
  30. StringJoiner stringJoiner = new StringJoiner(",", "", "");
  31. authorities.forEach(authority -> stringJoiner.add(authority.getAuthorityName()));
  32. return new User(userEntity.getUsername(), userEntity.getPassword(),
  33. AuthorityUtils.commaSeparatedStringToAuthorityList(stringJoiner.toString())
  34. );
  35. }
  36. }
  • 实现UserDetails

登录操作会用到UserDetails,用于获取用户名和权限。

  1. import lombok.AllArgsConstructor;
  2. import lombok.NoArgsConstructor;
  3. import org.springframework.security.core.GrantedAuthority;
  4. import org.springframework.security.core.userdetails.UserDetails;
  5. import java.util.Collection;
  6. /**
  7. * @Description: SpringSecurity用户实体类
  8. * @Author: 翰戈.summer
  9. * @Date: 2023/11/18
  10. * @Param:
  11. * @Return:
  12. */
  13. @NoArgsConstructor
  14. @AllArgsConstructor
  15. public class UserDetailsEntity implements UserDetails {
  16. private String username;
  17. private String password;
  18. private Collection<? extends GrantedAuthority> authorities;
  19. @Override
  20. public Collection<? extends GrantedAuthority> getAuthorities() {
  21. return authorities;
  22. }
  23. @Override
  24. public String getPassword() {
  25. return password;
  26. }
  27. @Override
  28. public String getUsername() {
  29. return username;
  30. }
  31. @Override
  32. public boolean isAccountNonExpired() {
  33. return true;
  34. }
  35. @Override
  36. public boolean isAccountNonLocked() {
  37. return true;
  38. }
  39. @Override
  40. public boolean isCredentialsNonExpired() {
  41. return true;
  42. }
  43. @Override
  44. public boolean isEnabled() {
  45. return true;
  46. }
  47. @Override
  48. public String toString() {
  49. return "UserDetailsEntity{" +
  50. "username='" + username + '\'' +
  51. ", password='" + password + '\'' +
  52. ", authorities=" + authorities +
  53. '}';
  54. }
  55. }
  • JWT工具类

生成 jwt令牌 或解析,其中的JwtProperties(jwt令牌配置属性类)可以自己创建,不是重点。

  1. import io.jsonwebtoken.Claims;
  2. import io.jsonwebtoken.Jwts;
  3. import io.jsonwebtoken.SignatureAlgorithm;
  4. import lombok.RequiredArgsConstructor;
  5. import org.springframework.stereotype.Component;
  6. import java.util.Date;
  7. import java.util.Map;
  8. /**
  9. * @Description: 生成和解析jwt令牌
  10. * @Author: 翰戈.summer
  11. * @Date: 2023/11/16
  12. * @Param:
  13. * @Return:
  14. */
  15. @Component
  16. @RequiredArgsConstructor
  17. public class JwtUtils {
  18. private final JwtProperties jwtProperties;
  19. /**
  20. * @Description: 生成令牌
  21. * @Author: 翰戈.summer
  22. * @Date: 2023/11/16
  23. * @Param: Map
  24. * @Return: String jwt
  25. */
  26. public String getJwt(Map<String, Object> claims) {
  27. String signingKey = jwtProperties.getSigningKey();
  28. Long expire = jwtProperties.getExpire();
  29. return Jwts.builder()
  30. .setClaims(claims) //设置载荷内容
  31. .signWith(SignatureAlgorithm.HS256, signingKey) //设置签名算法
  32. .setExpiration(new Date(System.currentTimeMillis() + expire)) //设置有效时间
  33. .compact();
  34. }
  35. /**
  36. * @Description: 解析令牌
  37. * @Author: 翰戈.summer
  38. * @Date: 2023/11/16
  39. * @Param: String jwt
  40. * @Return: Claims claims
  41. */
  42. public Claims parseJwt(String jwt) {
  43. String signingKey = jwtProperties.getSigningKey();
  44. return Jwts.parser()
  45. .setSigningKey(signingKey) //指定签名密钥
  46. .parseClaimsJws(jwt) //开始解析令牌
  47. .getBody();
  48. }
  49. }
  • 登录接口

用户登录成功并返回 jwt令牌,Result为统一响应的结果,UserLoginDTO用于封装用户登录信息,其中的UserDetails必须实现后才能获取到用户信息。

  1. import lombok.RequiredArgsConstructor;
  2. import org.springframework.security.authentication.AuthenticationManager;
  3. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  4. import org.springframework.security.core.Authentication;
  5. import org.springframework.security.core.GrantedAuthority;
  6. import org.springframework.security.core.context.SecurityContextHolder;
  7. import org.springframework.security.core.userdetails.UserDetails;
  8. import org.springframework.web.bind.annotation.PostMapping;
  9. import org.springframework.web.bind.annotation.RequestBody;
  10. import org.springframework.web.bind.annotation.RequestMapping;
  11. import org.springframework.web.bind.annotation.RestController;
  12. import java.util.Collection;
  13. import java.util.HashMap;
  14. import java.util.Map;
  15. /**
  16. * @Description: 用户登录操作相关接口
  17. * @Author: 翰戈.summer
  18. * @Date: 2023/11/20
  19. * @Param:
  20. * @Return:
  21. */
  22. @RestController
  23. @RequestMapping("/api/user/login")
  24. @RequiredArgsConstructor
  25. public class UserLoginController {
  26. private final AuthenticationManager authenticationManager;
  27. private final JwtUtils jwtUtils;
  28. @PostMapping
  29. public Result<String> doLogin(@RequestBody UserLoginDTO userLoginDTO) {
  30. try {
  31. UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(userLoginDTO.getUsername(), userLoginDTO.getPassword());
  32. Authentication authentication = authenticationManager.authenticate(auth);
  33. SecurityContextHolder.getContext().setAuthentication(authentication);
  34. UserDetails userDetails = (UserDetails) authentication.getPrincipal();
  35. //获取用户权限信息
  36. String authorityString = "";
  37. Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
  38. for (GrantedAuthority authority : authorities) {
  39. authorityString = authority.getAuthority();
  40. }
  41. //用户身份验证成功,生成并返回jwt令牌
  42. Map<String, Object> claims = new HashMap<>();
  43. claims.put("username", userDetails.getUsername());
  44. claims.put("authorityString", authorityString);
  45. String jwtToken = jwtUtils.getJwt(claims);
  46. return Result.success(jwtToken);
  47. } catch (Exception ex) {
  48. //用户身份验证失败,返回登陆失败提示
  49. return Result.error("用户名或密码错误!");
  50. }
  51. }
  52. }
  • 自定义token过滤器

过滤器中抛出的异常是不会被全局异常处理器捕获到的,直接返回错误结果。这里用到了SpringContextUtils通过上下文来获取Bean组件,下面会提供。

过滤器属于Servlet(作用范围更大),拦截器属于SpringMVC(作用范围较小),全局异常处理器只能捕获到拦截器中的异常。在过滤器中无法初始化Bean组件,可以通过上下文来获取。

  1. import com.fasterxml.jackson.databind.ObjectMapper;
  2. import io.jsonwebtoken.Claims;
  3. import jakarta.servlet.FilterChain;
  4. import jakarta.servlet.http.HttpServletRequest;
  5. import jakarta.servlet.http.HttpServletResponse;
  6. import org.springframework.security.authentication.AuthenticationManager;
  7. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  8. import org.springframework.security.core.Authentication;
  9. import org.springframework.security.core.authority.SimpleGrantedAuthority;
  10. import org.springframework.security.core.context.SecurityContextHolder;
  11. import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
  12. import org.springframework.util.StringUtils;
  13. import java.io.IOException;
  14. import java.util.Collections;
  15. /**
  16. * @Description: 自定义token验证过滤器,验证成功后将用户信息放入SecurityContext上下文
  17. * @Author: 翰戈.summer
  18. * @Date: 2023/11/18
  19. * @Param:
  20. * @Return:
  21. */
  22. public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
  23. public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
  24. super(authenticationManager);
  25. }
  26. @Override
  27. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException {
  28. try {
  29. //获取请求头中的token
  30. String jwtToken = request.getHeader("token");
  31. if (!StringUtils.hasLength(jwtToken)) {
  32. //token不存在,交给其他过滤器处理
  33. filterChain.doFilter(request, response);
  34. return; //结束方法
  35. }
  36. //过滤器中无法初始化Bean组件,使用上下文获取
  37. JwtUtils jwtUtils = SpringContextUtils.getBean("jwtUtils");
  38. if (jwtUtils == null) {
  39. throw new RuntimeException();
  40. }
  41. //解析jwt令牌
  42. Claims claims;
  43. try {
  44. claims = jwtUtils.parseJwt(jwtToken);
  45. } catch (Exception ex) {
  46. throw new RuntimeException();
  47. }
  48. //获取用户信息
  49. String username = (String) claims.get("username"); //用户名
  50. String authorityString = (String) claims.get("authorityString"); //权限信息
  51. Authentication authentication = new UsernamePasswordAuthenticationToken(
  52. username, null,
  53. Collections.singleton(new SimpleGrantedAuthority(authorityString))
  54. );
  55. //将用户信息放入SecurityContext上下文
  56. SecurityContextHolder.getContext().setAuthentication(authentication);
  57. filterChain.doFilter(request, response);
  58. } catch (Exception ex) {
  59. //过滤器中抛出的异常无法被全局异常处理器捕获,直接返回错误结果
  60. response.setCharacterEncoding("utf-8");
  61. response.setContentType("application/json; charset=utf-8");
  62. String value = new ObjectMapper().writeValueAsString(Result.error("用户未登录!"));
  63. response.getWriter().write(value);
  64. }
  65. }
  66. }
  • SpringContextUtils工具类

  1. import jakarta.annotation.Nonnull;
  2. import org.springframework.beans.BeansException;
  3. import org.springframework.context.ApplicationContext;
  4. import org.springframework.context.ApplicationContextAware;
  5. import org.springframework.stereotype.Component;
  6. /**
  7. * @Description: 用于创建上下文,实现ApplicationContextAware接口
  8. * @Author: 翰戈.summer
  9. * @Date: 2023/11/17
  10. * @Param:
  11. * @Return:
  12. */
  13. @Component
  14. public class SpringContextUtils implements ApplicationContextAware {
  15. private static ApplicationContext applicationContext;
  16. public static ApplicationContext getApplicationContext() {
  17. return applicationContext;
  18. }
  19. @Override
  20. public void setApplicationContext(@Nonnull ApplicationContext applicationContext) throws BeansException {
  21. SpringContextUtils.applicationContext = applicationContext;
  22. }
  23. @SuppressWarnings("unchecked")
  24. public static <T> T getBean(String name) throws BeansException {
  25. if (applicationContext == null) {
  26. return null;
  27. }
  28. return (T) applicationContext.getBean(name);
  29. }
  30. }
  • 添加自定义token验证过滤器

将自定义token验证过滤器,添加到UsernamePasswordAuthenticationFilter前面。

UsernamePasswordAuthenticationFilter实现了基于用户名和密码的认证逻辑,我们利用token进行身份验证,所以用不到这个过滤器。

  1. /**
  2. * @Description: 配置SecurityFilterChain过滤器链
  3. * @Author: 翰戈.summer
  4. * @Date: 2023/11/17
  5. * @Param: HttpSecurity
  6. * @Return: SecurityFilterChain
  7. */
  8. @Bean
  9. public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity httpSecurity) throws Exception {
  10. httpSecurity.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests
  11. .requestMatchers(HttpMethod.POST, "/api/user/login").permitAll() //登录放行
  12. .anyRequest().authenticated()
  13. );
  14. httpSecurity.authenticationProvider(authenticationProvider());
  15. //禁用登录页面
  16. httpSecurity.formLogin(AbstractHttpConfigurer::disable);
  17. //禁用登出页面
  18. httpSecurity.logout(AbstractHttpConfigurer::disable);
  19. //禁用session
  20. httpSecurity.sessionManagement(AbstractHttpConfigurer::disable);
  21. //禁用httpBasic
  22. httpSecurity.httpBasic(AbstractHttpConfigurer::disable);
  23. //禁用csrf保护
  24. httpSecurity.csrf(AbstractHttpConfigurer::disable);
  25. //通过上下文获取AuthenticationManager
  26. AuthenticationManager authenticationManager = SpringContextUtils.getBean("authenticationManager");
  27. //添加自定义token验证过滤器
  28. httpSecurity.addFilterBefore(new JwtAuthenticationFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class);
  29. return httpSecurity.build();
  30. }
  • 自定义用户未登录的处理

用户请求未携带token的处理,替换AuthenticationEntryPoint

  1. import com.fasterxml.jackson.databind.ObjectMapper;
  2. import jakarta.servlet.http.HttpServletRequest;
  3. import jakarta.servlet.http.HttpServletResponse;
  4. import org.springframework.security.core.AuthenticationException;
  5. import org.springframework.security.web.AuthenticationEntryPoint;
  6. import org.springframework.stereotype.Component;
  7. import java.io.IOException;
  8. /**
  9. * @Description: 自定义用户未登录的处理(未携带token)
  10. * @Author: 翰戈.summer
  11. * @Date: 2023/11/19
  12. * @Param:
  13. * @Return:
  14. */
  15. @Component
  16. public class AuthEntryPointHandler implements AuthenticationEntryPoint {
  17. @Override
  18. public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
  19. response.setCharacterEncoding("utf-8");
  20. response.setContentType("application/json; charset=utf-8");
  21. String value = new ObjectMapper().writeValueAsString(Result.error("未携带token!"));
  22. response.getWriter().write(value);
  23. }
  24. }
  • 自定义用户权限不足的处理

用户权限不足的处理,替换AccessDeniedHandler

  1. import com.fasterxml.jackson.databind.ObjectMapper;
  2. import jakarta.servlet.http.HttpServletRequest;
  3. import jakarta.servlet.http.HttpServletResponse;
  4. import org.springframework.security.access.AccessDeniedException;
  5. import org.springframework.security.web.access.AccessDeniedHandler;
  6. import org.springframework.stereotype.Component;
  7. import java.io.IOException;
  8. /**
  9. * @Description: 自定义用户权限不足的处理
  10. * @Author: 翰戈.summer
  11. * @Date: 2023/11/19
  12. * @Param:
  13. * @Return:
  14. */
  15. @Component
  16. public class AuthAccessDeniedHandler implements AccessDeniedHandler {
  17. @Override
  18. public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
  19. response.setCharacterEncoding("utf-8");
  20. response.setContentType("application/json; charset=utf-8");
  21. String value = new ObjectMapper().writeValueAsString(Result.error("权限不足!"));
  22. response.getWriter().write(value);
  23. }
  24. }
  • 添加自定义处理器

修改 SecurityConfig 配置类,注入 AuthAccessDeniedHandler 和 AuthEntryPointHandler

  1. /**
  2. * @Description: 配置SecurityFilterChain过滤器链
  3. * @Author: 翰戈.summer
  4. * @Date: 2023/11/17
  5. * @Param: HttpSecurity
  6. * @Return: SecurityFilterChain
  7. */
  8. @Bean
  9. public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity httpSecurity) throws Exception {
  10. httpSecurity.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests
  11. .requestMatchers(HttpMethod.POST, "/api/user/login").permitAll() //登录放行
  12. .anyRequest().authenticated()
  13. );
  14. httpSecurity.authenticationProvider(authenticationProvider());
  15. //禁用登录页面
  16. httpSecurity.formLogin(AbstractHttpConfigurer::disable);
  17. //禁用登出页面
  18. httpSecurity.logout(AbstractHttpConfigurer::disable);
  19. //禁用session
  20. httpSecurity.sessionManagement(AbstractHttpConfigurer::disable);
  21. //禁用httpBasic
  22. httpSecurity.httpBasic(AbstractHttpConfigurer::disable);
  23. //禁用csrf保护
  24. httpSecurity.csrf(AbstractHttpConfigurer::disable);
  25. //通过上下文获取AuthenticationManager
  26. AuthenticationManager authenticationManager = SpringContextUtils.getBean("authenticationManager");
  27. //添加自定义token验证过滤器
  28. httpSecurity.addFilterBefore(new JwtAuthenticationFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class);
  29. //自定义处理器
  30. httpSecurity.exceptionHandling(exceptionHandling -> exceptionHandling
  31. .accessDeniedHandler(authAccessDeniedHandler) //处理用户权限不足
  32. .authenticationEntryPoint(authEntryPointHandler) //处理用户未登录(未携带token)
  33. );
  34. return httpSecurity.build();
  35. }
  • 静态资源放行

SpringBoot3 中使用 Swagger3 接口文档,在整合了 SpringSecurity 后会出现无法访问的情况,需要给静态资源放行。

在 SecurityConfig 中添加

  1. /**
  2. * 静态资源放行
  3. */
  4. @Bean
  5. public WebSecurityCustomizer webSecurityCustomizer() {
  6. return (web) -> web.ignoring().requestMatchers(
  7. "/doc.html",
  8. "/doc.html/**",
  9. "/v3/api-docs",
  10. "/v3/api-docs/**",
  11. "/webjars/**",
  12. "/authenticate",
  13. "/swagger-ui.html/**",
  14. "/swagger-resources",
  15. "/swagger-resources/**"
  16. );
  17. }
  • 总结

SpringSecurity6 的用法和以前版本的有较大差别,比如WebSecurityConfigurerAdapter的废除,看到配置类继承了这个的都是过时的教程。因为不再继承,所以不能通过重写方法的方式去配置。另外很多配置的方式都变成使用Lambda表达式,或者是方法引用。

创作不易,如果对你有帮助的话就点个赞鼓励一下吧 (人 •͈ᴗ•͈) (୨୧•͈ᴗ•͈)◞ᵗʱᵃᵑᵏઽ*♡


本文转载自: https://blog.csdn.net/qq_74312711/article/details/134558633
版权归原作者 翰戈.summer 所有, 如有侵权,请联系我们删除。

“SpringBoot3 + SpringSecurity6 前后端分离”的评论:

还没有评论