Spring Security and Keycloak使用自定义身份验证提供程序失败



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

我有我自己的AuthenticationProviderAbstractAuthenticationProcessingFilter,但现在所有请求到服务器抛出一个403,甚至有效的Keycloak请求。奇怪的是,我的新代码甚至没有被执行,因为没有日志记录或断点命中的迹象。我已经通读了多个认证文档,并审查了几个SO帖子,但仍然无法使其工作。

这是我的自定义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。

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

@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();
}

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

最新更新