目录

Sping Security + OAuth2 在Spring 3.x中的使用

Sping Security + OAuth2 在Spring 3.x中的使用

Authentication(身份认证)依赖引入

  • JDK:17
  • SpringBoot:3.x (Spring Security 6.x.0) | Spring Cloud 2022
  • 其他依赖:Spring Web,Spring Security

spring cloud自从2020.0.0(含)以上版本就移除了spring-cloud-security-dependencies依赖,所以从2020.0.0版本开始,无法引入spring-cloud-starter-oauth2。

而oauth2的授权服务分离为一个独立的项目。我们要在项目中添加 spring-security-oauth2-authorization-server依赖

1
2
3
4
5
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-authorization-server</artifactId>
    <version>1.3.1</version>
</dependency>

我用的是springboot 3.0.2 , spring cloud 2022

Spring Security 底层原理

Spring Security 默认行为

  1. 保护应用程序URL,要求对应用程序的任何交互进行身份验证
    • 所有应用程序URL都必须经过身份验证才能访问。
  2. 程序启动时生成一个默认用户“user”
    • 在程序启动时生成一个默认用户“user”。
    • 生成一个默认的随机密码,并将此密码记录在控制台上。
  3. 生成默认的登录表单和注销页面
    • 提供基于表单的登录和注销流程。
  4. 对于Web请求
    • 未登录用户的Web请求重定向到登录页面。
  5. 对于服务请求
    • 未经授权的服务请求返回401状态码。
  6. 处理跨站请求伪造(CSRF)攻击
    • 实施CSRF保护措施,确保请求的合法性。
  7. 处理会话劫持攻击
    • 实施会话保护措施,防止会话劫持。
  8. 写入Strict-Transport-Security以确保HTTPS
    • 设置Strict-Transport-Security头部,强制使用HTTPS。
  9. 写入X-Content-Type-Options以处理探测攻击
    • 设置X-Content-Type-Options头部,防止MIME类型探测。
  10. 写入Cache Control头来保护经过身份验证的资源
    • 设置Cache-Control头部,防止缓存敏感信息。
  11. 写入X-Frame-Options以处理点击劫持攻击
    • 设置X-Frame-Options头部,防止点击劫持攻击。

Spring Security过滤器流程

https://pic.imgdb.cn/item/668f9936d9c307b7e9007806.png

  1. FilterChain

    Servlet容器创建了一个 FilterChain,其中包含Filter实例和Servlet.根据请求URI的路径处理 HttpServletRequest

    多个Filter在FilterChain的作用:

    1. 阻止HttpServletRequest 被下游 FilterServlet调用,Filter将写入 HttpServletResponse

    2. 修改下游 Filter 实例和 Servlet 实例使用的 HttpServletRequestHttpServletResponse

由于 Filter 只影响下游 Filter 实例和 Servlet ,因此调用每个 Filter 的顺序非常重要。

在Spring MVC应用程序中, ServletDispatcherServlet 的一个实例。 一个 Servlet 可以处理单个 HttpServletRequestHttpServletResponse

  1. DelegatingFilterProxy

    Spring提供了一个名为 DelegatingFilterProxyFilter 实现,它允许在Servlet容器的生命周期和Spring的 ApplicationContext 之间架桥。 Servlet容器允许使用自己的标准注册 Filter 实例,但它不知道spring定义的bean。 您可以通过标准的Servlet容器机制注册 DelegatingFilterProxy ,但将所有工作委托给实现 Filter 的Spring Bean。

    DelegatingFilterProxy是一个代理类,DelegatingFilterProxy通过标准的Servlet容器机制注册到FilterChain。因为Servlet并不知道Spring的Bean,DelegatingFilterProxy负责将Spring容器的Filter定义的bean注册到FilterChain的DelegatingFilterProxy

  2. FilterChainProxy

    Spring Security的Servlet支持包含在 FilterChainProxy 中。 FilterChainProxy 是Spring Security提供的一个特殊的 Filter ,它允许通过 SecurityFilterChain 委托给多个 Filter 实例。 因为 FilterChainProxy 是一个Bean,所以它通常被封装在DelegatingFilterProxy中。

  3. SecurityFilterChain

    SecurityFilterChain 中的安全过滤器通常是bean,但是它们注册在 FilterChainProxy 而不是DelegatingFilterProxy。 FilterChainProxy 为直接向Servlet容器或DelegatingFilterProxy注册提供了许多优点。 它为Spring Security的所有Servlet支持提供了一个起点。

    FilterChainProxy 决定应该使用哪个 SecurityFilterChain 。 只调用第一个匹配的 SecurityFilterChain 。 如果请求 /api/messages/ 的URL,它首先匹配 /api/**SecurityFilterChain0 模式,因此只调用 SecurityFilterChain0 ,即使它也匹配 SecurityFilterChainn 。 如果请求URL /messages/ ,它与 /api/**SecurityFilterChain0 模式不匹配,因此 FilterChainProxy 继续尝试每个 SecurityFilterChain

  4. Security Filters

    使用SecurityFilterChain API将安全过滤器插入到FilterChainProxy中。 这些过滤器可用于许多不同的目的,如身份验证、授权、漏洞利用保护等。 过滤器按照特定的顺序执行,以确保在正确的时间调用它们,例如,执行身份验证的 Filter 应该在执行授权的 Filter 之前调用。 通常不需要知道Spring Security的 Filter s的顺序。 然而,有时知道顺序是有益的,如果你想知道它们,你可以检查 FilterOrderRegistration 代码。

DefaultSecurityFilterChain默认过滤器链

DefaultSecurityFilterChain属于SecurityFilterChain。是Spring默认实现的安全过滤器链,里面有16个默认实现的过滤器实例Bean。

过滤器列表在应用程序启动时以INFO级别打印,因此可以在控制台输出中看到如下内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
2023-06-14T08:55:22.321-03:00  INFO 76975 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Will secure any request with [
org.springframework.security.web.session.DisableEncodeUrlFilter@404db674,
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@50f097b5,
org.springframework.security.web.context.SecurityContextHolderFilter@6fc6deb7,
org.springframework.security.web.header.HeaderWriterFilter@6f76c2cc,
org.springframework.security.web.csrf.CsrfFilter@c29fe36,
org.springframework.web.filter.CorsFilter@1cc8416a,
org.springframework.security.web.authentication.logout.LogoutFilter@ef60710,
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@7c2dfa2,
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@4397a639,
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@7add838c,
org.springframework.security.web.authentication.www.BasicAuthenticationFilter@5cc9d3d0,
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@7da39774,
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@32b0876c,
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@3662bdff,
org.springframework.security.web.access.ExceptionTranslationFilter@77681ce4,
org.springframework.security.web.access.intercept.AuthorizationFilter@169268a7]
  1. DisableEncodeUrlFilter

    • 禁用URL编码,以确保安全性。
  2. WebAsyncManagerIntegrationFilter

    • 支持Spring异步请求处理,与Spring Security集成。
  3. SecurityContextHolderFilter

    • 管理SecurityContext,在请求生命周期中持有和清理SecurityContext
  4. HeaderWriterFilter

    • 写入安全头部信息(如HSTS、X-Content-Type-Options、X-Frame-Options等)。
  5. CsrfFilter

    • 防止跨站请求伪造(CSRF)攻击,通过检查CSRF令牌的合法性来保护应用程序。
  6. CorsFilter

    • 处理跨域资源共享(CORS)请求,允许或拒绝来自不同域的请求,根据配置的CORS策略来决定请求是否被允许。

    CorsFilter必须要打开http.cors()允许跨域才会添加进DefaultSecurityFilterChain

    1
    2
    3
    4
    5
    
        @Bean
        public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
            http.cors();
            return http.build();
        }
    
  7. LogoutFilter

    • 处理注销请求,清理用户会话并重定向到注销页面。
  8. UsernamePasswordAuthenticationFilter

    • 处理基于用户名和密码的表单登录请求。
  9. DefaultLoginPageGeneratingFilter

    • 生成默认的登录页面,用于未提供自定义登录页面时。
  10. DefaultLogoutPageGeneratingFilter

    • 生成默认的注销页面,用于未提供自定义注销页面时。
  11. BasicAuthenticationFilter

    • 处理HTTP Basic认证。
  12. RequestCacheAwareFilter

    • 管理缓存的请求,以便在成功认证后重定向到用户最初请求的URL。
  13. SecurityContextHolderAwareRequestFilter

    • 在Spring MVC中提供基于安全上下文的便利方法,允许安全相关信息在控制器中使用。
  14. AnonymousAuthenticationFilter

    • 为未认证用户提供匿名身份,以便安全过滤器链能够继续处理。
  15. ExceptionTranslationFilter

    • 处理和翻译安全异常,将其转换为合适的HTTP响应(如403 Forbidden)。
  16. AuthorizationFilter

    • 执行授权决策,确保用户具有访问请求资源的权限。

Spring Security 自定义配置

  • SecurityConfig自定义配置类

  • 1
    2
    3
    4
    5
    6
    
    //@EnableWebSecurity  //开启SpringSecurity的自定义配置(spirngboot项目可以省略)
    @Configuration
    public class SecurityConfig {
    
    
    }
    

@EnableWebSecurity在SpringBoot项目中可以忽略

spirng-boot-autoconfigure包里面自动配置了。只要添加了Spring Security依赖,EnableWebSecurity.clss自动在SpringBoot上下文中配置。@EnableWebSecurity注解也会自动加入到配置类中。

SpringSecurity的默认配置

SpringSecurity默认实现HttpSecurity安全配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
	http
		.authorizeHttpRequests(authorize -> authorize
			.anyRequest().authenticated()
		)
		.formLogin(withDefaults())
		.httpBasic(withDefaults());
	return http.build();
}
  1. .authorizeHttpRequests()负责组装用户认证过程的具体功能
  2. .anyRequest().authenticated()所有请求开启授权保护,未认证跳转登录页,已认证的请求会自动授权。
  3. .formLogin(withDefaults())默认表单登录登出页面
    • 如果将.formLogin(withDefaults())注释掉可以查看浏览器的默认登录框.httpBasic()
  4. .httpBasic(withDefaults())使用默认基本授权方式
    • 由浏览器提供默认的登录输入框,不同浏览器不同
    • 如果注释掉,Spring启动时不会加载BasicAuthenticationFilter过滤器

UserDetailsService类与默认实现

在Spring Security中,UserDetailsService接口用于从特定的数据源加载用户信息。实现这个接口的类可以有很多种,具体实现类取决于你如何存储和管理用户数据。下面是一些常见的UserDetailsService实现类:

  1. InMemoryUserDetailsManager:

    • 用于在内存中存储用户信息,适用于小型应用程序或测试场景。
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); 	
        manager.createUser(
            User.withDefaultPasswordEncoder()	//使用默认的无编码密码 NoOpPasswordEncoder
            	.withUsername("user")
                .password("password")
                .roles("USER")	//用户角色
                .build());
        return manager;
    }
    
    1. 创建基于内存的用户信息管理器
    2. 使用manager管理UserDetails
  2. JdbcUserDetailsManager:

    • 使用JDBC从关系型数据库中加载用户信息。
    1
    2
    3
    4
    5
    6
    7
    
    @Bean
    public UserDetailsService userDetailsService(DataSource dataSource) {
        JdbcUserDetailsManager userDetailsManager = new 		JdbcUserDetailsManager(dataSource);
        userDetailsManager.setUsersByUsernameQuery("select username, password, enabled from users where username = ?");
        userDetailsManager.setAuthoritiesByUsernameQuery("select username, authority from authorities where username = ?");
        return userDetailsManager;
    }
    
  3. LdapUserDetailsManager:

    • 使用LDAP从LDAP服务器加载用户信息。
    1
    2
    3
    4
    5
    6
    7
    8
    
    @Bean
    public LdapUserDetailsManager ldapUserDetailsManager(BaseLdapPathContextSource contextSource) {
        LdapUserDetailsManager manager = new LdapUserDetailsManager();
        manager.setContextSource(contextSource);
        manager.setUserDnPatterns("uid={0},ou=people");
        manager.setGroupSearchBase("ou=groups");
        return manager;
    }
    
  4. CustomUserDetailsService:

    • 自定义实现UserDetailsService,可以从任意数据源加载用户信息,例如NoSQL数据库、Web服务等。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
     @Service
     public class CustomUserDetailsService implements UserDetailsService {
         @Autowired
         private UserRepository userRepository;

         @Override
         public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
             User user = userRepository.findByUsername(username);
             if (user == null) {
                 throw new UsernameNotFoundException("User not found");
             }
             return new org.springframework.security.core.userdetails.User(
                     user.getUsername(),
                     user.getPassword(),
                     AuthorityUtils.createAuthorityList(user.getRoles())
             );
         }
     }

     @Configuration
     @EnableWebSecurity
     public class SecurityConfig {

         @Bean
         public UserDetailsService userDetailsService(CustomUserDetailsService customUserDetailsService) {
             return customUserDetailsService;
         }
         
         @Bean
         public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
             http
                 .authorizeHttpRequests(authorize -> authorize
                     .anyRequest().authenticated()
                 )
                 .formLogin(withDefaults());
             return http.build();
         }
     }

基于内存的用户验证的流程

认证流程

  • 程序启动时

    1. 创建 InMemoryUserDetailsManager 对象。
    2. 创建 User 对象,封装用户名和密码。
    3. 使用 InMemoryUserDetailsManagerUser 存入内存。
  • 校验用户时

  • Spring Security 自动使用 InMemoryUserDetailsManagerloadUserByUsername 方法从内存中获取 User 对象。

  • UsernamePasswordAuthenticationFilter 过滤器中的 attemptAuthentication 方法中,将用户输入的用户名和密码与从内存中获取到的用户信息进行比较,进行用户认证。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public Authentication attemptAuthentication
    (HttpServletRequest request, 
     HttpServletResponse response) throws AuthenticationException {
    	if (this.postOnly && !request.getMethod().equals("POST")) {
        	throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
    	} else {
            String username = this.obtainUsername(request);
            username = username != null ? username.trim() : "";
            String password = this.obtainPassword(request);
            password = password != null ? password : "";
            UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
            this.setDetails(request, authRequest);
    return this.getAuthenticationManager().authenticate(authRequest);
    }
}

流程分析

  1. 检查请求方法:

    1
    2
    3
    
    if (this.postOnly && !request.getMethod().equals("POST")) {
        throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
    }
    
    • 如果 postOnlytrue 且请求方法不是 POST,则抛出 AuthenticationServiceException 异常,表示不支持非 POST 方法的认证请求。
  2. 获取用户名和密码:

    1
    2
    3
    4
    
    String username = this.obtainUsername(request);
    username = username != null ? username.trim() : "";
    String password = this.obtainPassword(request);
    password = password != null ? password : "";
    
    • 调用 obtainUsername(request) 获取请求中的用户名,如果用户名不为 null,则去掉前后空格,否则设置为空字符串。
    • 调用 obtainPassword(request) 获取请求中的密码,如果密码不为 null,则使用该密码,否则设置为空字符串。
  3. 创建未认证的 UsernamePasswordAuthenticationToken 对象:

    1
    
    UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
    
    • 使用获取到的用户名和密码封装一个未认证的 UsernamePasswordAuthenticationToken 对象。

    第5步的时候,Token用来验证 authentication 对象是否UsernamePasswordAuthenticationToken 类的实例

    1
    2
    3
    4
    5
    6
    7
    
    Assert.isInstanceOf(
        UsernamePasswordAuthenticationToken.class, authentication, () -> {
        return this.messages.getMessage(
            "AbstractUserDetailsAuthenticationProvider.onlySupports", 
            "Only UsernamePasswordAuthenticationToken is supported"
        );
    });
    
  4. 设置认证请求的详细信息:

    1
    
    this.setDetails(request, authRequest);
    
    • 调用 setDetails(request, authRequest) 方法,将请求详细信息(如请求的 remoteAddresssessionId 等)设置到 authRequest 中。
  5. 进行认证:

    1
    
    return this.getAuthenticationManager().authenticate(authRequest);
    
    • 调用 getAuthenticationManager().authenticate(authRequest) 方法进行认证。返回一个包含认证信息的 Authentication 对象。如果认证失败,会抛出相应的异常。
  6.  1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {
            return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported");
        });
        String username = this.determineUsername(authentication);
        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);
        if (user == null) {
            cacheWasUsed = false;
    
            try {
                user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            } catch (UsernameNotFoundException var6) {
                UsernameNotFoundException ex = var6;
                this.logger.debug("Failed to find user '" + username + "'");
                if (!this.hideUserNotFoundExceptions) {
                    throw ex;
                }throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
            }
    
    Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
        }
        try {
            this.preAuthenticationChecks.check(user);
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        } catch (AuthenticationException var7) {
            AuthenticationException ex = var7;
            if (!cacheWasUsed) {
                throw ex;
        }
    
        cacheWasUsed = false;
        user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
        this.preAuthenticationChecks.check(user);
        this.additionalAuthenticationChecks(
            user, (UsernamePasswordAuthenticationToken)authentication);
        }
    
        this.postAuthenticationChecks.check(user);
        if (!cacheWasUsed) {
            this.userCache.putUserInCache(user);
        }
    
        Object principalToReturn = user;
        if (this.forcePrincipalAsString) {
            principalToReturn = user.getUsername();
        }
    
            return this.createSuccessAuthentication(principalToReturn, authentication, user);
    
  • 12行 retrieveUser函数对用户输入的用户名和密码与内存中的用户名和密码进行比对

  • loadUserByUsername这个函数很重要,是用户认证的核心函数。

    1
    
    UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
    
  • 35行 additionalAuthenticationChecks函数

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException 
    {
        if (authentication.getCredentials() == null) {
            this.logger.debug("Failed to authenticate since no credentials provided");
            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        } else {
            String presentedPassword = authentication.getCredentials().toString();
            if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
                this.logger.debug("Failed to authenticate since password does not match stored value");
                throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
            }
        }
    }
    
    • if (authentication.getCredentials() == null)判断用户凭证
      • 在用户名密码模式下,用户凭证是用户名和密码
    • if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword()))使用对应的密码编码器对用户名和密码进行匹配。

基于数据库的用户验证流程

参照基于内存的用户验证,我们想要用数据库提供的用户数据进行用户验证。我们需要自己实现一个UserDetailsServiceBean。想要提供UserDetailsService首先要实现一个基于数据库的DBUserDetailService

1
2
public class InMemoryUserDetailsManager 
    implements UserDetailsManager, UserDetailsPasswordService 

可以看到InMemoryUserDetailsManager实现了两个接口UserDetailsManagerUserDetailsPasswordService

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public interface UserDetailsManager extends UserDetailsService {
    void createUser(UserDetails user);

    void updateUser(UserDetails user);

    void deleteUser(String username);

    void changePassword(String oldPassword, String newPassword);

    boolean userExists(String username);
}
1
2
3
public interface UserDetailsPasswordService {
    UserDetails updatePassword(UserDetails user, String newPassword);
}

可以看到UserDetailsManager继承了UserDetailsService,

1
2
3
4
public interface UserDetailsService {
    UserDetails loadUserByUsername(String username) 
        throws UsernameNotFoundException;
}

Spring Security 自动使用 InMemoryUserDetailsManagerloadUserByUsername 方法从内存中获取 User 对象。

所以参照基于内存的用户验证,我们可以这样做⬇️

认证流程

  • 程序启动时

    1. 创建 自定义的DBUserDetailsManager 对象。实现两个接口UserDetailsManagerUserDetailsPasswordService

    2.创建 User 对象,封装用户名和密码。 3. 使用 InMemoryUserDetailsManagerUser 存入内存。

  • 校验用户时

    • Spring Security 自动使用 DBUserDetailsManagerloadUserByUsername 方法从数据库中获取 User 对象。
    • UsernamePasswordAuthenticationFilter 过滤器中的 attemptAuthentication 方法中,将用户输入的用户名和密码与从数据库中获取到的用户信息进行比较,进行用户认证。

自己实现UserDetailService 认证

  1. 实现UserDetailsManager UserDetailsPasswordService接口的函数。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class DBUserDetailService  implements UserDetailsManager , UserDetailsPasswordService {
    @Override
    public UserDetails updatePassword(UserDetails user, String newPassword) {
        return null;
    }

    @Override
    public void createUser(UserDetails user) {

    }

    @Override
    public void updateUser(UserDetails user) {

    }

    @Override
    public void deleteUser(String username) {

    }

    @Override
    public void changePassword(String oldPassword, String newPassword) {

    }

    @Override
    public boolean userExists(String username) {
        return false;
    }

    //从数据库中获取用户信息
    @Override
    public UserDetails loadUserByUsername(String username) 
        throws UsernameNotFoundException {
        return null;
    }
}
  1. 📌实现重要的函数:loadUserByUsername(String username)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//从数据库中获取用户信息
@Override
public UserDetails loadUserByUsername(String username) 
    throws UsernameNotFoundException {
    //模拟从数据库中获取用户信息
    User user = new User();
    user.setUsername(username);
    user.setPassword("123456");
    user.setEnabled(true);
    user.setId(1);

    if(user==null){
        throw new UsernameNotFoundException(username);
    }else {
        //测试用的空权限列表
        Collection<GrantedAuthority> authorities=new ArrayList<>();
        return new org.springframework.security.core.userdetails.User(
            user.getUsername(),
            user.getPassword(),
            user.getEnabled(),
            true,       //用户是否过期
            true,                       //用户凭证是否过期
            true,                       //用户是否被锁定
            authorities                 //权限列表
        );
    }
    return null;
}

如果不设置密码加密编码器,可以使用无加密编码器NoOpPasswordEncoder

1
2
3
4
@Bean
public PasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();
}

⚠️实现UserDetailsService踩坑

报错:

1
No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken

​ 如果你实现了自定义的UserDetailService ,可以在配置类使用@Bean注解创建了UserDetailsService Bean。或者在实现的UserDetailService 类上注解@Component,也会自动创建Bean到SpringBoot容器。

🚫 千万不要 重复创建 UserDetailsService Bean

如果想要不同的UserDetailsService实现方法。可以定义一个自己的Authenticationprovider