spring cloud gateway with sso - spring-boot

I have an old application with springboot 2.1.3 where zuul proxy is used as API gateway. With Zuul|SSO|Feign is also implemented. Due to recent vulnerabilites, we are migrating to springboot 2.6.8 where we need to migrate zuul to spring cloud gateway along with cors, sso and Feign. I have implemented spring cloud gateway but not able to understand about sso and feign. Below giving some reference with existing code and new as well.
#EnableFeignClients
#EnableDiscoveryClient
#EnableZuulProxy
#EnableOAuth2Sso
#SpringBootApplication(exclude = { MongoAutoConfiguration.class, MongoDataAutoConfiguration.class,
MongoReactiveAutoConfiguration.class, DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
public class SampleApplication {
private Logger logger = LoggerFactory.getLogger(SampleApplication.class);
#Value("${spring.cloud.consul.discovery.instanceId}")
private String instanceId;
#Value("${spring.cloud.consul.host}")
private String host;
#Value("${spring.cloud.consul.port}")
private String port;
public static void main(String[] args) {
new SpringApplicationBuilder(SampleApplication.class).web(WebApplicationType.SERVLET).run(args);
}
#Bean
public RequestInterceptor getUserFeignClientInterceptor() {
return new UserFeignClientInterceptor();
}
#Bean
public FilterRegistrationBean<CorsFilter> simpleCorsFilter() {
logger.debug("simpleCorsFilter called");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
#PreDestroy
public void shutdown() {
logger.info("service {} is shutting down", instanceId);
RestTemplate restTemplate = new RestTemplate();
String url = String.format("http://%s:%d/agent/service/deregister/%s", host, port, instanceId);
try {
restTemplate.put(url, null);
} catch (Exception e) {
logger.error("{}", e);
}
}
}
SpringSecurity Conffiguration
#Configuration
#Order(Ordered.HIGHEST_PRECEDENCE)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
//web.ignoring().antMatchers(HttpMethod.OPTIONS).antMatchers("/actuator/**");
web.ignoring().antMatchers(HttpMethod.OPTIONS).antMatchers("/**");
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.cors().and()
.requestMatcher(new RequestHeaderRequestMatcher("Authorization")).authorizeRequests().antMatchers("/**")
.authenticated().and().csrf().disable();
}
FiegnInterceptor
public class UserFeignClientInterceptor implements RequestInterceptor {
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String BEARER_TOKEN_TYPE = "Bearer";
#Override
public void apply(RequestTemplate template) {
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
if (authentication != null && authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
template.header(AUTHORIZATION_HEADER, String.format("%s %s", BEARER_TOKEN_TYPE, details.getTokenValue()));
}
}
}
SpringCloudGateway - New imeplementation where defined all the routes and global cors but not sure how to use sso and openfeign.
spring:
application:
name: SampleApplicationNew
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: Microservice1
uri: lb://Microservice1
predicates:
- Path=/microservice1/**/
globalcors:
corsConfigurations:
'[/**]':
allowedOrigins: "*"
allowedHeaders:
- "*"
allowedMethods:
- GET
- POST

Related

Keycloak Multitenancy static Content Return 404

I'm working on modernizing monolithic application to be microservice based application supporting multi tenancy using Spring boot, Keycloak 17, the configuration is Keycloak configuration file depending on the path referring to this example
For me it it working, and can load the deployments from json, login, below is the url for the application and I'm parsing branch1 after "tenant" without issues
http://localhost:8100/tenant/branch1/
The main issue is rendering css and JS files which is containing tenant name knwoing that I'm using sing WAR
with multiple realms
http://localhost:8100/tenant/branch1/resources/bootstrap/js/bootstrap.min.js --> return 404 which is not exist
Actual code for including static contents
in The jsp files I'm reading css/js files as before <link rel="stylesheet" href="resources/bootstrap/css/bootstrap.min.css">
keycloal json file example
{"realm": "branch1",
"auth-server-url": "http://localhost:8181/",
"ssl-required": "external",
"resource": "app",
"public-client": true,
"confidential-port": 0,
"principal-attribute": "preferred_username"}
Please advise
rendering static content
is there any guidance after authentication to return one URL without tenant/branch1 specially I'm using CurrentTenantIdentifierResolver inside my application
#ConditionalOnProperty(prefix = "keycloak.config", name = "resolver", havingValue = "path")
public class PathBasedConfigResolver implements KeycloakConfigResolver {
private final ConcurrentHashMap<String, KeycloakDeployment> cache = new ConcurrentHashMap<>();
#SuppressWarnings("unused")
private static AdapterConfig adapterConfig;
#Override
public KeycloakDeployment resolve(OIDCHttpFacade.Request request) {
System.out.println("inside resolve :: ");
String realm = SubdomainUtils.obtainTenantFromSubdomain(request.getURI());
if (realm.contains("?")) {
realm = realm.split("\\?")[0];
}
if (!cache.containsKey(realm)) {
InputStream is = this.getClass().getResourceAsStream("/" + realm + "-keycloak.json");
cache.put(realm, KeycloakDeploymentBuilder.build(is));
}
return cache.get(realm);
}
static void setAdapterConfig(AdapterConfig adapterConfig) {
PathBasedConfigResolver.adapterConfig = adapterConfig;
}
}
public class SpringKeycloakSecurityConfiguration {
#DependsOn("keycloakConfigResolver")
#KeycloakConfiguration
#ConditionalOnProperty(name = "keycloak.enabled", havingValue = "true", matchIfMissing = true)
public static class KeycloakConfigurationAdapter extends KeycloakWebSecurityConfigurerAdapter {
/**
* Registers the KeycloakAuthenticationProvider with the authentication manager.
*/
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
SimpleAuthorityMapper soa = new SimpleAuthorityMapper();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(soa);
auth.authenticationProvider(keycloakAuthenticationProvider);
}
/**
* Defines the session authentication strategy.
*/
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
// required for bearer-only applications.
// return new NullAuthenticatedSessionStrategy();
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
#Override
protected AuthenticationEntryPoint authenticationEntryPoint() throws Exception {
return new MultitenantKeycloakAuthenticationEntryPoint(adapterDeploymentContext());
}
#Override
protected KeycloakAuthenticationProcessingFilter keycloakAuthenticationProcessingFilter() throws Exception {
KeycloakAuthenticationProcessingFilter filter = new KeycloakAuthenticationProcessingFilter(
authenticationManager(), new AntPathRequestMatcher("/tenant/*/sso/login"));
filter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy());
return filter;
}
#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();
}
/**
* Configuration spécifique à keycloak (ajouts de filtres, etc)
*
* #param http
* #throws Exception
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
// use previously declared bean
.sessionAuthenticationStrategy(sessionAuthenticationStrategy())
// keycloak filters for securisation
.and().addFilterBefore(keycloakPreAuthActionsFilter(), LogoutFilter.class)
.addFilterBefore(keycloakAuthenticationProcessingFilter(), X509AuthenticationFilter.class)
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint())
.and().logout().addLogoutHandler(keycloakLogoutHandler()).logoutUrl("/tenant/*/logout")
.logoutSuccessHandler(
// logout handler for API
(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) -> response.setStatus(HttpServletResponse.SC_OK))
.and().authorizeRequests().antMatchers("mobileservlet/**").permitAll().antMatchers("**/favicon.ico")
.permitAll().antMatchers("/error").permitAll().antMatchers("/login.go").permitAll()
.antMatchers("/resources/*").permitAll().anyRequest().authenticated();
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList(HttpMethod.OPTIONS.name(), "GET", "POST"));
configuration.setAllowedHeaders(
Arrays.asList("Access-Control-Allow-Headers", "Access-Control-Allow-Origin", "Authorization"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
}
public class MultitenantKeycloakAuthenticationEntryPoint extends KeycloakAuthenticationEntryPoint {
public MultitenantKeycloakAuthenticationEntryPoint(AdapterDeploymentContext adapterDeploymentContext) {
super(adapterDeploymentContext);
}
public MultitenantKeycloakAuthenticationEntryPoint(AdapterDeploymentContext adapterDeploymentContext, RequestMatcher apiRequestMatcher) {
super(adapterDeploymentContext, apiRequestMatcher);
}
#Override
protected void commenceLoginRedirect(HttpServletRequest request, HttpServletResponse response) throws IOException {
System.out.println("inside commenceLoginRedirect :: ");
String path = request.getRequestURI();
int multitenantIndex = path.indexOf("tenant/");
if (multitenantIndex == -1) {
throw new IllegalStateException("Not able to resolve the realm from the request path!");
}
String realm = path.substring(path.indexOf("tenant/")).split("/")[1];
if (realm.contains("?")) {
realm = realm.split("\\?")[0];
}
String contextAwareLoginUri = request.getContextPath() + "/tenant/" + realm + DEFAULT_LOGIN_URI;
response.sendRedirect(contextAwareLoginUri);
}
}
Bad news, the Keycloak adapters for spring you are using are very deprecated. Don't use it.
Better news, I host spring-boot starters for resource-servers which support multi-tenancy: accept identities issued by more than just one issuer (as many realms as you need in your case) and retrieve "roles" from realms and clients with the mapping you want (control case and prefix). It also enables you to configure "public" routes and CORS configuration from preperties file (plus a few more things).
Configuration for realm1 and other-realm both used by two clients (some client and other-client) is as simple as:
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<!-- replace "webflux" with "webmvc" if your app is a servlet -->
<!-- replace "jwt" with "introspecting" to use token introspection instead of JWT decoding -->
<artifactId>spring-addons-webflux-jwt-resource-server</artifactId>
<!-- this version is to be used with spring-boot 3.0.0-RC2, use 5.x for spring-boot 2.6.x or before -->
<version>6.0.5</version>
</dependency>
#EnableMethodSecurity
public static class WebSecurityConfig { }
com.c4-soft.springaddons.security.issuers[0].location=https://localhost:8443/realms/realm1
com.c4-soft.springaddons.security.issuers[0].authorities.claims=realm_access.roles,ressource_access.some-client.roles,ressource_access.other-client.roles
com.c4-soft.springaddons.security.issuers[1].location=https://localhost:8443/realms/other-realm
com.c4-soft.springaddons.security.issuers[1].authorities.claims=realm_access.roles,ressource_access.some-client.roles,ressource_access.other-client.roles
com.c4-soft.springaddons.security.cors[0].path=/some-api

How to fix Cors error Access-Control-Allow-Origin missing

I have a spring boot rest application and I am not using Spring security. My rest service looks like this
#RestController
#CrossOrigin
public class AuthenticationService {
...
#GetMapping(path = "/getUser")
public JSONObject getUser() {
...
}
}
I call the API from a REST application using axios get. Everything works fine locally.
But when the application is deployed on cloud as a docker image, I get the 403 error
(Reason: CORS header ‘Access-Control-Allow-Origin’ missing).
Even when I add a CorsConfiguration file I get the same error.
#Configuration
public class CorsConfiguration {
#Bean
public WebMvcConfigurer corsConfigurer()
{
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*")
.allowedHeaders("Accept", "Origin", "X-Requested-With,Content-Type", "Authorization", "X-XSRF-Header")
.allowCredentials(true);
}
};
}
}
I have spent a lot of time to find a solution for this but somehow it isn't working.
Declaring a bean works fine for me:
#Configuration
public class WebConfigurer implements ServletContextInitializer, WebMvcConfigurer {
private final Environment env;
private final MyProperties properties;
public WebConfigurer(Environment env, MyProperties properties) {
this.env = env;
this.properties = properties;
}
#Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = properties.getCors();
if (config.getAllowedOrigins() != null && !config.getAllowedOrigins().isEmpty()) {
log.debug("Registering CORS filter");
source.registerCorsConfiguration("/api/**", config);
source.registerCorsConfiguration("/management/**", config);
source.registerCorsConfiguration("/v3/api-docs", config);
}
return new CorsFilter(source);
}
}
Yaml properties:
# CORS is only enabled by default with the "dev" profile
cors:
allowed-origins: '*'
allowed-methods: '*'
allowed-headers: '*'
exposed-headers: 'Authorization,Link,X-Total-Count'
allow-credentials: true
max-age: 1800
fixed by adding spring security

Handling SOAP Fault in Spring Boot WebServiceTemplate

I am new to SOAP, trying to run a sample SOAP client using Spring Boot
How the SOAP fault, Exceptions or Errors are handled while using WebServiceTemplate
public class CountryClient extends WebServiceGatewaySupport {
private static final Logger log = LoggerFactory.getLogger(CountryClient.class);
public GetCountryResponse getCountry(String country) {
GetCountryRequest request = new GetCountryRequest();
request.setName(country);
log.info("Requesting location for " + country);
GetCountryResponse response = (GetCountryResponse) getWebServiceTemplate()
.marshalSendAndReceive("http://localhost:8080/ws/countries", request,
new SoapActionCallback(
"http://spring.io/guides/gs-producing-web-service/GetCountryRequest"));
return response;
}
}
One way is writing your custom interceptor which implements Spring WS's ClientInterceptor interface. You should override handleFault method to handle SOAP faults with your custom logic.
public class MySoapClientInterceptor implements ClientInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(MySoapClientInterceptor.class);
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
return true;
}
#Override
public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException {
return true;
}
#Override
public boolean handleFault(MessageContext messageContext) throws WebServiceClientException {
LOGGER.info("intercepted a fault...");
SoapBody soapBody = getSoapBody(messageContext);
SoapFault soapFault = soapBody.getFault();
LOGGER.error(soapFault.getFaultStringOrReason());
throw new RuntimeException(String.format("Error occured while invoking SOAP service - %s ", soapFault.getFaultStringOrReason()));
}
#Override
public void afterCompletion(MessageContext messageContext, Exception ex) throws WebServiceClientException {
}
private SoapBody getSoapBody(MessageContext messageContext) {
SoapMessage soapMessage = (SoapMessage) messageContext.getResponse();
SoapEnvelope soapEnvelope = soapMessage.getEnvelope();
return soapEnvelope.getBody();
}
}
Then you need to register your custom Interceptor class as an interceptor at your SOAP client config class. At the bean definition of CountryClient at your Configuration class in your case.
#Configuration
public class SoapClientConfig {
#Value("${soap.server.url}")
public String soap_server_url;
#Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath("com.example");
return marshaller;
}
#Bean
public SoapConnector soapConnector(Jaxb2Marshaller marshaller) {
SoapConnector client = new SoapConnector();
client.setDefaultUri(soap_server_url);
client.setMarshaller(marshaller);
client.setUnmarshaller(marshaller);
ClientInterceptor[] clientInterceptors = {new MySoapClientInterceptor()};
client.setInterceptors(clientInterceptors);
return client;
}
}

Spring boot Oauth2 : Token relay from a client using Feign, Ribbon, Zull and Eureka to a ressource

I have an oauth2 client that get a token from an authorization server successfully. (not always has been the case but now it is... :))
The client, the zuul gateway and the resource server are all registered in Eureka.
My client use a Proxy to access to a remote ressource service named microservice-files.
#RestController
#FeignClient(name = "zuul-server")
#RibbonClient(name = "microservice-files")
public interface ProxyMicroserviceFiles {
#GetMapping(value = "microservice-files/root")
FileBean getUserRoot();
}
So I'd like to relay the token to Zull and then to the resource server.
I can relay the token this way to contact Zuul and apparently the load balancing is managed too (I've just test I didn't know and it's great) also zuul can relay the token, but it's not very convenient I'd prefer the previous approach.
#EnableConfigurationProperties
#SpringBootApplication
#EnableFeignClients("com.clientui")
public class ClientUiApplication {
#Bean
public OAuth2RestOperations restOperations(
OAuth2ProtectedResourceDetails resource,
OAuth2ClientContext context) {
return new OAuth2RestTemplate(resource, context);
}
public static void main(String[] args) {
SpringApplication.run(ClientUiApplication.class, args);
}
}
here is the test controler
#Controller
public class ClientController {
#Autowired
private RestOperations restOperations;
#RequestMapping("/root")
public ResponseEntity userRootTest() {
String rootUrl = "http://localhost:9004/microservice-files/root";
return restOperations.getForEntity(rootUrl,FileBean.class);
}
}
If I correctly understand your problem then you can use a RequestInterceptor to add a token in each request by the feign. In order to do it you can use the next configuration:
#Bean
public RequestInterceptor oauth2FeignRequestInterceptor(OAuth2ClientContext oauth2ClientContext,
OAuth2ProtectedResourceDetails resource) {
return new OAuth2FeignRequestInterceptor(oauth2ClientContext, resource);
}
#Bean
protected OAuth2ProtectedResourceDetails resource() {
AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails();
resource.setAccessTokenUri("http://127.0.0.1:9000/auth/login");
resource.setUserAuthorizationUri("http://127.0.0.1:9000/auth/authorize");
resource.setClientId("my-client");
resource.setClientSecret("my-secret");
return resource;
}
This is what I did to make it work.
#Bean(name = "oauth2RestTemplate")
#LoadBalanced
public OAuth2RestTemplate restTemplate(SpringClientFactory clientFactory) {
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resourceDetails());
RibbonLoadBalancerClient ribbonLoadBalancerClient = new RibbonLoadBalancerClient(clientFactory);
LoadBalancerInterceptor loadBalancerInterceptor = new LoadBalancerInterceptor(ribbonLoadBalancerClient);
ClientCredentialsAccessTokenProvider accessTokenProvider = new ClientCredentialsAccessTokenProvider();
accessTokenProvider.setInterceptors(Arrays.asList(loadBalancerInterceptor));
restTemplate.setAccessTokenProvider(accessTokenProvider);
return restTemplate;
}
public ClientCredentialsResourceDetails resourceDetails() {
ClientCredentialsResourceDetails clientCredentialsResourceDetails = new ClientCredentialsResourceDetails();
clientCredentialsResourceDetails.setId("1");
clientCredentialsResourceDetails.setClientId("my-ms");
clientCredentialsResourceDetails.setClientSecret("123");
clientCredentialsResourceDetails.setAccessTokenUri("http://oauth-server/oauth/token");
clientCredentialsResourceDetails.setScope(Arrays.asList("read"));
clientCredentialsResourceDetails.setGrantType("client_credentials");
return clientCredentialsResourceDetails;
}

How to access a protected spring boot resource with access_token from command line , e.g. curl or httpie

In our spring boot application we use mitreid for openid connect. If we access a protected resource from browser, we are redirected to keycloak, our openid provider, and then after successful authentication, to the desired resource. This is working like intended.
Now we want to access the same resource from command line, e.g curl or httpie.
But nothing is working.
Getting access_token from keycloak returns an jwt with access_token and id_token.
But access with curl -H "Authorization: Bearer 'access_token>'" http://localhost:8080 doesn't work.
Here is my oidc configuration
public class OIDCSecurityConfiguration extends WebSecurityConfigurerAdapter {
public static final Logger LOG = LoggerFactory.getLogger(OIDCSecurityConfiguration.class);
private final String loginPath = OIDCAuthenticationFilter.FILTER_PROCESSES_URL;
#Value("${oidc.issuer-uri}")
private String issuerUri;
#Value("${oidc.client-uri}")
private String clientUri;
#Value("${oidc.client-id}")
private String clientId;
#Value("${oidc.client-secret}")
private String clientSecret;
#Value("${oidc.client-scopes}")
private String clientScopes;
private String getLogoutUri() {
final ServerConfiguration serverConfiguration = serverConfigurationService().getServerConfiguration(issuerUri);
if (serverConfiguration == null) {
LOG.error("OpenID Connect server configuration could not be retrieved");
return "/";
}
try {
return serverConfiguration.getEndSessionEndpoint() + "?redirect_uri=" + URLEncoder.encode(clientUri, "UTF-8");
} catch (UnsupportedEncodingException e) {
LOG.error("Cannot encode redirect uri");
return "/";
}
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(authenticationFilter(), AbstractPreAuthenticatedProcessingFilter.class)
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint())
.and()
.authorizeRequests()
.antMatchers("/logout").permitAll()
.and()
.formLogin().loginPage("/openid_connect_login")
.and()
.logout().logoutSuccessUrl(getLogoutUri());
}
#Autowired
public void configureAuthenticationProvider(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Bean
public AuthenticationEntryPoint authenticationEntryPoint() {
return new LoginUrlAuthenticationEntryPoint(loginPath);
}
#Bean
public WebMvcConfigurerAdapter mvcInterceptor() {
return new UserInfoInterceptorAdapter();
}
#Bean
public AuthenticationProvider authenticationProvider() {
final OIDCExtendedAuthenticationProvider authenticationProvider = new OIDCExtendedAuthenticationProvider();
authenticationProvider.setAuthoritiesMapper(new KeycloakAuthoritiesMapper(clientId));
return authenticationProvider;
}
#Bean
public AbstractAuthenticationProcessingFilter authenticationFilter() throws Exception {
final OIDCAuthenticationFilter filter = new OIDCAuthenticationFilter();
filter.setAuthenticationManager(authenticationManager());
filter.setServerConfigurationService(serverConfigurationService());
filter.setClientConfigurationService(clientConfigurationService());
final StaticSingleIssuerService issuer = new StaticSingleIssuerService();
issuer.setIssuer(this.issuerUri);
filter.setIssuerService(issuer);
// TODO: change to signed or encrypted builder for production.
filter.setAuthRequestUrlBuilder(new PlainAuthRequestUrlBuilder());
return filter;
}
#Bean
public ClientConfigurationService clientConfigurationService() {
final StaticClientConfigurationService service = new StaticClientConfigurationService();
final RegisteredClient client = new RegisteredClient();
client.setClientId(clientId);
client.setClientSecret(clientSecret);
client.setClientName(clientId);
client.setScope(Arrays.stream(clientScopes.split(",")).collect(Collectors.toSet()));
client.setTokenEndpointAuthMethod(AuthMethod.SECRET_BASIC);
client.setRedirectUris(Collections.singleton(clientUri + loginPath));
client.setRequestObjectSigningAlg(JWSAlgorithm.RS256);
service.setClients(Collections.singletonMap(this.issuerUri, client));
return service;
}
#Bean
public ServerConfigurationService serverConfigurationService() {
final DynamicServerConfigurationService service = new DynamicServerConfigurationService();
service.setWhitelist(Collections.singleton(issuerUri));
return service;
}
private static class UserInfoInterceptorAdapter extends WebMvcConfigurerAdapter {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new UserInfoInterceptor());
}
}
}

Resources