SSO with keyclock Spring boot and JWT - spring-boot

Background:
Recently I was playing around with Oauth2 and Single Sign-on features. I have successfully implemented a POC with Spring-Boot, Keycloak. I can be found here: https://github.com/rivu007/sso-springboot-keyclock
This a REST service and I am using keycloak as OAuth2 server to secured protected endpoints. The setup works great with access token (clientId and secret)
Complication
I would love to extend the POC with JWT. Keyclock provides signed JWT as client authenticator. I have generated the keys and certificate (keystore.jks) and updated the application.yml file accordingly:
keycloak:
auth-server-url: http://localhost:18080/auth
realm: sso
resource: product-app
credentials:
jwt:
client-keystore-file: "src/main/resources/keystore.jks"
client-keystore-type: "jks"
client-keystore-password: "storepw"
client-key-password: "keypw"
client-key-alias: "product-app"
token-expiration: 10
confidential-port: 0
use-resource-role-mappings: true
ssl-required: none
And SecurityConfiguration.java looks like this:
#Configuration
#EnableWebSecurity
#ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
#EnableGlobalMethodSecurity(prePostEnabled = true)
#KeycloakConfiguration
public class SecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {
#Autowired
public KeycloakClientRequestFactory keycloakClientRequestFactory;
#Autowired
private UserDetailsService userDetailsService;
/**
* Registers the KeycloakAuthenticationProvider with the authentication manager.
*/
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public KeycloakRestTemplate keycloakRestTemplate() {
return new KeycloakRestTemplate(keycloakClientRequestFactory);
}
/**
* Defines the session authentication strategy.
*/
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
#Bean
public KeycloakConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
super.configure(http);
http.csrf().disable()
.authorizeRequests()
// Allow anonymous access to "/" path
.antMatchers("/").permitAll()
.antMatchers("/user/protected*").authenticated()
.anyRequest().permitAll().and()
// Custom filter for logging in users at "/login"
.addFilterBefore(new JWTLoginFilter("/login", authenticationManager()), UsernamePasswordAuthenticationFilter.class)
// Custom filter for authenticating users using tokens
.addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
// Disable resource caching
.headers().cacheControl();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
//auth.userDetailsService(userDetailsService()).passwordEncoder(new BCryptPasswordEncoder());
}
}
This is throwing exception the following exception:
2018-03-06 23:35:54.766 ERROR 60780 --- [nio-8080-exec-5] o.k.a.rotation.AdapterRSATokenVerifier : Didn't find publicKey for kid: w3Qn-XFRI_1uGaLs8_bJxo_hYmHSs1_-EilRV7y98G4
2018-03-06 23:35:54.778 ERROR 60780 --- [nio-8080-exec-5] o.k.a.BearerTokenRequestAuthenticator : Failed to verify token
Any idea what am I doing wrong?

Related

Keycloak won't work with spring boot in browser, only in postman, java.lang.NoSuchMethodError: 'org.keycloak.TokenVerifier.audience(java.lang.String)

I am trying to authenticate with Keycloak, it does work in postman, but when I try to access the endpoints in the browser, after the login it redirects me back to the login page, and in the logs I see
Servlet.service() for servlet [dispatcherServlet] in context with path [/test-portal] threw exception [Filter execution threw an exception] with root cause
"java.lang.NoSuchMethodError: 'org.keycloak.TokenVerifier org.keycloak.TokenVerifier.audience(java.lang.String)'
How can I get to access my endpoints through the browser too? Where should I create this 'audience' method?
#KeycloakConfiguration
#EnableWebSecurity
#Order(1)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
private final KeycloakLogoutHandler keycloakLogoutHandler;
public SecurityConfig(KeycloakLogoutHandler keycloakLogoutHandler) {
this.keycloakLogoutHandler = keycloakLogoutHandler;
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http); //lasa-l pt postman
http.authorizeRequests().antMatchers("/infoboard/*").permitAll().and()
.authorizeRequests().anyRequest().authenticated();
http.oauth2Login()
.and()
.logout()
.addLogoutHandler(keycloakLogoutHandler)
.logoutSuccessUrl("/");
}
}
#Keycloak Configuration
keycloak.auth-server-url=https://keycloak.fh-kufstein.ac.at:8443/
keycloak.realm=BigOpenRealm
keycloak.resource=PortalMicroservices
keycloak.public-client=false
keycloak.principal-attribute=preferred_username
#keycloak.use-resource-role-mappings=false
keycloak.credentials.secret=a7VJbtIFRipKZJ78Ex9uIzOv1hikNTWx
spring.security.oauth2.client.registration.keycloak.client-id=PortalMicroservices
spring.security.oauth2.client.registration.keycloak.client-secret=a7VJbtIFRipKZJ78Ex9uIzOv1hikNTWx
spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.keycloak.scope=openid
spring.security.oauth2.client.provider.keycloak.issuer-uri=https://keycloak.fh-kufstein.ac.at:8443/realms/BigOpenRealm
spring.security.oauth2.client.provider.keycloak.user-name-attribute=preferred_username

keycloak Spring Security : Unable to login with Bearer token as null

I have integrated the Keylock with Spring boot using #KeycloakConfiguration in SecurityConfig Class,
#KeycloakConfiguration
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
// configureGlobal() tasks the SimpleAuthorityMapper to make sure roles are not
// prefixed with ROLE_.
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
// keycloakConfigResolver defines that we want to use the Spring Boot properties
// file support instead of the default keycloak.json.
#Bean
public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
// we are permitting all here but we are gonna have method level
// pre-authorization
#Override protected void configure(HttpSecurity http) throws Exception {
super.configure(http); http.cors().and().csrf().disable()
.authorizeRequests().antMatchers("/admin**").hasAnyRole("admin")
.anyRequest().permitAll();
}
// we configure to accepts CORS requests from all and any domains
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*").allowedMethods("GET", "POST", "PUT", "DELETE");
}
};
}
#PostMapping("/login")
public #ResponseBody AuthToken login(#RequestBody LoginForm form) {
Collection<SimpleGrantedAuthority> authorities =
(Collection<SimpleGrantedAuthority>) SecurityContextHolder
.getContext().getAuthentication().getAuthorities();
AuthToken authToken = authService.login(form);
authToken.setAuthorities(authorities);
return authToken;
}
and I am able to log in without a Bearer token and with an empty Bearer token.
I have created a login page in angular,
and from that, I am passing the bearer token is null.
I am getting
status": 401,
“error”: “Unauthorized”
and there are no security logs on eclipse.
Thanks and Regards
Put this into SecurityConfig
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
// All of Spring Security will ignore the requests
.antMatchers("/login");
}

Spring Security does not reject requests when missing HTTP basic authentication header

I'm trying to setup a simple HTTP basic authentication mechanism for accessing REST endpoints in an application.
Basically, all endpoints starting with /api/internal shall be secured with HTTP basic authentication, while further configurations shall secure other paths with e.g. OAuth2.
The problem is that, for example, a GET request to /api/internal/test is allowed even when the client does not provide any credentials in the request header.
This is my current security configuration class:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Order(1)
#Configuration
#EnableWebSecurity
public static class InternalApiSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
final PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
auth
.inMemoryAuthentication()
.passwordEncoder(passwordEncoder)
.withUser("user")
.password(passwordEncoder.encode("password"))
.roles("USER");
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.mvcMatcher("/api/internal/**")
.authorizeRequests().anyRequest().authenticated()
.and()
.httpBasic();
}
}
// Other security configuration follow here...
}
After having spent some more time on this problem, I found that the authentication works when adding the following to the chain:
sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

It does not have HTTP ok status - Spring boot keycloak version 4.0.0 final behind haproxy

I'm having an issue with spring boot application authenticated using keycloak, I have this application sitting behind a haproxy and have tried to completely disable cors on the spring app and manage this on the proxy side, however Im still having issues with cors.
"It does not have HTTP ok status"
Note I'm using an older version of the spring boot keycloak plugin due to the original application using spring boot version 1.5.10
Please find attached some of the configuration options I have explored:
Case no 1: Disable cors on spring app -------------------------------------------------
This setup returns http status ok not present on response preflight header
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
public SecurityWithoutCsrfConfig() {
super();
}
// Submits the KeycloakAuthenticationProvider to the AuthenticationManager
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Bean
public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
// Specifies the session authentication strategy
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
#Override
public void configure(final WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**",
"/v2/api-docs",
"/swagger-ui.html",
"/swagger2-ui.html",
"/springfox/**",
"/v2/swagger.json",
"/_twilio/**",
"/webjars/**",
"/configuration/**",
"/swagger-resources/**");
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
super.configure(http);
http.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/mypath/**").hasAnyRole("USER")
.antMatchers("/mypath_admin/**").hasAnyRole("USER_ADMIN", "ADMIN")
.anyRequest().fullyAuthenticated()
.and().httpBasic().and().cors().disable();
http.headers().cacheControl();
}
Case no 2 : set the headers on spring app side -----------------------------------------------
SecurityConfig
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
public SecurityWithoutCsrfConfig() {
super();
}
// Submits the KeycloakAuthenticationProvider to the AuthenticationManager
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Bean
public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
// Specifies the session authentication strategy
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
#Override
public void configure(final WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**",
"/v2/api-docs",
"/swagger-ui.html",
"/swagger2-ui.html",
"/springfox/**",
"/v2/swagger.json",
"/_twilio/**",
"/webjars/**",
"/configuration/**",
"/swagger-resources/**");
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
super.configure(http);
http.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/mypath/**").hasAnyRole("USER")
.antMatchers("/mypath_admin/**").hasAnyRole("USER_ADMIN", "ADMIN")
.anyRequest().fullyAuthenticated()
.and().httpBasic();
http.headers().cacheControl();
}
WebConfig
public class WebConfig extends WebMvcConfigurerAdapter {
/**
* Adds Cross Origin Resource Sharing filter
* #return CorsFilter
*/
#Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
For case 2 i encounter problems with the allowed origin headers. My haproxy config is standard setup you would find on any of the official haproxy site.
Any solutions or suggestions to the above is much appreciated...
Preferably a solution to the http status on the preflight response.
To debug the situation fuuther I added a logger to the configure method in my securityconfig class, as this is where I suspected the requests were breaking down
LOG.info(" --executing: configure(HttpSecurity)");
The factI had a logger in this class now meant that more config details would be printed to the output of the application, so I built the jar, copied it to the server and ran my jar file. When I reloaded the request that originally gave me the cors error (http okay status not present on pre-flight) the logs printed out an SSL handshake exception, this told me that somewhere in my app configuration it was set to only expect connections via https or a secure port. However this was not the case, I was using haproxy exposed on a secure port, forwarding the connections to the port the jar was listening on (8080).
So I double checked all of the configs for security throughout the app, and in my application properties I discovered I had the wrong setting for
keycloak.ssl-required=all
So I removed this setting, deployed the app and voila, issue averted. Hope this comes in as a help to anyone who has similar issues.

OAuth2 + Spring Boot 2 - ResourceServer in ZuulGateway with Authorization Server

I am trying OAuth 2 + JWT integration using Spring Boot 2 + Netflix OSS. On requesting access token, I am getting the below error in Zuul Gateway which acts as a Resource Server.
2019-05-04 14:41:29.157 DEBUG 23272 --- [nio-8765-exec-2] o.s.s.w.a.ExceptionTranslationFilter : Authentication exception occurred; redirecting to authentication entry point
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
Can someone help and tell me what am I missing in the code ?
Zuul Gateway + Resource Server
#Configuration
#EnableResourceServer
#Order(value = 0)
#EnableOAuth2Sso
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/oauth/token/**").permitAll()
//.antMatchers("/login/**,/oauth/**").permitAll()
.antMatchers("/trips/**").hasAnyRole("CLIENT", "USER", "ANONYMOUS")
.and().csrf().disable()
.anonymous().disable();
}
}
application.yml
logging:
level:
org.springframework: DEBUG
server:
port: 8765
spring:
application:
name: gateway
# Map path to auth service
zuul:
routes:
trips:
path: /trips/**
url: http://localhost:1000/api/trips
rides:
path: /rides/**
url: http://localhost:1000/api/rides
mauth:
path: /oauth/**
url: http://localhost:1000/oauth
#OAuth Configurations
security:
oauth2:
client:
#access-token-uri: https://auth/login
#user-authorization-uri: /auth/oauth/authorize
accessTokenUri: http://localhost:1000/oauth/authorize
userAuthorizationUri: http://localhost:1000/oauth/token
client-id: sapepool
client-secret: sapepool
resource:
jwt:
key-uri: http://localhost:1000/oauth/token_key
#key-value:
Authorization Server
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Autowired
#Qualifier("customUserDetailsService")
private UserDetailsService userDetailsService;
/**
* Token store.
*
* #return the token store
*/
/*#Bean
public DatastoreTokenStore tokenStore() {
return new DatastoreTokenStore(datastoreDataSource);
}*/
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
#Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setKeyPair(new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "password".toCharArray()).getKeyPair("jwt"));
converter.setSigningKey("123");
converter.setVerifierKey("123");
return converter;
}
/**
*
*/
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager).userDetailsService(userDetailsService)
.tokenStore(tokenStore())//.tokenServices(tokenServices())
.tokenEnhancer(jwtAccessTokenConverter())
.accessTokenConverter(jwtAccessTokenConverter())
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
}
/**
* Authorization server security Configuration.
*
* #param oauthServer
* the oauth server
* #throws Exception
* the exception
*/
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("sapepool")
.secret("{noop}sapepool")
//.secret("sapepool")
.authorizedGrantTypes("client_credentials", "password","refresh_token")
.authorities("ROLE_CLIENT", "ROLE_ANDROID_CLIENT", "ROLE_ANONYMOUS")
.scopes("read", "write", "trust")
.accessTokenValiditySeconds(5000)
.refreshTokenValiditySeconds(50000);
//.resourceIds("oauth2-resource") - isAutoApprove()
}
}
Authorization Server - Web Security Config
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static Logger log = LoggerFactory.getLogger(WebSecurityConfig.class);
#Autowired
private UserDetailsService userDetailsService;
#Override
#Bean
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/oauth/token/**").permitAll()
.anyRequest().permitAll()
//.antMatchers("*/oauth/**").permitAll()
//.antMatchers("/**").permitAll()
.and().csrf().disable();
//.anonymous().disable();
/*
* http.csrf().disable().exceptionHandling() //.authenticationEntryPoint( //
* (request, response, authException) ->
* response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
* .and().authorizeRequests().antMatchers("/**").authenticated().and().httpBasic
* ();
*/
}
#Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
}

Resources