Spring Keycloak Adapter loads Open-ID configuration for every single request - java

I have a Spring project with Keycloak adapter configured and noticed that it loads openid-configuration for every request. Is there any mechanism to cache this configuration, or, why is this happening?
Could not understand this behavior and Keycloak Docs say nothing about this. As I saw the source code, It resolves this configuration when KeycloakDeployment object is created, so every time a request comes a new KeycloakDeployment object is created (see: Keycloak adapter source)
This is the log:
2020-06-25 08:31:36.103 INFO 1 --- [io-8080-exec-10] o.keycloak.adapters.KeycloakDeployment : Loaded URLs from https://mykeyloak.com/auth/realms/myrealm/.well-known/openid-configuration
Here is my Keycloak adapter configuration:
#KeycloakConfiguration
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
private KeycloakProperties keycloakProperties;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Bean
public AdapterConfig adapterConfig() {
AdapterConfig adapterConfig = new AdapterConfig();
adapterConfig.setRealm(keycloakProperties.getRealm());
adapterConfig.setResource(keycloakProperties.getResource());
adapterConfig.setAuthServerUrl(keycloakProperties.getAuthServerUrl());
adapterConfig.setSslRequired(keycloakProperties.getSslRequired());
adapterConfig.setBearerOnly(keycloakProperties.getBearerOnly());
adapterConfig.setCredentials(keycloakProperties.getCredentials());
adapterConfig.setCors(keycloakProperties.getEnableCors());
adapterConfig.setUseResourceRoleMappings(keycloakProperties.getUseResourceRoleMappings());
return adapterConfig;
}
#Bean
public KeycloakConfigResolver keycloakConfigResolver(AdapterConfig adapterConfig) {
return new KeycloakConfigResolver() {
#Override
public KeycloakDeployment resolve(HttpFacade.Request request) {
return KeycloakDeploymentBuilder.build(adapterConfig);
}
};
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/**").authenticated()
.anyRequest().permitAll();
}
#Autowired
public void setKeycloakProperties(KeycloakProperties keycloakProperties) {
this.keycloakProperties = keycloakProperties;
}
}
Keycloak properties:
keycloak.realm=myrealm
keycloak.resource=myclient
keycloak.auth-server-url=https://mykeycloak.com/auth
keycloak.ssl-required=external
keycloak.bearer-only=true
keycloak.credentials={}
keycloak.enable-cors=true
keycloak.use-resource-role-mappings=false

I had to register KeycloakDeployment bean and return that from KeycloakConfigResolver resolve method.
#Bean
public KeycloakDeployment keycloakDeployment(AdapterConfig adapterConfig) {
return KeycloakDeploymentBuilder.build(adapterConfig);
}
#Bean
public KeycloakConfigResolver keycloakConfigResolver(KeycloakDeployment keycloakDeployment) {
return request -> keycloakDeployment;
}

Related

Spring boot login keycloak

My configuration in spring boot always redirect to login form keycloak.
i don't want to login with keycloak form.
i want to login to keycloak with my form login, how can I solve my problem?
this is my config:
#Configuration
public class SecurityConfig {
#Bean
protected SessionRegistry buildSessionRegistry() {
return new SessionRegistryImpl();
}
#Bean
public KeycloakSpringBootConfigResolver keycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
#KeycloakConfiguration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public static class KeycloakConfig extends KeycloakWebSecurityConfigurerAdapter {
#Autowired
SessionRegistry buildSessionRegistry;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(keycloakAuthenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.headers().frameOptions().sameOrigin().httpStrictTransportSecurity().disable();
http.authorizeRequests().anyRequest().authenticated();
http.sessionManagement().sessionAuthenticationStrategy(sessionAuthenticationStrategy()).and()
.addFilterBefore(keycloakPreAuthActionsFilter(), LogoutFilter.class)
.addFilterBefore(keycloakAuthenticationProcessingFilter(), LogoutFilter.class)
.addFilterAfter(keycloakSecurityContextRequestFilter(),
SecurityContextHolderAwareRequestFilter.class)
.addFilterAfter(keycloakAuthenticatedActionsRequestFilter(),
KeycloakSecurityContextRequestFilter.class)
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint()).and().logout()
.addLogoutHandler(keycloakLogoutHandler())
.logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/");
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(buildSessionRegistry);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/img/**", "/js/**", "/css/**");
}
}
}

How to add another authorization-filter to a url that is already protected by Keycloak

I have a Spring Boot application protected with KeycloakWebSecurityConfigurerAdapter. Now i want to add another (custom) way of authentication for certain endpoints. Therefore i created a class extending AbstractAuthenticationProcessingFilter and AuthenticationProvider with its custom logic. Now i want to add the custom filter to the HttpSecurity object in my security-configuration with
http.addFilterBefore(new VendorSessionAuthorizationFilter(), KeycloakAuthenticationProcessingFilter.class);
My understanding is that i get to my custom filter first and depending on the result, the security-filter-chain goes on to the Keycloak-Filters. When i test to call an endpoint i never get to the attemptAuthentication method i implemented in my filter.
I have the feeling that no matter what i do when calling http.addFilterBefore(... the Keycloak-Filters are always executed so there is no way for me to have another way of authentication.
Here the SecurityConfiguration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(jsr250Enabled = true, prePostEnabled = true)
public class KeycloakSecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.addFilterBefore(new VendorSessionAuthorizationFilter(), KeycloakAuthenticationProcessingFilter.class);
http.authenticationProvider(new VendorSessionAuthenticationProvider());
http
.cors().and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.sessionAuthenticationStrategy(sessionAuthenticationStrategy())
.and().formLogin().disable()
.httpBasic().disable()
.logout().disable()
.authorizeRequests()
.anyRequest().authenticated();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
#Bean
public KeycloakConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
#Bean
public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean(
KeycloakAuthenticationProcessingFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
#Bean
public FilterRegistrationBean keycloakPreAuthActionsFilterRegistrationBean(
KeycloakPreAuthActionsFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
#Bean
public FilterRegistrationBean keycloakAuthenticatedActionsFilterBean(
KeycloakAuthenticatedActionsFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
#Bean
public FilterRegistrationBean keycloakSecurityContextRequestFilterBean(
KeycloakSecurityContextRequestFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
}
```
If you want to use an alternative auth method for certain endpoints, there is a way as below
at the configure part of the adapter, you can add an antMatcher and reference a function of a bean. You can place the function in the same class
.antMatchers("/your-api-here")
.access("#keycloakSecurityConfiguration.checkSomething()")
Your total configuration would be something like below (adapt properly)
public class KeycloakSecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.addFilterBefore(new VendorSessionAuthorizationFilter(), KeycloakAuthenticationProcessingFilter.class);
http.authenticationProvider(new VendorSessionAuthenticationProvider());
http
.cors().and()
.csrf().disable()
.antMatchers("/your-api-here")
.access("#keycloakSecurityConfiguration.checkSomething()")
.....
}
public boolean checkSomething(){
// your code here
}
NOTE This will override the default keycloak auth for the api, not provide an extra one on top

Getting Unauthorized Error 401 When I Call API Secure Endpoint with Keycloak

I'm setting up a spring boot + keycloak project. I am able to generate the token by calling the keycloak endpoint. When I call an api from my application I get the error 401 Unauthorized Error. However I am passing "Bearer generated token".
Controller:
#PostMapping(path = "/products") public List<String> getProducts(){
return productService.getProducts(); }
KeycloakConfiguration:
#KeycloakConfiguration
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
#Bean
public KeycloakConfigResolver keycloakConfigResolver(){
return new KeycloakSpringBootConfigResolver();
}
#Autowired
public KeycloakClientRequestFactory keycloakClientRequestFactory;
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public KeycloakRestTemplate keycloakRestTemplate(){
return new KeycloakRestTemplate(keycloakClientRequestFactory);
}
#Override
protected void configure(HttpSecurity http) throws Exception{
super.configure(http);
http.authorizeRequests()
.antMatchers("/products*").hasRole("user")
.anyRequest().permitAll()
.and().csrf().disable();
}
}
Properties:
server.port=8090
keycloak.enabled=true
keycloak.auth-server-url=http://localhost:8080/auth
keycloak.realm=chamae-api
keycloak.resource=login-app
keycloak.bearer-only=true
I would just like to access the api "products" by passing the generated Bearer token

Spring boot encoding filter

I use spring boot and spring security and I need to encode requests. So I use encoding filter in spring security and add it before others filters. And it doesn't work. I have the following result:
#Configuration
#EnableWebSecurity
public class SecurityJavaConfig extends WebSecurityConfigurerAdapter {
#Autowired
private MyUserDetailsService myUserDetailsService;
#Autowired
private UserDao userDao;
#Autowired
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
#Autowired
private AuthenticationSuccessHandler authenticationSuccessHandler;
#Autowired
private CustomAuthenticationFailureHandler customAuthenticationFailureHandler;
#Bean(name = "myAuthenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder customPasswordEncoder() {
return new PasswordEncoder() {
#Override
public String encode(CharSequence rawPassword) {
return DigestUtils.md5Hex(rawPassword.toString());
}
#Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return DigestUtils.md5Hex(rawPassword.toString()).equals(encodedPassword);
}
};
}
#Bean
public CustomUsernamePasswordAuthenticationFilter customUsernamePasswordAuthenticationFilter()
throws Exception {
CustomUsernamePasswordAuthenticationFilter customUsernamePasswordAuthenticationFilter = new CustomUsernamePasswordAuthenticationFilter(userDao);
customUsernamePasswordAuthenticationFilter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/login", "POST"));
customUsernamePasswordAuthenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
customUsernamePasswordAuthenticationFilter.setAuthenticationFailureHandler(customAuthenticationFailureHandler);
customUsernamePasswordAuthenticationFilter
.setAuthenticationManager(authenticationManagerBean());
return customUsernamePasswordAuthenticationFilter;
}
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.userDetailsService(myUserDetailsService).passwordEncoder(customPasswordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
filter.setForceEncoding(true);
http.addFilterBefore(filter,CsrfFilter.class);
//Implementing Token based authentication in this filter
final TokenAuthenticationFilter tokenFilter = new TokenAuthenticationFilter(userDao);
http.addFilterBefore(tokenFilter, BasicAuthenticationFilter.class);
http.addFilterBefore(customUsernamePasswordAuthenticationFilter(), CustomUsernamePasswordAuthenticationFilter.class);
http.csrf().disable();
http.cors();
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.authorizeRequests()
.antMatchers("/news/create").hasAnyAuthority("ADMIN")
.antMatchers("/news/update").hasAnyAuthority("ADMIN")
.antMatchers("/news/update/*").hasAnyAuthority("ADMIN")
.antMatchers("/news/delete").hasAnyAuthority("ADMIN")
.antMatchers("/**").hasAnyAuthority("USER", "ADMIN")
.and()
.logout();
}
//cors configuration bean
...
}
I've used many different ways how to solve it. But nothing works...
I can't now post this question because there is a lot of code. So sorry, but I have to write some sentences to post it)
Thanks
try to add encoding config in application.properties
as below :
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true
See doc for more info

No Such Client Exception Spring Oauth2

I am trying to implement Spring Security OAuth2 using Java config.
My usecase requires the use of password grant_type.
I have configured this so far without the need for a web.xml and would prefer to keep it that way
Versions I am using:
Spring Framework: 4.1.6
Spring Security: 4.0.1
Spring Security OAuth:2.0.7
To make explaining this easier I have enabled GET on the token endpoint
#Override
public void configure
(AuthorizationServerEndpointsConfigurer endpoints) throws Exception
{
endpoints
.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.allowedTokenEndpointRequestMethods(HttpMethod.GET); //<-- Enable GET
}
The request that I am making is as follows:
http://localhost:8080/project/oauth/token?
client_id=testClient&
grant_type=password&
username=user&
password=password
The header includes an Authorization header that contains the encoded version of:
Username: user
Password: password
The exception I get is:
HTTP Status 500 - Request processing failed; nested exception is
org.springframework.security.oauth2.provider.NoSuchClientException:
No client with requested id: user
From the exception description it appears that OAuth is looking in the ClientDetailsService for the client: user. However user is a user credential. I am obviously missunderstanding something about the configuration.
My configuration is as follows;
ServletInitializer.java
public class ServletInitializer extends AbstractDispatcherServletInitializer {
#Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.scan(ClassUtils.getPackageName(getClass()));
return context;
}
#Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
#Override
protected WebApplicationContext createRootApplicationContext() {
return null;
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException{
super.onStartup(servletContext);
DelegatingFilterProxy filter = new DelegatingFilterProxy("springSecurityFilterChain");
filter.setContextAttribute("org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcher");
servletContext.addFilter("springSecurityFilterChain", filter).addMappingForUrlPatterns(null, false, "/*");
}
}
WebMvcConfig.java
#Configuration
#EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
SecurityConfiguration.java
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception{
auth.
inMemoryAuthentication()
.withUser("user")
.password("password")
.roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception{
http
.authorizeRequests()
.antMatchers("/Services/*")
.authenticated()
.and()
.httpBasic();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
OAuth2ServerConfig.java
#Configuration
public class OAuth2ServerConfig {
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter{
#Autowired
private TokenStore tokenStore;
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception{
clients
.inMemory()
.withClient("testClient")
.secret("secret")
.scopes("read", "write")
.authorities("ROLE_CLIENT")
.authorizedGrantTypes("password", "refresh_token")
.accessTokenValiditySeconds(60)
.refreshTokenValiditySeconds(3600);
}
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception{
endpoints
.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.allowedTokenEndpointRequestMethods(HttpMethod.GET);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
}
}
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources){
resources.resourceId("SomeResourseId").stateless(false);
}
#Override
public void configure(HttpSecurity http) throws Exception{
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.authorizeRequests()
.antMatchers("/secure/**").access("#oauth2.hasScope('read')");
}
}
}
Code in gitrepo for ease of access: https://github.com/dooffas/springOauth2
I'm not sure where the 500 comes from in your case. I see a 406 because there is no JSON converter for the access token (Spring used to register one by default for Jackson 1.* but now it only does it for Jackson 2.*). You token endpoint works for me if I add jackson-databind to the classpath, e.g.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.4.4</version>
<scope>runtime</scope>
</dependency>
This works for me:
$ curl -v testClient:secret#localhost:8080/oauth/token?'grant_type=password&username=user&password=password'
P.S. you really ought not to use GET for a token request.
You have defined different authorities
try this:
#Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception{
auth.
inMemoryAuthentication()
.withUser("user")
.password("password")
.roles("USER", "CLIENT");
}
And add param grant_type=password to your request

Categories