Keycloak spring-boot spring-security spring-security-oauth2
Schema: EurekaServer - Spring Cloud Gateway - Microservices
Hello,
I am doing a project using springBoot 2.7.11 and Keycloak 18.0.0 for security and role management, spring cloud version 2021.0.1, keycloak-adapter-bom 21.1.1. However, currently testing the services by going through the Gateway I am getting an Unauthorized 401 Error.
Requests: Keycloack - post - http://localhost/realms/EcommerceRealm/protocol/openid-connect/token
Service from gateway- get - http://localhost:8765/api/articles/prot/search/listArticles
Below I add the code:
application
eureka:
instance:
instance-id: ${server.port}-${spring.application.name}
hostname: localhost
keycloak:
auth-server-url: http://localhost:8080
bearer-only: true
confidential-port: 0
cors: true
principal-attribute: preferred_username
realm: EcommerceRealm
resource: api-services
ssl-required: none
use-resource-role-mappings: false
management:
endpoints:
web:
exposure:
include: '*'
server:
port: 8765
spring:
h2:
console:
enabled: true
jpa:
hibernate:
ddl-auto: validate
main:
allow-bean-definition-overriding: true
banner-mode: console
web-application-type: reactive
application:
name: tsaTechAPIGetaway
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
routes:
- id: articleModule
order: 0
predicates:
- Path=/api/articles/**
uri: lb://tsatechArticlesService
- id: articleProt
order: 1
predicates:
- Path=/api/articles/prot/**
uri: lb://tsaTechArticlesServiceProt
# ==============================================================
# = Impostazioni Client
# ==============================================================
client:
registerWithEureka: true
fetchRegistry: true
serviceUrl:
defaultZone: ${EUREKA_URL:http://localhost:8761/eureka/}
#time out di connessione in secondi al server Eureka (def 5 sec)
eureka-server-connect-timeout-seconds: 8
#==============================================================
# = Logs Parameters
# ==============================================================
logging:
level:
'[org.springframework.cloud]': DEBUG
'[org.springframework.security]': DEBUG
ApiGetawayApplication
@SpringBootApplication
@EnableEurekaClient
public class ApiGetawayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGetawayApplication.class, args);
}
}
KeycloakSecurityConfig
@KeycloakConfiguration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
private final KeycloakClientRequestFactory keycloakClientRequestFactory;
public KeycloakSecurityConfig(KeycloakClientRequestFactory keycloakClientRequestFactory) {
this.keycloakClientRequestFactory = keycloakClientRequestFactory;
// to use principal and authentication together with @async
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
}
/**
* If you don't want to use the keycloak.json file, then uncomment this bean.
*/
/**
* Use properties in application.properties instead of keycloak.json
*/
@Bean
@Primary
public KeycloakConfigResolver keycloakConfigResolver(KeycloakSpringBootProperties properties) {
return new CustomKeycloakSpringBootConfigResolver(properties);
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public KeycloakRestTemplate keycloakRestTemplate() {
return new KeycloakRestTemplate(keycloakClientRequestFactory);
}
public SimpleAuthorityMapper grantedAuthority() {
SimpleAuthorityMapper mapper = new SimpleAuthorityMapper();
mapper.setConvertToUpperCase(true);
return mapper;
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(grantedAuthority());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
/**
* Use NullAuthenticatedSessionStrategy for bearer-only tokens. Otherwise, use
* RegisterSessionAuthenticationStrategy.
*/
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
/**
* Secure appropriate endpoints
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.headers().frameOptions().sameOrigin();
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry expressionInterceptUrlRegistry = http.cors() //
.and() //
.csrf().disable() //
// .anonymous().disable() //
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) //
.and() //
.authorizeRequests();
expressionInterceptUrlRegistry = expressionInterceptUrlRegistry.antMatchers("/api/articles/prot/**").hasRole("ADMIN");
expressionInterceptUrlRegistry = expressionInterceptUrlRegistry.antMatchers("/api/articles/**").hasRole("USER");
expressionInterceptUrlRegistry.anyRequest().permitAll();
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean(KeycloakAuthenticationProcessingFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
public FilterRegistrationBean keycloakPreAuthActionsFilterRegistrationBean(KeycloakPreAuthActionsFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
public FilterRegistrationBean keycloakAuthenticatedActionsFilterBean(KeycloakAuthenticatedActionsFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
public FilterRegistrationBean keycloakSecurityContextRequestFilterBean(KeycloakSecurityContextRequestFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
@Bean
@Override
@ConditionalOnMissingBean(HttpSessionManager.class)
protected HttpSessionManager httpSessionManager() {
return new HttpSessionManager();
}
@Bean
public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
return new ServletListenerRegistrationBean<>(new HttpSessionEventPublisher());
}
}
CustomKeycloakSpringBootConfigResolver
@Configuration
public class CustomKeycloakSpringBootConfigResolver extends KeycloakSpringBootConfigResolver {
private final KeycloakDeployment keycloakDeployment;
public CustomKeycloakSpringBootConfigResolver(KeycloakSpringBootProperties properties) {
keycloakDeployment = KeycloakDeploymentBuilder.build(properties);
}
@Override
public KeycloakDeployment resolve(HttpFacade.Request facade) {
return keycloakDeployment;
}
}
The above code refers to the Gateway project.
Below I add the code for the service to be invoked through the gateway:
application
#==========================================================
#= Article Web Service - Base Version
#==========================================================
server:
port: 5052
spring:
application:
name: tsaTechProtArticlesService
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost:8080/realms/EcommerceRealm
management:
endpoints:
web:
exposure:
include: '*'
#==========================================================
#= PARAMETER DBMS MySQL
#==========================================================
sql:
init:
mode: always
platform: mysql
datasource:
password: ********
url: jdbc:mysql://localhost:3306/DBName
username: root
session:
jdbc:
initialize-schema: always
jpa:
hibernate:
ddl-auto: update
generate-ddl: true
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL5Dialect
show-sql: true
# ==============================================================
# = Eureka Properties
# ==============================================================
eureka:
client:
registerWithEureka: true
fetchRegistry: true
serviceUrl:
defaultZone: ${EUREKA_URL:http://localhost:8761/eureka/}
#==============================================================
# = Logs Parameters
# ==============================================================
logging:
level:
'[org.springframework.cloud]': DEBUG
'[org.springframework.security]': DEBUG
SecurityConfig
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity security) throws Exception {
security.authorizeRequests(authorize -> authorize.anyRequest().authenticated())
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
}
}
ArticlesProtServiceApplication
@SpringBootApplication
@EnableEurekaClient
public class ArticlesProtServiceApplication {
@GetMapping("/")
String home() {
return "Spring is here!";
}
public static void main(String[] args) {
SpringApplication.run(ArticlesProtServiceApplication.class, args);
}
}
Testing everything via Postman, then calling keycloak to get the token and subsequently invoking the service via OAUTH2 adding the token, I always get the 401 unauthorized error. I hope someone can help me find a solution. Thank you
LOG ERROR: 2023-05-31 16:06:52.716 DEBUG 74219 --- [nio-8765-exec-6] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : Trying to match using PathMatcherServerWebExchangeMatcher{pattern='/login', method=POST} 2023-05-31 16:06:52.716 DEBUG 74219 --- [nio-8765-exec-6] athPatternParserServerWebExchangeMatcher : Request 'GET /api/articles/search/listArticles' doesn't match 'POST /login' 2023-05-31 16:06:52.716 DEBUG 74219 --- [nio-8765-exec-6] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : No matches found 2023-05-31 16:06:52.717 DEBUG 74219 --- [nio-8765-exec-6] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : Trying to match using PathMatcherServerWebExchangeMatcher{pattern='/login', method=GET} 2023-05-31 16:06:52.717 DEBUG 74219 --- [nio-8765-exec-6] athPatternParserServerWebExchangeMatcher : Request 'GET /api/articles/search/listArticles' doesn't match 'GET /login' 2023-05-31 16:06:52.717 DEBUG 74219 --- [nio-8765-exec-6] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : No matches found 2023-05-31 16:06:52.717 DEBUG 74219 --- [nio-8765-exec-6] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : Trying to match using PathMatcherServerWebExchangeMatcher{pattern='/logout', method=GET} 2023-05-31 16:06:52.717 DEBUG 74219 --- [nio-8765-exec-6] athPatternParserServerWebExchangeMatcher : Request 'GET /api/articles/search/listArticles' doesn't match 'GET /logout' 2023-05-31 16:06:52.717 DEBUG 74219 --- [nio-8765-exec-6] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : No matches found 2023-05-31 16:06:52.719 DEBUG 74219 --- [ parallel-2] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : Trying to match using PathMatcherServerWebExchangeMatcher{pattern='/logout', method=POST} 2023-05-31 16:06:52.720 DEBUG 74219 --- [ parallel-2] athPatternParserServerWebExchangeMatcher : Request 'GET /api/articles/search/listArticles' doesn't match 'POST /logout' 2023-05-31 16:06:52.720 DEBUG 74219 --- [ parallel-2] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : No matches found 2023-05-31 16:06:52.720 DEBUG 74219 --- [ parallel-2] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : Trying to match using EndpointRequestMatcher includes=[health], excludes=[], includeLinks=false 2023-05-31 16:06:52.720 DEBUG 74219 --- [ parallel-2] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : Trying to match using PathMatcherServerWebExchangeMatcher{pattern='/actuator/health/', method=null} 2023-05-31 16:06:52.720 DEBUG 74219 --- [ parallel-2] athPatternParserServerWebExchangeMatcher : Request 'GET /api/articles/search/listArticles' doesn't match 'null /actuator/health/' 2023-05-31 16:06:52.720 DEBUG 74219 --- [ parallel-2] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : No matches found 2023-05-31 16:06:52.721 DEBUG 74219 --- [ parallel-2] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : No matches found 2023-05-31 16:06:52.721 DEBUG 74219 --- [ parallel-2] a.DelegatingReactiveAuthorizationManager : Checking authorization on '/api/articles/search/listArticles' using org.springframework.security.authorization.AuthenticatedReactiveAuthorizationManager@5392b123 2023-05-31 16:06:52.721 DEBUG 74219 --- [ parallel-2] ebSessionServerSecurityContextRepository : No SecurityContext found in WebSession: 'org.springframework.web.server.session.InMemoryWebSessionStore$InMemoryWebSession@585916dd' 2023-05-31 16:06:52.721 DEBUG 74219 --- [ parallel-2] o.s.s.w.s.a.AuthorizationWebFilter : Authorization failed: Access Denied 2023-05-31 16:06:52.721 DEBUG 74219 --- [ parallel-2] ebSessionServerSecurityContextRepository : No SecurityContext found in WebSession: 'org.springframework.web.server.session.InMemoryWebSessionStore$InMemoryWebSession@585916dd' 2023-05-31 16:06:52.721 DEBUG 74219 --- [ parallel-2] DelegatingServerAuthenticationEntryPoint : Trying to match using MediaTypeRequestMatcher [matchingMediaTypes=[text/html], useEquals=false, ignoredMediaTypes=[/]] 2023-05-31 16:06:52.722 DEBUG 74219 --- [ parallel-2] .s.u.m.MediaTypeServerWebExchangeMatcher : httpRequestMediaTypes=[/] 2023-05-31 16:06:52.722 DEBUG 74219 --- [ parallel-2] .s.u.m.MediaTypeServerWebExchangeMatcher : Processing / 2023-05-31 16:06:52.722 DEBUG 74219 --- [ parallel-2] .s.u.m.MediaTypeServerWebExchangeMatcher : Ignoring 2023-05-31 16:06:52.722 DEBUG 74219 --- [ parallel-2] .s.u.m.MediaTypeServerWebExchangeMatcher : Did not match any media types 2023-05-31 16:06:52.722 DEBUG 74219 --- [ parallel-2] DelegatingServerAuthenticationEntryPoint : Trying to match using OrServerWebExchangeMatcher{matchers=[org.springframework.security.config.web.server.ServerHttpSecurity$HttpBasicSpec$$Lambda$1003/0x00000008007dd040@4ad21437, AndServerWebExchangeMatcher{matchers=[NegatedServerWebExchangeMatcher{matcher=MediaTypeRequestMatcher [matchingMediaTypes=[text/html], useEquals=false, ignoredMediaTypes=[]]}, MediaTypeRequestMatcher [matchingMediaTypes=[application/atom+xml, application/x-www-form-urlencoded, application/json, application/octet-stream, application/xml, multipart/form-data, text/xml], useEquals=false, ignoredMediaTypes=[/]]]}]} 2023-05-31 16:06:52.722 DEBUG 74219 --- [ parallel-2] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : Trying to match using org.springframework.security.config.web.server.ServerHttpSecurity$HttpBasicSpec$$Lambda$1003/0x00000008007dd040@4ad21437 2023-05-31 16:06:52.722 DEBUG 74219 --- [ parallel-2] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : Trying to match using AndServerWebExchangeMatcher{matchers=[NegatedServerWebExchangeMatcher{matcher=MediaTypeRequestMatcher [matchingMediaTypes=[text/html], useEquals=false, ignoredMediaTypes=[]]}, MediaTypeRequestMatcher [matchingMediaTypes=[application/atom+xml, application/x-www-form-urlencoded, application/json, application/octet-stream, application/xml, multipart/form-data, text/xml], useEquals=false, ignoredMediaTypes=[/]]]} 2023-05-31 16:06:52.722 DEBUG 74219 --- [ parallel-2] .s.s.w.s.u.m.AndServerWebExchangeMatcher : Trying to match using NegatedServerWebExchangeMatcher{matcher=MediaTypeRequestMatcher [matchingMediaTypes=[text/html], useEquals=false, ignoredMediaTypes=[]]} 2023-05-31 16:06:52.722 DEBUG 74219 --- [ parallel-2] .s.u.m.MediaTypeServerWebExchangeMatcher : httpRequestMediaTypes=[/] 2023-05-31 16:06:52.722 DEBUG 74219 --- [ parallel-2] .s.u.m.MediaTypeServerWebExchangeMatcher : Processing / 2023-05-31 16:06:52.722 DEBUG 74219 --- [ parallel-2] .s.u.m.MediaTypeServerWebExchangeMatcher : text/html .isCompatibleWith / = true 2023-05-31 16:06:52.722 DEBUG 74219 --- [ parallel-2] .w.s.u.m.NegatedServerWebExchangeMatcher : matches = false 2023-05-31 16:06:52.723 DEBUG 74219 --- [ parallel-2] .s.s.w.s.u.m.AndServerWebExchangeMatcher : Did not match 2023-05-31 16:06:52.723 DEBUG 74219 --- [ parallel-2] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : No matches found 2023-05-31 16:06:52.723 DEBUG 74219 --- [ parallel-2] DelegatingServerAuthenticationEntryPoint : No match found. Using default entry point org.springframework.security.web.server.DelegatingServerAuthenticationEntryPoint@344b11cb 2023-05-31 16:06:52.723 DEBUG 74219 --- [ parallel-2] DelegatingServerAuthenticationEntryPoint : Trying to match using org.springframework.security.config.web.server.ServerHttpSecurity$HttpBasicSpec$$Lambda$1003/0x00000008007dd040@4ad21437 2023-05-31 16:06:52.723 DEBUG 74219 --- [ parallel-2] DelegatingServerAuthenticationEntryPoint : No match found. Using default entry point org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint@47d7881c