【问题标题】:Spring Security and Keycloak fails with a custom authentication providerSpring Security 和 Keycloak 因自定义身份验证提供程序而失败
【发布时间】:2021-12-14 00:00:30
【问题描述】:

我们已经将 Keycloak 与 Spring Security (Spring Boot 2) 一起使用了一段时间,现在我们正在尝试添加一个自定义 API-Key 身份验证机制,在该机制中我们检查名为 api-key 的标头并将该值发送到远程服务进行验证,如果有效,则完全跳过 Keycloak 检查。这适用于所有请求和端点。

我有自己的 AuthenticationProviderAbstractAuthenticationProcessingFilter,但现在 所有 对服务器的请求都会抛出 403,甚至是有效的 Keycloak 请求。奇怪的是,我的新代码甚至都没有被执行,因为没有日志记录或断点命中的迹象。我已经阅读了多重身份验证documentationreviewed several SO posts,但仍然无法正常工作。

这是我的自定义AuthenticationProvider

public class ApiKeyAuthenticationProvider implements AuthenticationProvider {

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        log.info("API-KEY: Provider.authenticate()");

        ApiKeyAuthenticationToken auth = (ApiKeyAuthenticationToken) authentication;

        String apiKey = auth.getCredentials().toString();

        // Always returns TRUE at the moment to test bypassing Keycloak
        boolean isApiKeyValid = RemoteApiKeyService.verify(apiKey);

        if (isApiKeyValid) {
            log.info("API-KEY: auth successful");
            auth.setAuthenticated(true);
        } else {
            log.warn("API-KEY: auth failed");
            throw new BadCredentialsException("Api-Key Authentication Failed");
        }

        return auth;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        log.info("API-KEY: Provider.supports(): " + authentication.getSimpleName());
        return authentication.isAssignableFrom(ApiKeyAuthenticationToken.class);
    }
}

我的令牌:

public class ApiKeyAuthenticationToken extends AbstractAuthenticationToken {

    private final String token;

    public ApiKeyAuthenticationToken(String token) {
        super(null);
        this.token = token;
    }

    @Override
    public Object getCredentials() {
        return token;
    }

    @Override
    public Object getPrincipal() {
        return null;
    }
}

这是过滤器:

public class ApiKeyFilter extends AbstractAuthenticationProcessingFilter {

    public ApiKeyFilter() {
        super("/*");
        log.info("API-KEY filter.init()");
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        log.info("API-KEY filter.attemptAuthentication()");
        String apiKeyHeader = request.getHeader("api-key");
        if (apiKeyHeader != null) {
            return new ApiKeyAuthenticationToken(apiKeyHeader);
        }

        return null;
    }
}

最后,我如何使用多个提供程序将所有内容与我的安全配置捆绑在一起:

@Slf4j
@Configuration
@EnableWebSecurity
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
public class SecurityConf {

  @Configuration
  @Order(1) //Order is 1 -> First the special case
  public static class ApiKeySecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception
    {
      http.csrf().disable().authorizeRequests()
              .antMatchers("/**").authenticated();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
      // our custom authentication provider
      auth.authenticationProvider(new ApiKeyAuthenticationProvider());
    }
  }

  @Configuration
  @Order(2) // processed after our API Key bean config
  public static class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
      KeycloakAuthenticationProvider provider = keycloakAuthenticationProvider();
      provider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
      auth.authenticationProvider(provider);
    }

    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
      return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
      super.configure(http);

      http.csrf().disable().authorizeRequests();
      http.headers().frameOptions().disable();
    }

    // necessary due to http://www.keycloak.org/docs/latest/securing_apps/index.html#avoid-double-filter-bean-registration
    @Bean
    public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean(KeycloakAuthenticationProcessingFilter filter) {
      FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
      registrationBean.setEnabled(false);
      return registrationBean;
    }

    // necessary due to http://www.keycloak.org/docs/latest/securing_apps/index.html#avoid-double-filter-bean-registration
    @Bean
    public FilterRegistrationBean keycloakPreAuthActionsFilterRegistrationBean(KeycloakPreAuthActionsFilter filter) {
      FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
      registrationBean.setEnabled(false);
      return registrationBean;
    }

    // necessary due to http://www.keycloak.org/docs/latest/securing_apps/index.html#avoid-double-filter-bean-registration
    @Bean
    public FilterRegistrationBean keycloakAuthenticatedActionsFilterBean(
            KeycloakAuthenticatedActionsFilter filter) {
      FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
      registrationBean.setEnabled(false);
      return registrationBean;
    }

    // necessary due to http://www.keycloak.org/docs/latest/securing_apps/index.html#avoid-double-filter-bean-registration
    @Bean
    public FilterRegistrationBean keycloakSecurityContextRequestFilterBean(
            KeycloakSecurityContextRequestFilter filter) {
      FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
      registrationBean.setEnabled(false);
      return registrationBean;
    }


    @Bean
    @Scope(value = "singleton")
    public KeycloakSpringBootConfigResolver keycloakConfigResolver() {

      final KeycloakDeployment keycloakDeployment = KeycloakDeploymentBuilder.build(
              KeycloakClient.default_client.toAdapterConfig()
      );

      return new KeycloakSpringBootConfigResolver() {

        @Override
        public KeycloakDeployment resolve(HttpFacade.Request request) {
          return keycloakDeployment;
        }

      };
    }
  }
}

知道什么配置错误吗?我的任何代码都没有运行但破坏 Keycloak 的事实很有趣。

【问题讨论】:

    标签: java spring spring-boot spring-security keycloak


    【解决方案1】:

    以此为例,在你的代码中进行相应的尝试

    @Override
        protected void configure(HttpSecurity http) throws Exception {
            AuthenticationProvider rememberMeAuthenticationProvider = rememberMeAuthenticationProvider();
            TokenBasedRememberMeServices tokenBasedRememberMeServices = tokenBasedRememberMeServices();
    
            List<AuthenticationProvider> authenticationProviders = new ArrayList<AuthenticationProvider>(2);
            authenticationProviders.add(rememberMeAuthenticationProvider);
            authenticationProviders.add(customAuthenticationProvider);
            AuthenticationManager authenticationManager = authenticationManager(authenticationProviders);
    
            http
                    .csrf().disable()
                    .headers().disable()
                    .addFilter(new RememberMeAuthenticationFilter(authenticationManager, tokenBasedRememberMeServices))
                    .rememberMe().rememberMeServices(tokenBasedRememberMeServices)
                    .and()
                    .authorizeRequests()
                    .antMatchers("/js/**", "/css/**", "/img/**", "/login", "/processLogin").permitAll()
                    .antMatchers("/index.jsp", "/index.html", "/index").hasRole("USER")
                    .antMatchers("/admin", "/admin.html", "/admin.jsp", "/js/saic/jswe/admin/**").hasRole("ADMIN")
                    .and()
                    .formLogin().loginProcessingUrl("/processLogin").loginPage("/login").usernameParameter("username").passwordParameter("password").permitAll()
                    .and()
                    .exceptionHandling().accessDeniedPage("/login")
                    .and()
                    .logout().permitAll();
        }
    

    注意:这里的关键是在配置文件中添加令牌和过滤器,如上所述。 很抱歉没有发布直接答案,因为我有这个,它会给你一个广泛的工作领域或一个想法去工作以使代码正常运行

    【讨论】:

    • 这个configure() 应该在我的扩展WebSecurityConfigurerAdapter 的第一类(bean @Order(1))中,还是我正在自定义扩展KeycloakWebSecurityConfigureAdapter 的第二类?根据官方documentation 的说法,多个身份验证提供者每个都应该有自己的类和configure() 覆盖。我也从未见过RememberMeServices,所以我会检查一下。
    • 对于多个身份验证提供者,每个提供者都应该有自己的类和 configure() 覆盖。这是正确的。
    • 所以你想问什么,请澄清
    • Tarun - 抱歉延迟回复。我认为主要问题是,每当我引入一个总是验证为真的新提供者/rememberMeService,只是为了测试,然后一切都会中断。 Keycloak 请求返回 401,而我的新提供者总是执行 auth=true 并返回 400。在我们到达过滤器之前,作为低级管道的 Keycloak Valve 可能与它有关。我只是找不到 Keycloak 和另一个自定义提供程序的简单示例。我会继续修改并浏览文档。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-02-09
    • 2016-07-19
    • 2016-09-24
    • 2011-01-03
    • 2016-01-06
    • 2014-05-01
    • 2021-10-30
    相关资源
    最近更新 更多