Service-to-service communication with feign in spring security 5 - spring-boot

in fact the spring-oauth project turn into maintenance mode we trying migrate our application into pure spring security 5 which support resource server configuration as well.
Our actual resource server configuration looks like this:
#Configuration
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests(
authorizeRequests -> {
authorizeRequests.antMatchers("/api/unsecured/**").permitAll();
authorizeRequests.anyRequest().authenticated();
}
);
}
#Bean
#ConfigurationProperties(prefix = "security.oauth2.client")
public ClientCredentialsResourceDetails clientCredentialsResourceDetails() {
return new ClientCredentialsResourceDetails();
}
#Bean
public TokenStore jwkTokenStore() {
return new JwkTokenStore("http://localhost:8080/...", new JwtAccessTokenConverter());
}
#Bean
public RequestInterceptor oauth2FeignRequestInterceptor(){
return new OAuth2FeignRequestInterceptor(new DefaultOAuth2ClientContext(), clientCredentialsResourceDetails());
}
#Bean
public OAuth2RestTemplate clientCredentialsRestTemplate() {
return new OAuth2RestTemplate(clientCredentialsResourceDetails());
}
}
and these properties:
security:
oauth2:
client:
client-id: service-id
client-secret: secret
access-token-uri: http://localhost:8081/oauth/token
This resource server is configured to work with jwt token. To verify token uses rsa public key from link passes to jwkstore. It is also able call another resource server with Feign.
And this is new configuration:
#Configuration
static class OAuth2ResourceServerConfig extends WebSecurityConfigurerAdapter {
private final JwtDecoder jwtDecoder;
ResourceServerConfiguration(JwtDecoder jwtDecoder) {
this.jwtDecoder = jwtDecoder;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests -> authorizeRequests
.antMatchers("/public/unsecured/**").permitAll()
.anyRequest().authenticated())
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.oauth2ResourceServer(oauth2ResourceServer -> oauth2ResourceServer
.jwt(jwtConfigurer -> {
jwtConfigurer.decoder(NimbusJwtDecoder.withJwkSetUri("http://localhost:8080/...").build());
jwtConfigurer.jwtAuthenticationConverter(tokenExtractor());
})
);
}
This configuration works fine to decode and verify tokens, but Feign doesn't work. Previous configuration with spring oauth2 supports Oauth2 feign interceptor which call authorization server to get its own access token. But I don't know how to configure this in spring security 5. This is flow which I need:
frontend client call spring resource server A with token
resource server A need data from resource server B
resource server A call authorization server to get access token with client_credentials grant type
resource server A call resource server B with its access token set to request header by feign
resource server A return all data to frontend client
Can you tell me how to configure 3. and 4. step in spring security 5 without spring's oauth project? Thank you.

Related

Spring Cloud Gateway - Intercept under hood request/response to Keycloak IDP

We are implementing a Spring Cloud Gateway application (with Webflux) that is mediating the OAuth2 authentication with Keycloak.
SCG checks if the Spring Session is active: if not, redirects to Keycloak login page and handles the response from the IDP. This process is executed out-of-the-box by the framework itself.
Our needs is to intercept the IDP Keycloak response in order to retrieve a field from the response payload.
Do you have any advices that will help us to accomplish this behavior?
Thanks!
You can implement ServerAuthenticationSuccessHandler:
#Component
public class AuthenticationSuccessHandler implements ServerAuthenticationSuccessHandler {
private ServerRedirectStrategy redirectStrategy;
public AuthenticationSuccessHandler(AuthenticationService authenticationService) {
redirectStrategy = new DefaultServerRedirectStrategy();
}
#Override
public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
if(authentication instanceof OAuth2AuthenticationToken) {
//Your logic here to retrieve oauth2 user info
}
ServerWebExchange exchange = webFilterExchange.getExchange();
URI location = URI.create(httpRequest.getURI().getHost());
return redirectStrategy.sendRedirect(exchange, location);
}
}
And update your security configuration to include success handler:
#Configuration
public class SecurityConfiguration {
private AuthenticationSuccessHandler authSuccessHandler;
public SecurityConfiguration(AuthenticationSuccessHandler authSuccessHandler) {
this.authSuccessHandler = authSuccessHandler;
}
#Bean
SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchange -> exchange
//other security configs
.anyExchange().authenticated()
.and()
.oauth2Login(oauth2 -> oauth2
.authenticationSuccessHandler(authSuccessHandler)
);
return http.build();
}
}

spring cloud secured gateway ratelimiting not allowing permitall urls

I was trying to apply rate limiting on my spring cloud gateway which is secured through oauth2 and keycloak. the gateway sits in front of 3 microservices. each microservice exposes openapi3 config. in both gateway and microservices made the url to openapi3 config as public. but when i followed instruction to apply rate limiting using redis the public urls are not public anymore and getting 403 forbidden.
gateway security -->
#Configuration
#EnableWebFluxSecurity
public class WebFluxSecurityConfig {
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity serverHttpSecurity) {
serverHttpSecurity.authorizeExchange(exchange -> exchange
.pathMatchers("/v3/api-docs/**",
"/employee/v3/api-docs/**",
"/department/v3/api-docs/**",
"/organization/v3/api-docs/**",
"/webjars/swagger-ui/**",
"/swagger-ui/**", "/swagger-ui.html").permitAll()
.anyExchange().authenticated())
.oauth2ResourceServer(ServerHttpSecurity.OAuth2ResourceServerSpec::jwt);
serverHttpSecurity.csrf().disable();
serverHttpSecurity.formLogin().disable();
serverHttpSecurity.httpBasic().disable();
return serverHttpSecurity.build();
}
}
Rate limiting config -->
#Configuration
public class RateLimitingConfig {
/*
* NOTE: this stops all unauthenticated access :(
* need a way to allow public permitted urls from this. but how!
*/
#Bean
KeyResolver userKeyResolver() {
return exchange -> ReactiveSecurityContextHolder.getContext()
.map(ctx -> ctx.getAuthentication().getPrincipal().toString());
}
}
microservice security config -->
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
//NOTE: this is to configure authorization
http.authorizeRequests(authorize -> authorize
.antMatchers("/v3/api-docs/**").permitAll()
.anyRequest().authenticated())
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
http.csrf().disable();
http.formLogin().disable();
http.httpBasic().disable();
}
}
the rate limiting is working properly for authenticated user but my permit all methods are not permitted by the gateway anymore.
how can i enforce rate limiting for private urls and also expose public urls to unauthenticated users?
here is the full code base -
https://github.com/tareqmy/springcloudexamples

ReactiveSpringSecurity role mapping

Based on this example https://blog.jdriven.com/2019/11/spring-cloud-gateway-with-openid-connect-and-token-relay I'm trying to use Spring Gateway, the latest version of Spring Security and Keycloak. Behind the Gateway there is a static application that I want to limit access to. I managed to configure everything so that authentication works. However, I am unable to validate the role correctly. Spring Security does not read it from the token and always assigns ROLE_USER. Token contains proper role and other parameters like username or scopes are read correctly. How to map a roles using ReactiveSecurity. Below is my configuration.
#Configuration
public class SecurityConfig {
#Bean
public OidcClientInitiatedServerLogoutSuccessHandler logoutSuccessHandler(#Value("${postLogoutRedirectUrl}") URI postLogoutRedirectUrl, ReactiveClientRegistrationRepository clientRegistrationRepository) {
OidcClientInitiatedServerLogoutSuccessHandler logoutSuccessHandler = new OidcClientInitiatedServerLogoutSuccessHandler(clientRegistrationRepository);
logoutSuccessHandler.setPostLogoutRedirectUri(postLogoutRedirectUrl);
return logoutSuccessHandler;
}
#Bean
public SecurityWebFilterChain springSecurityFilterChain(#Value("${securedPaths}") String[] securedPaths, ServerHttpSecurity http,
ReactiveClientRegistrationRepository clientRegistrationRepository,
OidcClientInitiatedServerLogoutSuccessHandler logoutSuccessHandler) {
http.oauth2Login();
http.logout(logout -> logout.logoutSuccessHandler(new OidcClientInitiatedServerLogoutSuccessHandler(clientRegistrationRepository)));
http.authorizeExchange()
.pathMatchers(securedPaths).hasRole("admin")
.anyExchange().permitAll();
http.logout().logoutSuccessHandler(logoutSuccessHandler);
http.exceptionHandling().accessDeniedHandler(new AccessDeniedHandler());
http.csrf().disable();
return http.build();
}
}

Storing JWT tokens on OAuth2 web client using Spring Security

I'm implementing an OAuth2 web application Client using Spring Boot 2.1.3 and Spring Security 5.1.3 that is obtaining JWT tokens from an authorization server through authorization code grant type and calls a protected resource server.
This is how the implementation looks up till now:
Security configuration and a restTemplate bean used to call the protected resource:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/").permitAll()
.anyRequest().authenticated()
.and()
.oauth2Login()
.and()
.oauth2Client()
.and().logout().logoutSuccessUrl("/");
}
#Bean
public RestTemplate restTemplate(OAuth2AuthorizedClientService clientService) {
RestTemplate restTemplate = new RestTemplate();
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
if (CollectionUtils.isEmpty(interceptors)) {
interceptors = new ArrayList<>();
}
interceptors.add(new AuthorizationHeaderInterceptor(clientService));
restTemplate.setInterceptors(interceptors);
return restTemplate;
}
}
The interceptor that adds the authorization header (from the framework's InMemoryOAuth2AuthorizedClientService) in the restTemplate:
public class AuthorizationHeaderInterceptor implements ClientHttpRequestInterceptor {
private OAuth2AuthorizedClientService clientService;
public AuthorizationHeaderInterceptor(OAuth2AuthorizedClientService clientService) {
this.clientService = clientService;
}
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] bytes, ClientHttpRequestExecution execution) throws IOException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String accessToken = null;
if (authentication != null && authentication.getClass().isAssignableFrom(OAuth2AuthenticationToken.class)) {
OAuth2AuthenticationToken auth = (OAuth2AuthenticationToken) authentication;
String clientRegistrationId = auth.getAuthorizedClientRegistrationId();
OAuth2AuthorizedClient client = clientService.loadAuthorizedClient(clientRegistrationId, auth.getName());
accessToken = client.getAccessToken().getTokenValue();
request.getHeaders().add("Authorization", "Bearer " + accessToken);
}
return execution.execute(request, bytes);
}
}
And the controller that calls the protected resource server:
#Controller
#RequestMapping("/profile")
public class ProfileController {
#Autowired
private RestTemplate restTemplate;
#Value("${oauth.resourceServerBase}")
private String resourceServerBase;
#GetMapping
public String getProfile(Model model) {
Profile profile = restTemplate.getForEntity(resourceServerBase + "/api/profile/", Profile.class).getBody();
model.addAttribute("profile", profile);
return "profile";
}
}
The OAuth2 client configuration is directly in the application.yml:
spring:
security:
oauth2:
client:
registration:
auth-server:
client-id: webClient
client-secret: clientSecret
scope: read,write
authorization-grant-type: authorization_code
redirect-uri: http://localhost:8081/client/login/oauth2/code/auth-server
provider:
auth-server:
authorization-uri: http://localhost:8080/auth-server/oauth/authorize
token-uri: http://localhost:8080/auth-server/oauth/token
user-info-uri: http://localhost:8082/resource-server/users/info
user-name-attribute: user_name
After doing some debugging I've observed that at the end of a successful authentication flow through OAuth2LoginAuthtenticationFilter the framework is storing the obtained access and refresh JWT tokens under OAuth2AuthorizedClient model in memory through the provided InMemoryOAuth2AuthorizedClientService.
I am trying to find out how to override this behaviour so that the tokens can remain available after a server restart. And also keep the user logged in based on this.
Should I just provide a custom OAuth2AuthorizedClientService implementation? How could I configure Spring Security to use it? And should this custom implementation store the tokens in a cookie?
Should I just provide a custom OAuth2AuthorizedClientService
implementation?
I think yes, for solving your use case
How could I configure Spring Security to use it?
From spring doc:
If you would like to provide a custom implementation of
AuthorizationRequestRepository that stores the attributes of
OAuth2AuthorizationRequest in a Cookie, you may configure it as shown
in the following example:
#EnableWebSecurity
public class OAuth2ClientSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.oauth2Client()
.authorizationCodeGrant()
.authorizationRequestRepository(this.cookieAuthorizationRequestRepository())
...
}
private AuthorizationRequestRepository<OAuth2AuthorizationRequest> cookieAuthorizationRequestRepository() {
return new HttpCookieOAuth2AuthorizationRequestRepository();
}
}

Run a Spring Boot oAuth2 application as resource server AND serving web content

I'm using Spring Boot 1.5.13 and with that Spring Security 4.2.6 and Spring Security oAuth2 2.0.15.
I want to find a best practice setup for our Spring Boot applications that serve a mixed set of content: A REST API, and some web pages that provide a convenience "landing page" for developers with some links on it, plus Swagger based API documentation, which is also web content.
I have a configuration that allows me to run the app with proper authorization code flow, hence I can access all web content via Browser and get authenticated by the configured IdP (in my case PingFederate), plus I can make API calls from within the Browser, i.e. directly or with a REST Client, e.g. with RESTClient.
This is my security configuration:
#Slf4j
#Configuration
#EnableWebSecurity
#EnableOAuth2Sso // this annotation must stay here!
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login**", "/webjars/**", "/css/**").permitAll()
.antMatchers("/cfhealth").permitAll()
.antMatchers("/").permitAll()
.antMatchers("/protected", "/api/**").authenticated();
}
#Bean
public RequestContextListener requestContextListener() {
return new RequestContextListener();
}
}
and the oAuth2 configuration:
#Configuration
#Slf4j
public class OAuth2Config extends ResourceServerConfigurerAdapter {
#Value("${pingfederate.pk-uri}")
String pingFederatePublicKeyUri;
#Autowired
PingFederateKeyUtils pingFederateKeyUtils;
#Override
public void configure(ResourceServerSecurityConfigurer config) {
config.tokenServices(tokenServices());
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
String certificate = pingFederateKeyUtils.getKeyFromServer(pingFederatePublicKeyUri);
String publicKey = pingFederateKeyUtils.extractPublicKey(certificate);
converter.setVerifier(pingFederateKeyUtils.createSignatureVerifier(publicKey));
return converter;
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
return defaultTokenServices;
}
}
But when I want to call a REST API programmatically/outside the Browser with a bearer token in the header, e.g. with curl, the authorization code flow kicks in and redirects to the local login endpoint. What I want is that API calls accept the bearer token for authentication, without creating a session, and that all web content/mvc calls in the Browser establish a session.
curl -i -H "Accept: application/json" -H "Authorization: Bearer $TOKEN" -X GET http://localhost:8080/authdemo/api/hello
Adding the #EnableResourceServer annotation to the above SecurityConfig class (and adding security.oauth2.resource.filter-order=3 in the application properties file, I can make the curl command work, but then the authorization code flow is broken, I get the following output in the Browser for all URLs in my application:
<oauth>
<error_description>
Full authentication is required to access this resource
</error_description>
<error>unauthorized</error>
</oauth>
Now is there a way to get this szenario working nicely? If yes, how would that look like? Or is it only supported in later versions of Spring Boot+Security+oAuth2?
The question at Spring Boot with Security OAuth2 - how to use resource server with web login form? is quite similar
I found the solution: It takes multiple HttpSecurity configurations. I found out by reading the great article written by Matt Raible at https://developer.okta.com/blog/2018/02/13/secure-spring-microservices-with-oauth where he introduced me to the notion of requestMatchers(.). This is how I finally implemented it:
#Configuration
#EnableResourceServer
#EnableWebSecurity(debug = true)
#EnableOAuth2Sso
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Bean
public RequestContextListener requestContextListener() {
return new RequestContextListener();
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(new RequestHeaderRequestMatcher("Authorization"))
.authorizeRequests().anyRequest().fullyAuthenticated();
}
}
With that I can access the service with a Browser, leading to a authorization code flow. But accessing the API (or actually any part of the service) leads to a validation of the provided Bearer token.
And to illustrate the way how some endpoints can be exluded/made public in such a case, here's how I configure the actuator endpoints and one very simple 'ping' endpoint I've added myself:
#Configuration
#Order(1)
public class ActuatorSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatcher(new OrRequestMatcher(EndpointRequest.to("health", "info"),
new AntPathRequestMatcher("/cfhealth"))).authorizeRequests().anyRequest().permitAll();
}
}
And my implementation of the /cfhealth endpoint:
#Controller
#Slf4j
public class MainController {
#GetMapping(value = "/cfhealth")
#ResponseBody
public String cfhealth() {
return "ok";
}
}
I'm happy to learn from others if that's the best practice way of Spring Security configuration or if there are better ways to do it. I've spent quite some time on the topic in the last few weeks on it, and it takes quite some effort to grasp the basic Spring Security concepts.

Resources