Reputation: 295
I'm trying to access a Spring App via Keycloak, but I always get a 401 Unauthorized error. Basically I have a chat module that works fine on its own, but once I add Keycloak I'm unable to access the app due to that 401 error. I've followed about 3 tutorials that showed similar things to what I've done, and I still have no idea what I've done wrong.
Here's my app's config :
keycloak:
enabled: true
realm: myReal
resource: myReal-api
public-client: true
bearer-only: true
auth-server-url: http://localhost:8080/auth
credentials:
secret: 82eXXXXX-3XXX-4XXX-XXX7-287aXXXXXXXX
principal-attribute: preferred_username
cors: true
From localhost:port/
I have a first interface (with no Keycloak safety) that has a link to my service, which is localhost:port/index/{topicName}
. Now when I click on that link, I'm supposed to get the Keycloak authentication screen, but I get a 401 error instead.
I've checked the header of my request, adding a HttpServletRequest as a parameter to my displayMessage
method, and I actually could display the access_token and the X-Auth-Token in my IDE's console. But it seems like when I follow that link, it sends the request without the token.
Here are my controller methods (my Controller class is annotated with @Controller
:
@GetMapping(path = "/")
public String index() {
return "external";
}
@GetMapping(path = "/index/{topicName}",
produces = MediaType.APPLICATION_JSON_VALUE)
public String displayMessages(Model model,
@PathVariable String topicName) {
//HttpHeaders headers = new HttpHeaders();
//headers.set("Authorization", request.getHeader("Authorization"));
//header = request.getHeader("Authorization");
//System.out.println(" T O K E N "+request.getHeader("X-Auth-Token"));
projectServiceImpl.findByName(topicName);
List<Message> messages = messageServiceImpl.findAllMessagesByProjectName(topicName);
model.addAttribute("topic", topicName);
model.addAttribute("message",messages);
return "index";
}
My Keycloak config file is inspired from the tuto's I've read, so there might be a mistake in there that I don't know about (not sure what the difference between methods access
and hasRole
is) :
@Configuration
@ComponentScan(
basePackageClasses = KeycloakSecurityComponents.class,
excludeFilters = @ComponentScan.Filter(
type = FilterType.REGEX,
pattern = "org.keycloak.adapters.springsecurity.management.HttpSessionManager"))
@EnableWebSecurity
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
@Bean
public HttpSessionIdResolver httpSessionIdResolver() { //replace HttpSessionStrategy
return HeaderHttpSessionIdResolver.xAuthToken();
}
//Registers the KeycloakAuthenticationProvider with the authentication manager.
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
try {
SimpleAuthorityMapper grantedAuthorityMapper = new SimpleAuthorityMapper();
grantedAuthorityMapper.setPrefix("ROLE_");
grantedAuthorityMapper.setConvertToUpperCase(true);
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(grantedAuthorityMapper);
auth.authenticationProvider(keycloakAuthenticationProvider());
} catch(Exception ex) {
logger.error("SecurityConfig.configureGlobal: " + ex);
}
/*try {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}catch(Exception ex){
logger.error("SecurityConfig.configureGlobal: " +ex);
}*/
}
//Load Keycloak properties from service config-file
@Bean
public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
//Defines the session authentication strategy.
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
//Public or Confidential application keycloak/OpenID Connect client
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
//Bearer mode only keycloak/OpenID Connect client without keycloak session -> stateless behavior
//return new NullAuthenticatedSessionStrategy();
}
@Override
protected void configure(HttpSecurity http) throws Exception
{
super.configure(http);
http.authorizeRequests()
//BEGIN
//USER -done to be tested
.antMatchers(HttpMethod.GET,"/index**").access("hasAuthority('ADMIN')")
.antMatchers(HttpMethod.GET,"/").access("hasAuthority('ADMIN')")
.antMatchers(HttpMethod.GET,"/").access("hasAnyAuthority('ADMIN','MANAGER','EXPERT','STANDARD')")
.anyRequest().authenticated()
.and()
.cors()
.and()
.csrf().disable()
//BEGIN Login/Logout
.formLogin()
.permitAll()//.successHandler(authenticationSuccessHandler) //
.and()
.logout()//.clearAuthentication(true) //Add .clearAuthentication(true) to logout()
//.logoutUrl("/custom-logout")
.addLogoutHandler(keycloakLogoutHandler())
//.addLogoutHandler(new LogoutHandlerImpl())
.clearAuthentication(true)
.invalidateHttpSession(true)
.permitAll();
//END Login/Logout
//BEGIN Session
http
.sessionManagement()
//.sessionCreationPolicy(SessionCreationPolicy.ALWAYS) //BY default IF_REQUIRED
.maximumSessions(1)
.maxSessionsPreventsLogin(false) // if true generate an error when user login after reaching maximumSession (SessionAuthenticationStrategy rejected the authentication object / SessionAuthenticationException: Maximum sessions of 1 for this principal exceeded)
//.expiredUrl("/auth/login")
.sessionRegistry(sessionRegistry());
}
@Bean
@Scope(scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public AccessToken accessToken() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
return ((KeycloakSecurityContext) ((KeycloakAuthenticationToken) request.getUserPrincipal()).getCredentials()).getToken();
}
///BEGIN session
@Bean
public SessionRegistry sessionRegistry() {
SessionRegistry sessionRegistry = new SessionRegistryImpl();
return sessionRegistry;
}
@Bean
public RegisterSessionAuthenticationStrategy registerSessionAuthStr( ) {
return new RegisterSessionAuthenticationStrategy( sessionRegistry( ) );
}
// Register HttpSessionEventPublisher
@Bean
public static ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
return new ServletListenerRegistrationBean<HttpSessionEventPublisher>(new HttpSessionEventPublisher());
}
I don't really know what else I should change to make it work, but I believe there has to be something wrong in there. But I think if I can have the Keycloak authentication screen when trying to access my service, this would be alright.
Upvotes: 4
Views: 16354
Reputation: 147
This solved my issue. I was using within a docker container and had to match both to host.docker.internal
Upvotes: 3
Reputation: 4756
I received the same error, one thing to double check is that auth-server-url
is the same for the server, and the client getting the token.
I.e. if one is the dns name, and one is the IP address, it will not authorize. (in my case I had localhost and 127.0.0.1 so authorization failed)
Server, src/main/resources/application.yml
Postman/client:
Upvotes: 7