Spring Security 和 Keycloak 因自定义身份验证提供程序而失败
Posted
技术标签:
【中文标题】Spring Security 和 Keycloak 因自定义身份验证提供程序而失败【英文标题】:Spring Security and Keycloak fails with a custom authentication provider 【发布时间】:2021-12-14 00:00:30 【问题描述】:我们已经将 Keycloak 与 Spring Security (Spring Boot 2) 一起使用了一段时间,现在我们正在尝试添加一个自定义 API-Key 身份验证机制,在该机制中我们检查名为 api-key
的标头并将该值发送到远程服务进行验证,如果有效,则完全跳过 Keycloak 检查。这适用于所有请求和端点。
我有自己的 AuthenticationProvider
和 AbstractAuthenticationProcessingFilter
,但现在 所有 对服务器的请求都会抛出 403,甚至是有效的 Keycloak 请求。奇怪的是,我的新代码甚至都没有被执行,因为没有日志记录或断点命中的迹象。我已经阅读了多重身份验证documentation 和reviewed 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 的事实很有趣。
【问题讨论】:
【参考方案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 和另一个自定义提供程序的简单示例。我会继续修改并浏览文档。以上是关于Spring Security 和 Keycloak 因自定义身份验证提供程序而失败的主要内容,如果未能解决你的问题,请参考以下文章
社交登录,spring-security-oauth2 和 spring-security-jwt?
Spring Security入门(3-4)Spring Security 异常处理异常传递和异常获取
Spring 框架 4.0 和 Spring security 3.2.4 上的 Spring Security SAML 扩展
org.springframework.security.oauth 和 org.codehaus.spring-security-oauth 有啥区别?
Spring Security入门(3-5)Spring Security 的鉴权 - 决策管理器和投票器
在使用 Oauth、SAML 和 spring-security 的多租户的情况下从 spring-security.xml 中获取错误