如何在 Spring 授权服务器 0.2.0 中实现自定义 UserDetailsS​​ervice 或自定义 AuthenticationProvider

How to implement custom UserDetailsService or custom AuthenticationProvider in Spring authorization server 0.2.0

我正在尝试新的 Spring 授权服务器 0.2.0。我已成功 运行 位于 https://github.com/spring-projects/spring-authorization-server/tree/main/samples/boot/oauth2-integration.

的示例应用程序

现在,我正在尝试将自定义 UserDetailsS​​ervice 添加到授权服务器。我创建了一个自定义的 UserDetailsS​​ervice,用户保存在 Mysql 数据库中。

我已经替换了这个

    @Bean
    UserDetailsService users() {
        UserDetails user = User.withDefaultPasswordEncoder()
                .username("user1")
                .password("password")
                .roles("USER")
                .build();
        return new InMemoryUserDetailsManager(user);
    }

有了这个

    @Bean
    UserDetailsService users() {
        return new CustomUserDetailsService();
    }

现在,应用程序在尝试登录“授权码”授权流程时抛出错误“找不到 org.springframework.security.authentication.UsernamePasswordAuthenticationToken 的 AuthenticationProvider”。

我不知道在哪里添加我的自定义 AuthenticationProvider。我尝试如下添加到 DefaultSecurityConfig。但是授权码授予流程总是returns invalid_client.

    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http.authenticationProvider(authenticationProvider)
                .authorizeRequests(authorizeRequests ->
                        authorizeRequests.antMatchers("/actuator/**").permitAll()
                                .anyRequest().authenticated())
                .formLogin(withDefaults());
        return http.build();
    }

我想,我在这里遗漏了一些关于客户端身份验证的内容,因为我的自定义 authenticationProvider 必须仅为用户而不是客户端验证该方法。

现在我的问题是如何在不将 AuthenticationProvider 添加到 Spring 授权服务器的情况下添加自定义 AuthenticationProvider 或 CustomUserDetailsS​​ervice。

更新:

这是我的 CustomUserDetailsS​​ervice 和 CustomAuthenticationProvider


    @Service
    public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public DcubeUserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Supplier<UsernameNotFoundException> s =
                () -> new UsernameNotFoundException("Problem during authentication!");

        DcubeUser u = userRepository.findUserByUsername(username).orElseThrow(s);

        return new DcubeUserDetails(u);
       }
    }
    @Service
    public class CustomAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();

        CustomUserDetails user = userDetailsService.loadUserByUsername(username);
                return checkPassword(user, password);
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(aClass);
    }

    private Authentication checkPassword(CustomUserDetails user, String rawPassword) {
        if (Objects.equals(rawPassword, user.getPassword())) {
            return new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), user.getAuthorities());
        } else {
            throw new BadCredentialsException("Bad credentials");
        }
      }
    }

这是我的日志

2021-10-27 09:08:34.387 TRACE 10928 --- [nio-8888-exec-7] o.s.s.w.s.HttpSessionRequestCache        : Did not match request /error to the saved one DefaultSavedRequest [http://localhost:8888/oauth2/authorize?response_type=code&client_id=web-client&scope=web:write]
2021-10-27 09:08:34.387 TRACE 10928 --- [nio-8888-exec-7] o.s.security.web.FilterChainProxy        : Invoking SecurityContextHolderAwareRequestFilter (10/14)
2021-10-27 09:08:34.387 TRACE 10928 --- [nio-8888-exec-7] o.s.security.web.FilterChainProxy        : Invoking AnonymousAuthenticationFilter (11/14)
2021-10-27 09:08:34.387 TRACE 10928 --- [nio-8888-exec-7] o.s.s.w.a.AnonymousAuthenticationFilter  : Did not set SecurityContextHolder since already authenticated UsernamePasswordAuthenticationToken [Principal=Ramesh, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=D4FF0763B895353AC334140528DE35CD], Granted Authorities=[ADMIN]]
2021-10-27 09:08:34.387 TRACE 10928 --- [nio-8888-exec-7] o.s.security.web.FilterChainProxy        : Invoking SessionManagementFilter (12/14)
2021-10-27 09:08:34.387 TRACE 10928 --- [nio-8888-exec-7] o.s.security.web.FilterChainProxy        : Invoking ExceptionTranslationFilter (13/14)
2021-10-27 09:08:34.387 TRACE 10928 --- [nio-8888-exec-7] o.s.security.web.FilterChainProxy        : Invoking FilterSecurityInterceptor (14/14)
2021-10-27 09:08:34.387 DEBUG 10928 --- [nio-8888-exec-7] o.s.security.web.FilterChainProxy        : Secured GET /error
2021-10-27 09:08:34.464 DEBUG 10928 --- [nio-8888-exec-7] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request
2021-10-27 09:08:56.440 TRACE 10928 --- [nio-8888-exec-8] o.s.security.web.FilterChainProxy        : Trying to match request against DefaultSecurityFilterChain [RequestMatcher=org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer$$Lambda85/0x0000000801355c90@32e7df65, Filters=[org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@421d7900, org.springframework.security.web.context.SecurityContextPersistenceFilter@42a7e7e1, org.springframework.security.web.header.HeaderWriterFilter@55e88bc, org.springframework.security.web.csrf.CsrfFilter@20a116a0, org.springframework.security.web.authentication.logout.LogoutFilter@67e21ea2, org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter@e2ee348, org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter@5d67bf4d, org.springframework.security.oauth2.server.authorization.web.NimbusJwkSetEndpointFilter@1477d4e6, org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter@5ec3689b, org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter@448fa659, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@7c0a6f62, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@4946dfde, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@45964b9e, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@554e9509, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@34ea86ff, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@166a5659, org.springframework.security.web.session.SessionManagementFilter@4c12f54a, org.springframework.security.web.access.ExceptionTranslationFilter@417b3642, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@22ed2886, org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter@76219fe, org.springframework.security.oauth2.server.authorization.web.OAuth2TokenIntrospectionEndpointFilter@4c599679, org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter@1bcf2c64]] (1/2)
2021-10-27 09:08:56.440 DEBUG 10928 --- [nio-8888-exec-8] o.s.security.web.FilterChainProxy        : Securing POST /oauth2/authorize
2021-10-27 09:08:56.440 TRACE 10928 --- [nio-8888-exec-8] o.s.security.web.FilterChainProxy        : Invoking WebAsyncManagerIntegrationFilter (1/22)
2021-10-27 09:08:56.441 TRACE 10928 --- [nio-8888-exec-8] o.s.security.web.FilterChainProxy        : Invoking SecurityContextPersistenceFilter (2/22)
2021-10-27 09:08:56.441 TRACE 10928 --- [nio-8888-exec-8] w.c.HttpSessionSecurityContextRepository : Retrieved SecurityContextImpl [Authentication=UsernamePasswordAuthenticationToken [Principal=Ramesh, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=D4FF0763B895353AC334140528DE35CD], Granted Authorities=[ADMIN]]] from SPRING_SECURITY_CONTEXT
2021-10-27 09:08:56.441 DEBUG 10928 --- [nio-8888-exec-8] s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to SecurityContextImpl [Authentication=UsernamePasswordAuthenticationToken [Principal=Ramesh, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=D4FF0763B895353AC334140528DE35CD], Granted Authorities=[ADMIN]]]
2021-10-27 09:08:56.441 TRACE 10928 --- [nio-8888-exec-8] o.s.security.web.FilterChainProxy        : Invoking HeaderWriterFilter (3/22)
2021-10-27 09:08:56.442 TRACE 10928 --- [nio-8888-exec-8] o.s.security.web.FilterChainProxy        : Invoking CsrfFilter (4/22)
2021-10-27 09:08:56.442 TRACE 10928 --- [nio-8888-exec-8] o.s.security.web.csrf.CsrfFilter         : Did not protect against CSRF since request did not match And [CsrfNotRequired [TRACE, HEAD, GET, OPTIONS], Not [Or [org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer$$Lambda85/0x0000000801355c90@32e7df65]]]
2021-10-27 09:08:56.442 TRACE 10928 --- [nio-8888-exec-8] o.s.security.web.FilterChainProxy        : Invoking LogoutFilter (5/22)
2021-10-27 09:08:56.443 TRACE 10928 --- [nio-8888-exec-8] o.s.s.w.a.logout.LogoutFilter            : Did not match request to Ant [pattern='/logout', POST]
2021-10-27 09:08:56.443 TRACE 10928 --- [nio-8888-exec-8] o.s.security.web.FilterChainProxy        : Invoking OAuth2AuthorizationEndpointFilter (6/22)
2021-10-27 09:08:56.444 TRACE 10928 --- [nio-8888-exec-8] o.s.s.authentication.ProviderManager     : Authenticating request with OAuth2AuthorizationCodeRequestAuthenticationProvider (1/8)
2021-10-27 09:08:56.447 DEBUG 10928 --- [nio-8888-exec-8] o.s.s.web.DefaultRedirectStrategy        : Redirecting to http://localhost:8080/authorized?code=iaFZSqcRLeJucw2mx_HNgji1PWN9QPHaUZt0htdH2zc3_4hEPFoBamnijwuRcK2xTzOT_W4jCTne3AmjAKB2gyoVzod5otPfgB8WSLc_8-x2B13oapwhlWX4dBUUER2e
2021-10-27 09:08:56.447 TRACE 10928 --- [nio-8888-exec-8] o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match request to [Is Secure]
2021-10-27 09:08:56.447 DEBUG 10928 --- [nio-8888-exec-8] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request
2021-10-27 09:09:23.121 TRACE 10928 --- [io-8888-exec-10] o.s.security.web.FilterChainProxy        : Trying to match request against DefaultSecurityFilterChain [RequestMatcher=org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer$$Lambda85/0x0000000801355c90@32e7df65, Filters=[org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@421d7900, org.springframework.security.web.context.SecurityContextPersistenceFilter@42a7e7e1, org.springframework.security.web.header.HeaderWriterFilter@55e88bc, org.springframework.security.web.csrf.CsrfFilter@20a116a0, org.springframework.security.web.authentication.logout.LogoutFilter@67e21ea2, org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter@e2ee348, org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter@5d67bf4d, org.springframework.security.oauth2.server.authorization.web.NimbusJwkSetEndpointFilter@1477d4e6, org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter@5ec3689b, org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter@448fa659, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@7c0a6f62, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@4946dfde, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@45964b9e, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@554e9509, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@34ea86ff, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@166a5659, org.springframework.security.web.session.SessionManagementFilter@4c12f54a, org.springframework.security.web.access.ExceptionTranslationFilter@417b3642, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@22ed2886, org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter@76219fe, org.springframework.security.oauth2.server.authorization.web.OAuth2TokenIntrospectionEndpointFilter@4c599679, org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter@1bcf2c64]] (1/2)
2021-10-27 09:09:23.122 DEBUG 10928 --- [io-8888-exec-10] o.s.security.web.FilterChainProxy        : Securing POST /oauth2/token?grant_type=authorization_code&code=iaFZSqcRLeJucw2mx_HNgji1PWN9QPHaUZt0htdH2zc3_4hEPFoBamnijwuRcK2xTzOT_W4jCTne3AmjAKB2gyoVzod5otPfgB8WSLc_8-x2B13oapwhlWX4dBUUER2e&scope=dcube:write&redirect_uri=http://localhost:8888/authorized
2021-10-27 09:09:23.122 TRACE 10928 --- [io-8888-exec-10] o.s.security.web.FilterChainProxy        : Invoking WebAsyncManagerIntegrationFilter (1/22)
2021-10-27 09:09:23.122 TRACE 10928 --- [io-8888-exec-10] o.s.security.web.FilterChainProxy        : Invoking SecurityContextPersistenceFilter (2/22)
2021-10-27 09:09:23.122 TRACE 10928 --- [io-8888-exec-10] w.c.HttpSessionSecurityContextRepository : No HttpSession currently exists
2021-10-27 09:09:23.122 TRACE 10928 --- [io-8888-exec-10] w.c.HttpSessionSecurityContextRepository : Created SecurityContextImpl [Null authentication]
2021-10-27 09:09:23.122 DEBUG 10928 --- [io-8888-exec-10] s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
2021-10-27 09:09:23.122 TRACE 10928 --- [io-8888-exec-10] o.s.security.web.FilterChainProxy        : Invoking HeaderWriterFilter (3/22)
2021-10-27 09:09:23.122 TRACE 10928 --- [io-8888-exec-10] o.s.security.web.FilterChainProxy        : Invoking CsrfFilter (4/22)
2021-10-27 09:09:23.122 TRACE 10928 --- [io-8888-exec-10] o.s.security.web.csrf.CsrfFilter         : Did not protect against CSRF since request did not match And [CsrfNotRequired [TRACE, HEAD, GET, OPTIONS], Not [Or [org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer$$Lambda85/0x0000000801355c90@32e7df65]]]
2021-10-27 09:09:23.122 TRACE 10928 --- [io-8888-exec-10] o.s.security.web.FilterChainProxy        : Invoking LogoutFilter (5/22)
2021-10-27 09:09:23.122 TRACE 10928 --- [io-8888-exec-10] o.s.s.w.a.logout.LogoutFilter            : Did not match request to Ant [pattern='/logout', POST]
2021-10-27 09:09:23.122 TRACE 10928 --- [io-8888-exec-10] o.s.security.web.FilterChainProxy        : Invoking OAuth2AuthorizationEndpointFilter (6/22)
2021-10-27 09:09:23.123 TRACE 10928 --- [io-8888-exec-10] o.s.security.web.FilterChainProxy        : Invoking OidcProviderConfigurationEndpointFilter (7/22)
2021-10-27 09:09:23.123 TRACE 10928 --- [io-8888-exec-10] o.s.security.web.FilterChainProxy        : Invoking NimbusJwkSetEndpointFilter (8/22)
2021-10-27 09:09:23.123 TRACE 10928 --- [io-8888-exec-10] o.s.security.web.FilterChainProxy        : Invoking OAuth2AuthorizationServerMetadataEndpointFilter (9/22)
2021-10-27 09:09:23.123 TRACE 10928 --- [io-8888-exec-10] o.s.security.web.FilterChainProxy        : Invoking OAuth2ClientAuthenticationFilter (10/22)
2021-10-27 09:09:23.124 TRACE 10928 --- [io-8888-exec-10] o.s.s.authentication.ProviderManager     : Authenticating request with OAuth2ClientAuthenticationProvider (1/8)
2021-10-27 09:09:23.124  WARN 10928 --- [io-8888-exec-10] o.s.s.c.bcrypt.BCryptPasswordEncoder     : Encoded password does not look like BCrypt
2021-10-27 09:09:23.127 TRACE 10928 --- [io-8888-exec-10] o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match request to [Is Secure]
2021-10-27 09:09:23.127 DEBUG 10928 --- [io-8888-exec-10] w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
2021-10-27 09:09:23.127 DEBUG 10928 --- [io-8888-exec-10] w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
2021-10-27 09:09:23.127 DEBUG 10928 --- [io-8888-exec-10] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request

首先,交叉检查您是否在安全配置文件中进行了相关更改。

@Configuration
@EnableWebSecurity
@ComponentScan("com.frugalis")
public class CustAuthProviderConfig extends WebSecurityConfigurerAdapter {
  
    @Autowired
    private CustomAuthenticationProvider authProvider;
 
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authProvider);
    }
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated()
            .and().httpBasic();
    }
}

以上代码注册自定义身份验证提供程序并授权用户。

要创建自定义用户服务,您需要实现UserDetailsService接口并覆盖loadUserByUsername()方法。

service 包下创建 UserDetailsServiceImp class。

public class UserDetailsServiceImp implements UserDetailsService {
  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

    /*Here we are using dummy data, you need to load user data from
     database or other third party application*/
    User user = findUserbyUername(username);

    UserBuilder builder = null;
    if (user != null) {
      builder = org.springframework.security.core.userdetails.User.withUsername(username);
      builder.password(new BCryptPasswordEncoder().encode(user.getPassword()));
      builder.roles(user.getRoles());
    } else {
      throw new UsernameNotFoundException("User not found.");
    }

    return builder.build();
  }

  private User findUserbyUername(String username) {
    if(username.equalsIgnoreCase("admin")) {
      return new User(username, "admin123", "ADMIN");
    }
    return null;
  }
}

确保使用合适的 spring 引导版本和 maven 存储库。

更多请参考这个例子:Spring Security 5 - Custom UserDetailsService example

现在定义一个自定义身份验证提供程序

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

    boolean shouldAuthenticateAgainstThirdPartySystem = true;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String name = authentication.getName();
        String password = authentication.getCredentials().toString();

        if (name.equals("admin") && password.equals("password")) {
            final List<GrantedAuthority> grantedAuths = new ArrayList<>();
            grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
            final UserDetails principal = new User(name, password, grantedAuths);
            final Authentication auth = new UsernamePasswordAuthenticationToken(principal, password, grantedAuths);
            return auth;
        } else {
            return null;
        }

    }

    @Override
    public boolean supports(Class<?> authentication) {

        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }

}

在上面的代码中,我们从身份验证对象中检索了用户名和密码。一旦我们检索到身份验证对象和凭据,我们就会验证用户名和密码。

您可以执行基于数据库的身份验证,我们在这里进行了硬编码验证。

一旦用户有效,我们就尝试在字符串列表中设置 GRANTED_AUTHORITY 和 return 一个 UserDetails 对象给调用者一个身份验证对象。我们可以自定义 principal 和 return.

中设置的用户对象,而不是 spring 提供的 UserDetails

Custom Authentication Provider Spring Security

您需要从 DefaultSecurityConfig class 中删除以下 Bean

@Bean
UserDetailsService users() {
UserDetails user = User.withDefaultPasswordEncoder()
        .username("user1")
        .password("password")
        .roles("USER")
        .build();
   return new InMemoryUserDetailsManager(user);
 }

将以下方法和 Autowired 自定义 AuthenticationProvider 添加到相同的 class

   @Autowired
   public void myCoolMethodName(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(authenticationProvider);
   }

现在一切如我所愿。

完整的DefaultSecurityConfig会像

@EnableWebSecurity
public class DefaultSecurityConfig {

@Autowired
private CustomAuthenticationProvider authenticationProvider;

 @Bean
 PasswordEncoder passwordEncoder() {
   return PasswordEncoderFactories.createDelegatingPasswordEncoder();
  }

@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
    http
            .authorizeRequests(authorizeRequests ->
                    authorizeRequests.anyRequest().authenticated())
            .formLogin(withDefaults());
    return http.build();
  }

@Autowired
public void whateverMethodName(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(authenticationProvider);
   }

}