Spring Cloud Security with WSO2 Identity Server - spring-boot

I am trying to implement a spring-boot-web application, secured using spring-cloud-security with an external on-premise authentication server (WSO2 Identity Server). We're using OAuth2 OpenID Connect (JWT tokens).
My application will redirect to the WSO2 server just fine for the /oauth2/authorize request, but is failing when it tries to convert the authorization_code into an access_token. spring-boot reports a 401 authentication error.
I believe this is because the oauth2/token endpoint requires basic authentication.
Can someone guide me how to make the /oauth2/token request use basic authentication headers with spring-cloud-security?
Here's my application.yml
spring:
oauth2:
sso:
home:
secure: false
path: /,/**/*.html
client:
accessTokenUri: https://URI:9443/oauth2/token
userAuthorizationUri: https://URI:9443/oauth2/authorize
clientId: id
clientSecret: secret
scope: openid
clientAuthenticationScheme: header
resource:
userInfoUri: https://URI:9443/oauth2/userinfo?schema=openid
Here is my simple application.java
package demo;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.security.oauth2.sso.EnableOAuth2Sso;
import org.springframework.cloud.security.oauth2.sso.OAuth2SsoConfigurerAdapter;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.WebUtils;
#SpringBootApplication
#EnableZuulProxy
#EnableOAuth2Sso
public class SpringOauth2WithWso2Application {
public static void main(String[] args) {
SpringApplication.run(SpringOauth2WithWso2Application.class, args);
}
#Configuration
protected static class SecurityConfiguration extends OAuth2SsoConfigurerAdapter {
#Override
public void match(RequestMatchers matchers) {
matchers.anyRequest();
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/index.html", "/home.html", "/")
.permitAll().anyRequest().authenticated().and().csrf()
.csrfTokenRepository(csrfTokenRepository()).and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
}
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class
.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null
&& !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
}

Although the need to add the WSO2 IS certificate to the Java Certificate Store has been mentioned as a solution, in case it helps anyone the steps required to do so, as well as set up mitmproxy so you can inspect the traffic has been documented here by means of a template project: https://github.com/nicodewet/template-spring-boot-oauth2-wso2-is

Related

403 forbidden error on authentication filter

I am working on a basic spring boot api using mysql as database
I created an endpoint for signup user("/users") which is bcrypt the password
while login i created a authentication filter which is adding jwt token in the header of response
but while accesing endpoint ("/login") i am getting 403 error,
I have already configured the ant match for request named "/login"
**Web Security Configuration **
package com.mukul.app.mobileappws.security;
import com.mukul.app.mobileappws.security.FIlter.AuthenticationFilter;
import com.mukul.app.mobileappws.services.UserService;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
#Configuration
#EnableWebSecurity
public class ConfigurationSecurity extends WebSecurityConfigurerAdapter {
UserService userService;
BCryptPasswordEncoder bcrypt;
ConfigurationSecurity(UserService u, BCryptPasswordEncoder b) {
this.userService = u;
this.bcrypt = b;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// http.authorizeRequests().antMatchers(HttpMethod.POST,
// "/users").permitAll().anyRequest()
// .authenticated();
//
AuthenticationFilter af = new AuthenticationFilter(authenticationManager());
http.csrf().disable();
http.authorizeRequests().antMatchers(HttpMethod.POST,
"/users").permitAll();
http.authorizeRequests().antMatchers("/login").permitAll();
http.authorizeRequests().anyRequest()
.authenticated();
http.addFilter(af);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(bcrypt);
}
}
Authentication filter
package com.mukul.app.mobileappws.security.FIlter;
import java.io.IOException;
import java.util.Date;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.mukul.app.mobileappws.security.SecurityConstants;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authManager;
public AuthenticationFilter(AuthenticationManager am) {
this.authManager = am;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
final String email = request.getParameter("email");
final String password = request.getParameter("password");
return authManager.authenticate(new UsernamePasswordAuthenticationToken(email, password));
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication auth) throws IOException, ServletException {
// generate token
User u = (User) auth.getPrincipal();
String email = u.getUsername();
String token = Jwts.builder()
.setSubject(email)
.setExpiration(new Date(System.currentTimeMillis() + SecurityConstants.EXPIRE))
.signWith(SignatureAlgorithm.HS512, SecurityConstants.SECRET)
.compact();
response.addHeader(SecurityConstants.HEADER, SecurityConstants.PREFIX + token);
super.successfulAuthentication(request, response, chain, auth);
}
}
enter image description here
I think your configuration is okay.
http.addFilter(authFilter) will put filter at appropriate position by examining the filter type.
In your case, I suspect issue is not triggering login request properly. As per the content in given repo, I ran the project and used embedded H2 instead of full blown database.
This is how you need to trigger your request if you are reading from request.getParameter(parameterName). Please note that I have received 404 error because Spring is trying to redirect me to '/' post successful login which doesn't exist. :)
With Spring Security I always had problem with CSRF on login, because the page doesn't have the CSRF token and POST is not allowed without it, try to check with it.

AuthenticationManager.authenticate method not getting called

I am trying to follow the API Key authentication code from this answer:
https://stackoverflow.com/a/48448901
I created my filter class:
package com.propfinancing.CADData.web;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.LoggerFactory;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
public class PullAPIKeyFromHeaderFilter extends AbstractPreAuthenticatedProcessingFilter {
private String apiKeyHeaderName;
public PullAPIKeyFromHeaderFilter(String apiKeyHeaderName) {
this.apiKeyHeaderName = apiKeyHeaderName;
}
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
String headerValue = request.getHeader(apiKeyHeaderName);
return request.getHeader(headerValue);
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
return apiKeyHeaderName;
}
}
And then I implemented my security configuration:
package com.propfinancing.CADData.web;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
#Configuration
#EnableWebSecurity
#Order(1)
public class APIKeySecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${caddata.apiKey.header.name}")
private String apiKeyHeaderName;
#Value("${caddata.apiKey}")
private String apiKey;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
PullAPIKeyFromHeaderFilter pullAPIKeyFromHeaderfilter = new PullAPIKeyFromHeaderFilter(apiKeyHeaderName);
AuthenticationManager authManager = new AuthenticationManager() {
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
String principal = (String) authentication.getPrincipal();
if (!apiKey.equals(principal))
throw new BadCredentialsException("Invalid API key");
authentication.setAuthenticated(true);
return authentication;
}
};
pullAPIKeyFromHeaderfilter.setAuthenticationManager(authManager);
httpSecurity.antMatcher("/**");
httpSecurity.addFilter(pullAPIKeyFromHeaderfilter);
httpSecurity.requiresChannel().anyRequest().requiresSecure();
httpSecurity.csrf().disable();
httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry urlAuthConfigurer = httpSecurity.authorizeRequests();
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.AuthorizedUrl authorizedUrl = urlAuthConfigurer.anyRequest();
authorizedUrl.authenticated();
}
}
When I do an external call to the application with the header as part of the request, I get a 403 Forbidden response.
I can see the filter pulling the key from the header. That part is working.
But, the authenticate() method is not being called to check if the header is valid.
I am not sure what I missed, the code looks the same to me.
Any ideas?
Looks like the wrong base class, per https://docs.spring.io/spring-security/site/docs/4.0.x/apidocs/org/springframework/security/web/authentication/preauth/AbstractPreAuthenticatedProcessingFilter.html :
The purpose is then only to extract the necessary information on the
principal from the incoming request, rather than to authenticate them.
Try extending https://docs.spring.io/spring-security/site/docs/4.0.x/apidocs/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.html instead.
I was not able to get the code above to work, but I changed it to use the second answer on the thread (Using a Filter) https://stackoverflow.com/a/63852212 It works as expected.

Spring Boot: "relaying" basic auth from REST controller to RestTemplate

I'm working with two Spring Boot applications, let's call them ServiceA and ServiceB, both exposing a REST API.
ServiceA is called by end users from the browser via a frontend app (we use #RestController classes). On some calls, ServiceA has to call ServiceB (using RestTemplate). We've got authentication and authorization sorted out for our target environment, but for testing locally we are relying on Basic Auth instead, and that's where we're hitting a snag: we would like ServiceA to re-use the Basic Auth credentials the user provided when calling Service B.
Is there an easy way to pass the Basic Auth credentials used on the call to our REST controller to the RestTemplate call?
Quick and dirty solution
The easiest way to do this would be:
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
#RestController
class SomeController {
private final RestTemplate restTemplate = new RestTemplate();
#PostMapping("/delegate/call")
public void callOtherService(#RequestHeader(HttpHeaders.AUTHORIZATION) String authorization) {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.AUTHORIZATION, authorization);
restTemplate.postForEntity("other-service.com/actual/call", new HttpEntity<Void>(null, headers), Void.class);
// handling the response etc...
}
}
Using interceptors and RestTemplateCustomizer
I didn't want to change to add an extra parameter on each controller method, and I wanted a way to enable or disable this behavior depending on the environment, so here is a slightly more complicated solution that can be enabled using Spring profiles, and doesn't touch the controllers:
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class BasicAuthPropagationInterceptor implements HandlerInterceptor, ClientHttpRequestInterceptor {
private final ThreadLocal<String> cachedHeader = new ThreadLocal<>();
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
final String header = request.getHeader(HttpHeaders.AUTHORIZATION);
cachedHeader.set(header);
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
cachedHeader.remove();
}
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
String ch = cachedHeader.get();
if (!request.getHeaders().containsKey(HttpHeaders.AUTHORIZATION) && ch != null) {
request.getHeaders().add(HttpHeaders.AUTHORIZATION, ch);
}
return execution.execute(request, body);
}
}
This stores the received header in a ThreadLocal and adds it with an interceptor for RestTemplate.
This can then be configured as such:
import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
#Configuration
#Profile("LOCAL")
class LocalConfiguration implements WebMvcConfigurer {
private final BasicAuthPropagationInterceptor basicAuthPropagationInterceptor
= new BasicAuthPropagationInterceptor();
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(basicAuthPropagationInterceptor);
}
#Bean
RestTemplateCustomizer restTemplateCustomizer() {
return restTemplate -> restTemplate.getInterceptors().add(basicAuthPropagationInterceptor);
}
}
RestTemplate obtained by using the default RestTemplateBuilder bean will then automatically set the Authorization HTTP header if it's available in the current thread.

Spring Boot Keycloak Multi Tenant Configuration

I have a Keycloak instance and created two realms and one user for each realm.
Realm1 (Tenant1) -> User 1
Realm2 (Tenant2) -> User 2
And i have my spring boot application.yml (resource server - API) for one specific realm and fixed in my code.
keycloak:
realm: Realm1
auth-server-url: https://localhost:8443/auth
ssl-required: external
resource: app
bearer-only: true
use-resource-role-mappings: true
It's working and validate for Realm1.
but now i can receive requests from user2 (tenant2) and the token will not be valid because the public key (realm1) is not valid for the signed request jwt token (realm2).
What is the best way to allow multi tenancy and dynamically configuration for multi realms?
thanks,
There's a whole chapter on it: 2.1.18: Multi-Tenanacy
Instead of defining the keycloak config in spring application.yaml, keep multiple keycloak.json config files, and use a custom KeycloakConfigResolver:
public class PathBasedKeycloakConfigResolver implements KeycloakConfigResolver {
#Override
public KeycloakDeployment resolve(OIDCHttpFacade.Request request) {
if (request.getPath().startsWith("alternative")) { // or some other criteria
InputStream is = getClass().getResourceAsStream("/tenant1-keycloak.json");
return KeycloakDeploymentBuilder.build(is); //TODO: cache result
} else {
InputStream is = getClass().getResourceAsStream("/default-keycloak.json");
return KeycloakDeploymentBuilder.build(is); //TODO: cache result
}
}
}
I'm not sure if this works well with the keycloak-spring-boot-starter, but I think it's enough to just wire your custom KeycloakConfigResolver in the KeycloakWebSecurityConfigurerAdapter:
#Configuration
#EnableWebSecurity
class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
#Bean
public KeycloakConfigResolver keycloakConfigResolver() {
return new PathBasedKeycloakConfigResolver();
}
[...]
}
import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.DependsOn;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
#DependsOn("keycloakConfigResolver")
#KeycloakConfiguration
#EnableGlobalMethodSecurity(jsr250Enabled = true)
#ConditionalOnProperty(name = "keycloak.enabled", havingValue = "true", matchIfMissing = true)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
/**
* Registers the KeycloakAuthenticationProvider with the authentication manager.
*/
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider authenticationProvider = new KeycloakAuthenticationProvider();
authenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(authenticationProvider);
}
/**
* Defines the session authentication strategy.
*/
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.cors()
.and()
.authorizeRequests().antMatchers(HttpMethod.OPTIONS)
.permitAll()
.antMatchers("/api-docs/**", "/configuration/ui",
"/swagger-resources/**", "/configuration/**", "/v2/api-docs",
"/swagger-ui.html/**", "/webjars/**", "/swagger-ui/**")
.permitAll()
.anyRequest().authenticated();
}
}
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.OIDCHttpFacade;
import java.io.InputStream;
import java.util.concurrent.ConcurrentHashMap;
public class PathBasedConfigResolver implements KeycloakConfigResolver {
private final ConcurrentHashMap<String, KeycloakDeployment> cache = new ConcurrentHashMap<>();
#Override
public KeycloakDeployment resolve(OIDCHttpFacade.Request request) {
String path = request.getURI();
String realm = "realmName";
if (!cache.containsKey(realm)) {
InputStream is = getClass().getResourceAsStream("/" + realm + "-keycloak.json");
cache.put(realm, KeycloakDeploymentBuilder.build(is));
}
return cache.get(realm);
}
}
import org.keycloak.adapters.KeycloakConfigResolver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.context.annotation.Bean;
#SpringBootApplication()
public class Application {
public static void main(String[] args) {
SpringApplication.run(DIVMasterApplication.class, args);
}
#Bean
#ConditionalOnMissingBean(PathBasedConfigResolver.class)
public KeycloakConfigResolver keycloakConfigResolver() {
return new PathBasedConfigResolver();
}
}

Need to get authenticated user name from the OAuth2User in Spring Cloud Gateway routes

I am a newbie to Spring Cloud Gateway. I have created a simple SCG application and my use case is to forward the authenticated username to my downstream application via http request header.
This is my SpringBoot class
package com.xyz;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties.Jwt;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.cloud.security.oauth2.gateway.TokenRelayGatewayFilterFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
#Controller
#SpringBootApplication
public class OracleSSOMultiplexer {
#Autowired
private TokenRelayGatewayFilterFactory filterFactory;
#GetMapping("/")
public String index(Model model,
#RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient,
#AuthenticationPrincipal OAuth2User oauth2User) {
model.addAttribute("userName", oauth2User.getName());
model.addAttribute("clientName", authorizedClient.getClientRegistration().getClientName());
model.addAttribute("userAttributes", oauth2User.getAttributes());
return "index";
}
#Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
//.route("resource", r -> r.path("/resource")
.route(r -> r.path("/hyperion/**")
.filters(f -> f.filters(filterFactory.apply())
.addRequestHeader("HYPLOGIN", "scott") // I need to pass the authenticated username here instead of hardcoding it
.addResponseHeader("hyp-response", "hyp-response-header-val"))
.uri("http://localhost:8081/")
.id("hyperionModule"))
.build();
}
public static void main(String[] args) {
SpringApplication.run(OracleSSOMultiplexer.class, args);
}
}
and this my application.yml
server:
port: 8080
logging:
level:
root: INFO
org.springframework.web: INFO
org.springframework.web.HttpLogging: DEBUG
org.springframework.security: DEBUG
org.springframework.security.oauth2: DEBUG
org.springframework.cloud.gateway: DEBUG
spring:
autoconfigure:
# TODO: remove when fixed https://github.com/spring-projects/spring-security/issues/6314
exclude: org.springframework.boot.actuate.autoconfigure.security.reactive.ReactiveManagementWebSecurityAutoConfiguration
thymeleaf:
cache: false
security:
oauth2:
client:
registration:
gateway:
provider: pingfederate
client-id: oidchyperion
client-secret: Lms#12345
authorization-grant-type: authorization_code
redirect-uri: http://localhost:8080/login/oauth2/code/gateway
scope: openid
provider:
pingfederate:
authorization-uri: https://xyz:9035/as/authorization.oauth2
token-uri: https://xyz:9035/as/token.oauth2
user-info-uri: https://xyz:9035/idp/userinfo.openid
user-name-attribute: sub
jwk-set-uri: https://xyz:9035/pf/JWKS
# cloud:
# gateway:
# routes:
# - id: resource
# uri: http://resource:9000
# predicates:
# - Path=/resource
# filters:
# - TokenRelay=
# - RemoveRequestHeader=Cookie
I am able to perform OAuth Authentication and able to display the authenticated user information using the Model at the index.html. I need now to just pass the logged in user's username (oauth2user.getName()) in the request header but I am not able to figure out.
Any help in this regards is appreciated.
Thanks,
Thani
package com.likeminds.filter;
import java.security.Principal;
import java.util.Collections;
import java.util.List;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import
org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
#Component
public class AuthNUserHeaderFilterFactory extends
AbstractGatewayFilterFactory<AuthNUserHeaderFilterFactory.Config> {
private static final String VALUE = "value";
public AuthNUserHeaderFilterFactory() {
super(Config.class);
}
#Override
public Config newConfig() {
return new Config();
}
#Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList(VALUE);
}
#Override
public GatewayFilter apply(Config config) {
//Custom Pre Filter. Suppose we can extract JWT and perform
Authentication
return (exchange, chain) -> exchange.getPrincipal()
.map(Principal::getName)
.defaultIfEmpty("Default User")
.map(userName -> {
//adds header to proxied request
System.out.println("Config value ="+config.value);
exchange.getRequest().mutate().header(config.value,
userName).build();
System.out.println("Config First pre header filter" +
exchange.getRequest().getHeaders());
return exchange;
})
.flatMap(chain::filter);
}
public static class Config {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
}
You can write a simple gateway filter config and intercept the request before passing the request to downstream microservices if you are using it as a gateway.
If you are using OAuth2 Authentication then you should be able to get the users information by adding below lines of code:
if (principal instanceof OAuth2AuthenticationToken) {
//Get username from Principal
String userName = principal.getName();
//Get user infor from spring security context
SecurityContextImpl context = exchange.getSession().block().getAttribute("SPRING_SECURITY_CONTEXT");
DefaultOidcUser principal1 = (DefaultOidcUser) context.getAuthentication().getPrincipal();
String fullName = principal1.getUserInfo().getFullName();
String emailId = principal1.getUserInfo().getEmail();
}
Once you get the use information, you can add this information in the request header or in cookies and pass on to the downstream microservices.
// Adding cookies
session = new HttpCookie("SESSION", exchange.getSession().block().getId()).toString();
// adds header to proxied request
exchange.getRequest().mutate().header("X-Auth-Username", new String[] { userName })
.header("X-Auth-Userid", new String[] { userId })
.header("X-Auth-Email", new String[] { emailId }).header("cookie", session)
.build();
Below is complete code which you can use:
import java.net.HttpCookie;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.context.SecurityContextImpl;
import
org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import reactor.core.publisher.Mono;
#Configuration
public class GatewayConfig {
#SuppressWarnings("deprecation")
#Bean
public GlobalFilter customGlobalFilter() {
return (exchange, chain) -> exchange.getPrincipal().map(principal -> {
String emailId = "";
String session = "";
String userName = "";
if (principal instanceof OAuth2AuthenticationToken) {
//Get username from Principal
userName = principal.getName();
SecurityContextImpl context = exchange.getSession().block().getAttribute("SPRING_SECURITY_CONTEXT");
DefaultOidcUser principal1 = (DefaultOidcUser) context.getAuthentication().getPrincipal();
emailId = principal1.getUserInfo().getEmail();
// Adding cookies
session = new HttpCookie("SESSION", exchange.getSession().block().getId()).toString();
}
// adds header to proxied request
exchange.getRequest().mutate().header("X-Auth-Username", new String[] { userName })
.header("X-Auth-Email", new String[] { emailId }).header("cookie", session)
.build();
return exchange;
}).flatMap(chain::filter).then(Mono.fromRunnable(() -> {
}));
}
}
You can use these headers and cookies in all the downstream microservices.

Resources