I have an API that is authenticated by x509. However I need my actuator health check end point to be bypassed from authentication, so that I can do health check without client certificates.
I realized this can be done by using different ports for server and management where actuator endpoints will hit.
My application.yml config is as below:
server:
port: 9000
ssl:
key-store: classpath:keystore.jks
key-store-password: password
key-password: password
enabled: true
trust-store: classpath:truststore.jks
trust-store-password: password
client-auth: need
management:
server:
port: 9010
ssl:
enabled: false
security:
enabled: false
My problem is that even with this configuration, management endpoint is not where I can access the health endpoint.
http://localhost:9010/health --> this doesn't work
I am still able to access it with the server port and certificates.
https://localhost:9000/health --> this works
My security configuration is as below :
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.x509()
.subjectPrincipalRegex("CN=(.*?)(?:,|$)")
.userDetailsService(userDetailsService())
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.NEVER)
.and()
.csrf().disable();
}
#Bean
public UserDetailsService userDetailsService() {
return new UserDetailsService() {
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (username.equals("user")) {
return new User(username, "", AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
}
throw new UsernameNotFoundException("User not found!");
}
};
}
I have the spring-boot-actuator dependency in my pom. I don't understand why my management port doesn't work.
Any suggestions?
I figured out what was wrong here. It was spring boot dependencies. I was using spring-boot-actuator, when I changed it to spring-boot-starter-actuator, the management port started listening to my configured port. I then disabled the SSL authentication on that port. Providing my config below so that it would be helpful for someone else:
management:
server:
port: 8000
ssl:
enabled: false
server:
port: 9000
ssl:
key-store: classpath:keystore.jks
key-store-password: password
key-alias: alias
key-password: password
enabled: true
trust-store: classpath:truststore.jks
trust-store-password: password
client-auth: need
Now I can access my actuator endpoints like health via http on port 8000 and my application rest APIs on port 9000 via https with valid client certificate.
Related
I have several microservices, that I only want to access them by zuul resources server and avoid direct access.
Zuul server implements jwt authentication.
I have these redirections in application.yaml file
zuul:
routes:
servicio-carga-electores:
path: /cargaElectores/**
service-id: servicio-carga-electores
url: ${server-uri}/cargaElectoresWS
servicio-oauth-server:
service-id: servicio-oauth-server
path: /oauth/**
url: ${server-uri}/celec-oauth-server
sensitive-headers:
- Cookie
- Set-Cookie
ribbon:
eureka:
enabled: false
Trying with postman, after getting jwt token, all redirections causes 403 forbidden.
http://localhost:8080/celec-zuul-server/cargaElectores/recupera_ficheros
{
"timestamp": "2022-08-24T08:00:12.806+0000",
"status": 403,
"error": "Forbidden",
"message": "Forbidden",
"path": "/cargaElectoresWS/recupera_ficheros"
}
I've seen in other places using hasIpAdress, but isn't working.
#Configuration
#EnableWebSecurity
public class ResourceServerConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/estado.jsp").permitAll()
.and()
.authorizeRequests()
.antMatchers("/**").hasIpAddress("::1");
}
}
If I don't use oauth2, the redirection goes right, but I can access directly to the microservices.
Thanks for advance.
José Pascual
I am using a setup with Keycloak as Identity Provider, Spring Cloud Gateway as API Gateway and multiple Microservices.
I can receive a JWT via my Gateway (redirecting to Keycloak) via http://localhost:8050/auth/realms/dev/protocol/openid-connect/token.
I can use the JWT to access a resource directly located at the Keycloak server (e.g. http://localhost:8080/auth/admin/realms/dev/users).
But when I want to use the Gateway to relay me to the same resource (http://localhost:8050/auth/admin/realms/dev/users) I get the Keycloak Login form as response.
My conclusion is that there must me a misconfiguration in my Spring Cloud Gateway application.
This is the Security Configuration in the Gateway:
#Configuration
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
public class SecurityConfiguration {
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, ReactiveClientRegistrationRepository clientRegistrationRepository) {
// Authenticate through configured OpenID Provider
http.oauth2Login();
// Also logout at the OpenID Connect provider
http.logout(logout -> logout.logoutSuccessHandler(
new OidcClientInitiatedServerLogoutSuccessHandler(clientRegistrationRepository)));
//Exclude /auth from authentication
http.authorizeExchange().pathMatchers("/auth/realms/ahearo/protocol/openid-connect/token").permitAll();
// Require authentication for all requests
http.authorizeExchange().anyExchange().authenticated();
// Allow showing /home within a frame
http.headers().frameOptions().mode(Mode.SAMEORIGIN);
// Disable CSRF in the gateway to prevent conflicts with proxied service CSRF
http.csrf().disable();
return http.build();
}
}
This is my application.yaml in the Gateway:
spring:
application:
name: gw-service
cloud:
gateway:
default-filters:
- TokenRelay
discovery:
locator:
lower-case-service-id: true
enabled: true
routes:
- id: auth
uri: http://localhost:8080
predicates:
- Path=/auth/**
security:
oauth2:
client:
registration:
keycloak:
client-id: 'api-gw'
client-secret: 'not-relevant-but-correct'
authorizationGrantType: authorization_code
redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}'
scope: openid,profile,email,resource.read
provider:
keycloak:
issuerUri: http://localhost:8080/auth/realms/dev
user-name-attribute: preferred_username
server:
port: 8050
eureka:
client:
service-url:
default-zone: http://localhost:8761/eureka
register-with-eureka: true
fetch-registry: true
How can I make the Gateway able to know that the user is authenticated (using the JWT) and not redirect me to the login page?
If you want to make requests to Spring Gateway with access token you need to make it a resource server. Add the following:
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
application.yml
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://.../auth/realms/...
SecurityConfiguration.java
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http,
ReactiveClientRegistrationRepository clientRegistrationRepository) {
// Authenticate through configured OpenID Provider
http.oauth2Login();
// Also logout at the OpenID Connect provider
http.logout(logout -> logout.logoutSuccessHandler(
new OidcClientInitiatedServerLogoutSuccessHandler(clientRegistrationRepository)));
// Require authentication for all requests
http.authorizeExchange().anyExchange().authenticated();
http.oauth2ResourceServer().jwt();
// Allow showing /home within a frame
http.headers().frameOptions().mode(Mode.SAMEORIGIN);
// Disable CSRF in the gateway to prevent conflicts with proxied service CSRF
http.csrf().disable();
return http.build();
}
I bypassed the problem by communicating directly with Keycloak without relaying requests to it via Spring Cloud Gateway.
That's actually not a workaround but actually best practice/totally ok as far as I understand.
This code is for Client_credentials grant_type. if you use other grant type you need to add client_id and client_secret in request parameters.
public class MyFilter2 extends OncePerRequestFilter {
private final ObjectMapper mapper = new ObjectMapper();
#Value("${auth.server.uri}")
private String authServerUri;
#Value("${client_id}")
private String clientId;
#Value("${client_secret}")
private String clientSecret;
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
FilterChain filterChain) throws IOException {
try {
String token = httpServletRequest.getHeader("Authorization");
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type","application/x-www-form-urlencoded");
headers.set("Authorization",token);
final HttpEntity finalRequest = new HttpEntity("{}", headers);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.postForEntity(authServerUri,finalRequest,String.class);
if (!HttpStatus.OK.equals(response.getStatusCode())) {
Map<String, Object> errorDetails = new HashMap<>();
errorDetails.put("status", HttpStatus.UNAUTHORIZED.value());
errorDetails.put("message", "Invalid or empty token");
httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
mapper.writeValue(httpServletResponse.getWriter(), errorDetails);
} else {
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}catch(HttpClientErrorException he) {
Map<String, Object> errorDetails = new HashMap<>();
errorDetails.put("status", HttpStatus.UNAUTHORIZED.value());
errorDetails.put("message", "Invalid or empty token");
httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
mapper.writeValue(httpServletResponse.getWriter(), errorDetails);
}catch (Exception exception) {
}
}
I am working on a microservice application with a gateway service using spring-cloud-gateway. I use Keycloak to handle authentication. I want to forward the user informations from keycloak to the other services but I have some trouble with Filters.
I think that I could add it to the query parameters but I don't know how. I also do not realy know how to get the user informations in my gateway microservice.
My application.yml :
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "*"
routes:
- id: id_1
uri: http://localhost:10102/
predicates:
- Path=/id_1/**
- id: id_2
uri: http://localhost:10103/
predicates:
- Path=/id_2/**
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost:8080/auth/realms/myrealm
My security config :
#Configuration
#EnableWebFluxSecurity
public class SecurityConfig {
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.authorizeExchange()
.anyExchange().authenticated()
.and()
.oauth2ResourceServer().jwt();
http.csrf().disable();
return http.build();
}
}
Thanks a lot !
I'm trying to retrieve the azure JWT access token from my Spring Boot application from another application by querying a /token endpoint, but the token I receive is seemingly incorrect.
The project has a Spring Boot backend and an Eclipse rcp frontend. I'm attempting to retrieve the access token from the eclipse frontend. For this, I have the controller below:
#Autowired
private OAuth2AuthorizedClientService authorizedClientService;
#GetMapping("/token")
public String user(OAuth2AuthenticationToken authentication) {
OAuth2AuthorizedClient authorizedClient = this.authorizedClientService
.loadAuthorizedClient(authentication.getAuthorizedClientRegistrationId(), authentication.getName());
return authorizedClient.getAccessToken().getTokenValue();
}
Which returns a token with the following format:
PAQABAAAAAABeAFzDwllzTYGDLh_qYbH8hgtbYMB8x7YLamQyQPk_MEXyd9Ckc5epDFQMv3RxjmMie0JDr5uN82U4RFLgU3fnDBxGolo4XVwzLEsTZDmUK_r0YG6ZwLbbQI_ch_Xn8xCxhsFq-AoRbEESDqK3GmK4eXwCYoT0G8_XfZjHTvCNTOMqUb2Q-CD2EalIKf0zSZ5184qrvlXfdNeT_BJdH_tqaodn80Bp2UL2hdnOCDZuWRqKl_2fi4v-eOOKJCcjOqY6SreVEeoKkIvVdayGE8F6qCxFehmlA0sX9sVW34FIVYVo4lDRsTkm-WN2KJwxJmalNcxg0k2ObDnIeC1ulPPpiPq-O_LK9bVA4HEZ63cJi9ZwQHwLPUhOO6TquoCOroHSy5KPoFkX3N796hM1i0NpaaY4MeAx17CSYeZ9P06jvYD7UMTV3OwWt-OVrDm5z_AvbOvyHRf9wjh31H6oLoc-iu_NCspT6NzC2UZQSHBtKdydEcP6sNkRp073jrZEg8UtcVT6HzddIBk2P0tVeIiSyU3SfLETbzJE67xtJVip3ai9aLN28c0qt3rDBaVGDAXjXhqrh5D3NiXdQjS6YTAKy0bVmNk9Yr9o2CGBA2wFjE8OZ6_Hb3k8_13KMJHafx0gAA
Dependencies from pom.xml
Built using spring boot with the following relevant dependencies:
spring-boot-starter-web v2.2.4
azure-active-directory-spring-boot-starter v2.2.1
spring-security-oauth2-client v5.2.1
spring-security-oauth2-jose v5.2.1
spring-security-oauth2-resource-server v5.2.1
Config from application.yml
We support multiple authorization servers, here is the fully configured azure client:
spring:
security:
oauth2:
client:
azure:
client-id: XXX
client-secret: XXX
client-name: Microsoft
scope: openid, https://graph.microsoft.com/user.read, profile
authorization-grant-type: authorization_code
redirect-uri: http://localhost:8080/login/oauth2/code/azure
client-authentication-method: basic
authentication-method: post
provider:
authorization-uri: https://login.microsoftonline.com/XXX/oauth2/authorize
token-uri: https://login.microsoftonline.com/XXX/oauth2/token
user-info-uri: https://login.microsoftonline.com/XXX/openid/userinfo
jwt-set-uri: https://login.microsoftonline.com/dXXX/discovery/keys
azure:
activedirectory:
tenant-id: XXX
active-directory-groups: XXX
allow-telemetry: false
websecurityconfig.java
#Configuration
#EnableConfigurationProperties
#EnableWebSecurity
#Order(1)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
[...]
.anyRequest().authenticated();
http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
http.oauth2Login()
.userInfoEndpoint()
.oidcUserService(oidcUserService)
.and()
.authorizationEndpoint();
}
[...]
}
This is how I ended up obtaining the open id token from Azure
#GetMapping("/token")
public String user(OAuth2AuthenticationToken authentication) {
DefaultOidcUser user = (DefaultOidcUser) authentication.getPrincipal();
return user.getIdToken().getTokenValue();
}
My app works well with normal non-SSL OAuth2 Servers. But I dont know how to config client to work with the SSL equivalents. I always get 401 when oauth2 server redirects back to http://localhost:8051/app1/login after success authentication and granting permissions. My App code likes below:
application.yml:
server:
port: 8051
servlet:
contextPath: /app1
security:
oauth2:
client:
clientId: sampleclient
clientSecret: samplepw
accessTokenUri: https://localhost:8668/sso-server/oauth/token
userAuthorizationUri: https://localhost:8668/sso-server/oauth/authorize
tokenName: oauth_token
resource:
userInfoUri: https://localhost:8688/api/me
Application.java
#SpringBootApplication
#EnableOAuth2Sso
public class App1 extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/", "/login", "/error**", "/webjars/**").permitAll()
.anyRequest().authenticated()
.and().logout()
.logoutUrl("/logout")
.logoutSuccessUrl("https://localhost:8668/sso-server/logout")
.invalidateHttpSession(true)
.deleteCookies("client-session", "JSESSIONID")
.permitAll()
.and().csrf().disable()
;
}
public static void main(String[] args) {
SpringApplication.run(App1.class, args);
}
}
OAuth2 Servers work as expected and I can call all oauth APIs that clients need with https schema on Postman, so I think we have nothing to do with them and don't post the code here.