Has anyone tried using both the newly release Spring Authorization Server 0.1.0 and the regular Spring Resource Server in 1 project and in 1 server such as:
The resource server is at http://localhost:8080 and the authorization server is also at http://localhost:8080? Any ideas on how to do it?
The problem is that at start up, the resource server checks the authorization server's /.well-known/openid-configuration which is obviously not yet avaialble.
Instead of issuer-uri, you can instead specify the jwk-set-uri, which isn't pinged at startup.
Or, since the authorization server and resource server will use the memory space for keys, you might construct your own Nimbus JWTProcessor instead so that you skip the internal HTTP request:
#Bean
JwtDecoder jwtDecoder() {
JWKSource<SecurityContext> source = // ... some internal store for the public keys, e.g. not RemoteJWKSet
ConfigurableJWTProcessor<SecurityContext> processor = new DefaultJWTProcessor<>();
JWSKeySelector<SecurityContext> selector = new JWSVerificationKeySelector(
JWSAlgorithm.RS256, source);
processor.setJWSKeySelector(selector);
NimbusJwtDecoder decoder = new NimbusJwtDecoder(processor);
decoder.setJwtValidator(... add any validation rules ...);
return decoder;
}
I believe you can just set your jwtDecoder as follows:
#Bean
JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
As you probably already have that defined as a Bean elsewhere when setting up your auth server
#Bean
public JWKSource<SecurityContext> jwkSource() {
// implementation ...
}
Related
I am having the same problem configuring spring-security-saml2-core that is presented in this POST. In my case, I have deployed my application in a Weblogic cluster with several managed nodes.
When the authentication flow starts, the user is correctly authorized but on my server (SP) it shows the error: "SAML message intended destination endpoint ..." saying that the request has been made from "https://nodename/" instead of the DNS I want the response from.
As suggested in the post I quoted at the beginning, I have verified that the reply address in Azure is correct.
Also, I have reviewed these posts and I have applied all the solutions they propose, but I still have the problem, I have not found a solution:
SAML Exception Intended destination endpoint did not match: I don't know where to configure the destination endpoint in Spring (because I understand that I don't have that it is not the destination property of the discovery).
Recipient endpoint doesn't match with SAML response: I have configured both SAMLContextProviderLB and MetadataGenerator#setBaseUrl() with the DNS the response has to be returned to.
#Bean
public MetadataGenerator metadataGenerator() {
MetadataGenerator metadataGenerator = new MetadataGenerator();
metadataGenerator.setEntityId(serviceProviderEntityId);
metadataGenerator.setEntityBaseURL("https://my.dns:8993/app");
metadataGenerator.setExtendedMetadata(extendedMetadata());
metadataGenerator.setIncludeDiscoveryExtension(false);
metadataGenerator.setKeyManager(keyManager());
return metadataGenerator;
}
#Bean
public ExtendedMetadata extendedMetadata() {
ExtendedMetadata extendedMetadata = new ExtendedMetadata();
extendedMetadata.setSigningAlgorithm("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
extendedMetadata.setSignMetadata(true);
extendedMetadata.setEcpEnabled(true);
extendedMetadata.setIdpDiscoveryEnabled(false);
return extendedMetadata;
}
#Bean
public SAMLContextProviderLB contextProvider() {
SAMLContextProviderLB samlContextProviderLB = new SAMLContextProviderLB();
samlContextProviderLB.setScheme("https");
samlContextProviderLB.setServerPort(8993);
samlContextProviderLB.setServerName("my.dns");
samlContextProviderLB.setIncludeServerPortInRequestURL(true);
samlContextProviderLB.setContextPath("/app");
log.info("SAMLContextProviderLB ==> {}", samlContextProviderLB);
return samlContextProviderLB;
}
I honestly don't know what else to do. Do I have to do any special configuration in Weblogic? Am I leaving something unconfigured in Azure?
Thanks in advance
I have two RegisteredClients(and two resource servers):
#Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient spa1 = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("client1")
...
.redirectUri("http://127.0.0.1:3000/authorized")
...
.build();
RegisteredClient spa2 = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("client2")
...
.redirectUri("http://127.0.0.1:3001/authorized")
...
.build();
return new InMemoryRegisteredClientRepository(spa1, spa2);
}
When I obtain access_token as client1, I can access endpoints in both resource servers.
I need client1 to be able to access only resourceServer1, and client2 to access resourceServer2.
I went through the docs and source code, but I cannot find a way to configure the client to be able to access only certain resource.
I think this was possible in spring security by doing:
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
ClientDetailsServiceBuilder.ClientBuilder clientBuilder = clients
.inMemory()
.withClient("client1")
.resourceIds("resourceServer1")
...
Is it doable?
This is usually done by means of the aud claim in the access token.
The client will send the intended audience to the authorization server when it requests an access token, and each resource server must make sure to validate that the aud claim in the access token contains the resource server's ID.
If you use Spring Boot's Resource Server starter I think you can configure the audience ID with the spring.security.oauth2.resourceserver.jwt.audiences property (read more about it here.)
Otherwise, you'll have to look into configuring your own JwtDecoder with its own JwtValidator (which is what validates the JWT claims.)
Overview
I am trying to write a program that accesses a public REST API. In order for me to be able to consume it, I need to provide an OAuth2 token.
My App uses Spring Boot 2.4.2 and Spring Cloud version 2020.0.1. The app itself does call the REST API once every 24h, download the data, and stores it in a database. A different microservice consumes this data at some other point and needs the data to have been refreshed daily.
My approach to this is to use OpenFeign to declare the REST Client that consumes the REST API and provide it an OAuth2 token. This is a problem that is quite common, so I assume that machine to machine client_credentials workflow is well documented.
And indeed, I did find a simple example to do this with OpenFeign - here: https://github.com/netshoes/sample-feign-oauth2-interceptor/blob/master/src/main/java/com/sample/feign/oauth2/interceptor/OrderFeignClientConfiguration.java
TL;DR: Trying to write a machine-to-machine microservice requiring an OAuth2 token (client_credentials grant type).
Problem
This was my first try, but unfortunately with the new Spring Security release, I can't seem to get the OAuth2FeignRequestInterceptor instantiated, I might have a package problem. I then went on to study the documentation for Spring Security and the new OAuth2 rewrite, which can be found here: https://docs.spring.io/spring-security/site/docs/5.1.2.RELEASE/reference/htmlsingle/#oauth2client.
Approaches
My approach is to use a RequestInterceptor which injects the current OAuth2 token into the request of the OpenFeign client, by adding an Authorization Bearer header. My assumption is that I can retrieve this, more or less automagically, using the Spring Security OAuth2 layer.
Using the documentation I tried providing a bean of OAuth2RegisteredClient to my interceptor, as well as a bean of type OAuth2AccessToken - which both didn't work. My last try looked like this and is to be seen, as a sort of hail mary, kind of approach:
#Bean
public OAuth2AccessToken apiAccessToken(
#RegisteredOAuth2AuthorizedClient("MY_AWESOME_PROVIDER") OAuth2AuthorizedClient authorizedClient) {
return authorizedClient.getAccessToken();
}
This doesn't work because RegisteredOAuth2AuthorizedClient requires a user session, lest it is null. I also saw someone else on Stackoverflow trying the same approach, but they actually did it in a Controller (=> Resolving OAuth2AuthorizedClient as a Spring bean)
I also tried some approaches that I have found here on SO:
Feign and Spring Security 5 - Client Credentials (Provided answer uses Spring Boot 2.2.4 - thus not relevant anymore)
Alternative For OAuth2FeignRequestInterceptor as it is deprecated NOW another gentleman looking for an alternative for OAuth2FeignRequestInterceptor
OAuth2FeignRequestInterceptor class deprecated in Spring Boot 2.3 - solution here again required an active user-session
https://github.com/jgrandja/spring-security-oauth-5-2-migrate this Github repo pops up every now and then, I studied it, but I deem it irrelevant to my question - maybe I missed something? From what I understood, this sample application has multiple providers using multiple scopes - but still a user that triggers a login and thus the automagic generation of an OAuth2 token through Spring Security. (also featured in this question: Migrating from Spring Boot Oauth2 to Spring Security 5) [1]
https://github.com/spring-cloud/spring-cloud-openfeign/issues/417 -> as of right now there is no replacement for OAuth2FeignRequestInterceptor
My assumption is that I can somehow use Spring Security 5 to solve this, but I simply can't wrap my head around how to actually do it. It seems to me that most of the tutorials and code samples I have found actually require a user-session, or are outdated with Spring Security 5.
It really seems that I am missing something and I hope that somebody can point me in the right direction, towards a tutorial or written documentation on how to achieve this.
In depth example
I tried supplying an OAuth2AuthorizedClientManager as seen in this example (https://github.com/jgrandja/spring-security-oauth-5-2-migrate).
For this, I registered an OAuth2AuthorizedClientManager following the example code:
#Bean
public OAuth2AuthorizedClientManager authorizedClientManager(ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken()
.clientCredentials()
.password()
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
and provided it for my RequestInterceptor as can be seen here:
#Bean
public RequestInterceptor requestInterceptor(OAuth2AuthorizedClientManager clientManager) {
return new OAuthRequestInterceptor(clientManager);
}
Finally I wrote the interceptor, which looks like this:
private String getAccessToken() {
OAuth2AuthorizeRequest request = OAuth2AuthorizeRequest.withClientRegistrationId(appClientId)
// .principal(appClientId) // if this is not set, I receive "principal cannot be null" (or empty)
.build();
return Optional.ofNullable(authorizedClientManager)
.map(clientManager -> clientManager.authorize(request))
.map(OAuth2AuthorizedClient::getAccessToken)
.map(AbstractOAuth2Token::getTokenValue)
.orElseThrow(OAuth2AccessTokenRetrievalException::failureToRetrieve);
}
#Override
public void apply(RequestTemplate template) {
log.debug("FeignClientInterceptor -> apply CALLED");
String token = getAccessToken();
if (token != null) {
String bearerString = String.format("%s %s", BEARER, token);
template.header(HttpHeaders.AUTHORIZATION, bearerString);
log.debug("set the template header to this bearer string: {}", bearerString);
} else {
log.error("No bearer string.");
}
}
When I run the code, I can see "FeignClientInterceptor -> apply called" output in the console, followed by an Exception:
Caused by: java.lang.IllegalArgumentException: servletRequest cannot be null
My assumption is that I receive this, because I don't have an active user session. It seems to me thus, that I absolutely need one to fix this problem - which I don't have in machine-to-machine communcations.
This is a common use-case so I am sure I must have made a mistake at some point.
Used packages
Maybe I made a mistake with my packages?
implementation 'org.springframework.boot:spring-boot-starter-amqp'
implementation 'org.springframework.boot:spring-boot-starter-jooq'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
According documentation need use AuthorizedClientServiceOAuth2AuthorizedClientManager instead of DefaultOAuth2AuthorizedClientManager
When operating outside of the context of a HttpServletRequest, use AuthorizedClientServiceOAuth2AuthorizedClientManager instead.
So. I was playing with your solution in my free time. And found the simple solution:
just add SecurityContextHolder.getContext().authentication principle to your code OAuth2AuthorizeRequest request = OAuth2AuthorizeRequest.withClientRegistrationId(appClientId).build();
Should be like this:
val request = OAuth2AuthorizeRequest
.withClientRegistrationId("keycloak") // <-- here your registered client from application.yaml
.principal(SecurityContextHolder.getContext().authentication)
.build()
Used packages:
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.cloud:spring-cloud-starter-openfeign")
implementation("org.springframework.boot:spring-boot-starter-oauth2-client")
application.yaml:
spring:
security:
oauth2:
client:
registration:
keycloak: # <--- It's your custom client. I am using keycloak
client-id: ${SECURITY_CLIENT_ID}
client-secret: ${SECURITY_CLIENT_SECRET}
authorization-grant-type: client_credentials
scope: openid # your scopes
provider:
keycloak: # <--- Here Registered my custom provider
authorization-uri: ${SECURITY_HOST}/auth/realms/${YOUR_REALM}/protocol/openid-connect/authorize
token-uri: ${SECURITY_HOST}/auth/realms/${YOUR_REALM}/protocol/openid-connect/token
feign:
compression:
request:
enabled: true
mime-types: application/json
response:
enabled: true
client.config.default:
connectTimeout: 1000
readTimeout: 60000
decode404: false
loggerLevel: ${LOG_LEVEL_FEIGN:basic}
SecurityConfiguration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
class SecurityConfiguration() : WebSecurityConfigurerAdapter() {
#Throws(Exception::class)
override fun configure(http: HttpSecurity) {
// #formatter:off
http
.authorizeRequests { authorizeRequests ->
authorizeRequests
.antMatchers(HttpMethod.GET, "/test").permitAll() // Here my public endpoint which do logic with secured client enpoint
.anyRequest().authenticated()
}.cors().configurationSource(corsConfigurationSource()).and()
.csrf().disable()
.cors().disable()
.httpBasic().disable()
.formLogin().disable()
.logout().disable()
.oauth2Client()
// #formatter:on
}
#Bean
fun authorizedClientManager(
clientRegistration: ClientRegistrationRepository?,
authorizedClient: OAuth2AuthorizedClientRepository?
): OAuth2AuthorizedClientManager? {
val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder
.builder()
.clientCredentials()
.build()
val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(clientRegistration, authorizedClient)
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
return authorizedClientManager
}
}
FeignClientConfiguration:
private val logger = KotlinLogging.logger {}
class FeignClientConfiguration(private val authorizedClientManager: OAuth2AuthorizedClientManager) {
#Bean
fun requestInterceptor(): RequestInterceptor = RequestInterceptor { template ->
if (template.headers()["Authorization"].isNullOrEmpty()) {
val accessToken = getAccessToken()
logger.debug { "ACCESS TOKEN TYPE: ${accessToken?.tokenType?.value}" }
logger.debug { "ACCESS TOKEN: ${accessToken?.tokenValue}" }
template.header("Authorization", "Bearer ${accessToken?.tokenValue}")
}
}
private fun getAccessToken(): OAuth2AccessToken? {
val request = OAuth2AuthorizeRequest
.withClientRegistrationId("keycloak") // <- Here you load your registered client
.principal(SecurityContextHolder.getContext().authentication)
.build()
return authorizedClientManager.authorize(request)?.accessToken
}
}
TestClient:
#FeignClient(
name = "test",
url = "http://localhost:8080",
configuration = [FeignClientConfiguration::class]
)
interface TestClient {
#GetMapping("/test")
fun test(): ResponseEntity<Void> // Here my secured resource server endpoint. Expect 204 status
}
I was working on getting a client credential flow with Auth0 to work using Spring Security 5.4.1. I created a little demo application for reference: https://github.com/mathias-ewald/spring-security-auth0-clientcredentials-demo
Everything works fine, but I was wondering how to handle multiple OAuth2 clients. As far as I understand, the configuration made in OAuth2ClientSecurityConfig is valid for all client credential flows to any provider, correct?
What if I have another provider and don't want to convert RequestEntity in the same way?
There's usually no perfect answer for multi-tenancy since a lot depends on how early in the request you want to fork the behavior.
In Spring Security's OAuth 2.0 Client support, the ClientRegistration is the tenant, and that tenant information is available in most of the client APIs.
For example, your Auth0RequestEntityConverter could have different behavior based on the ClientRegistration in the request:
public RequestEntity<?> convert(
OAuth2ClientCredentialsGrantRequest request) {
ClientRegistration client = request.getClientRegistration();
if (client ...) {
} else if (client ...) {
} ...
}
Or, if you need to configure more things than the request entity converter, you could instead fork the behavior earlier by constructing a OAuth2AuthorizedClientManager for each provider:
public class ClientsOAuth2AuthorizedClientManager implements OAuth2AuthorizedClientManager {
private final Map<String, OAuth2AuthorizedClientManager> managers;
// ...
public OAuth2AuthorizedClient authorize(OAuth2AuthorizeRequest request) {
String clientRegistrationId = request.getClientRegistrationId();
return this.managers.get(clientRegistrationId).authorize(request);
}
}
I'm configuring a Spring Cloud (Angel.SR6) application using the Zuul reverse proxy utility, in order to hide the internal service ports. My zuul (edge) service is published in the 8765 port and my organizations service is in the 8083 one. Everything goes smoothly when I access the application with no security, http://localhost:8765/organization/organizations returns the JSON with all the organizations.
However, now I want to integrate a Keycloak SSO (OAuth2) server for authorization purposes. I have added the Spring Security adapter in my organization service and configured it to authenticate in http://localhost:8080/auth. Everything goes well, except that zuul performs a redirection instead of proxying. So when authentication is successful, I get redirected to http://localhost:8083/organizations instead of http://localhost:8765/organization/organizations. Here there are my browser requests:
That's because the keycloak adapter creates a token verification endpoint in the http://localhost:8083/sso/login, from which it performs a redirection to the authorization server in order to validate the token. When authorization server acknowledges it, a redirection is sent to the organization service, with the /organization path, so the end url being loaded is http://localhost:8083/organizations. But I would like the first requested url to be loaded instead.
Which choice do I have?
Recently I've had the same problem. I've solved it by:
Add to application.properties in Zuul
zuul.sensitive-headers=Cookie,Set-Cookie
Introduce KeycloakFilterRoute in Zuul
class KeycloakFilterRoute extends ZuulFilter {
private static final String AUTHORIZATION_HEADER = "authorization";
#Override
public String filterType() {
return "route";
}
#Override
public int filterOrder() {
return 0;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
if (ctx.getRequest().getHeader(AUTHORIZATION_HEADER) == null) {
addKeycloakTokenToHeader(ctx);
}
return null;
}
private void addKeycloakTokenToHeader(RequestContext ctx) {
RefreshableKeycloakSecurityContext securityContext = getRefreshableKeycloakSecurityContext(ctx);
if (securityContext != null) {
ctx.addZuulRequestHeader(AUTHORIZATION_HEADER, buildBearerToken(securityContext));
}
}
private RefreshableKeycloakSecurityContext getRefreshableKeycloakSecurityContext(RequestContext ctx) {
if (ctx.getRequest().getUserPrincipal() instanceof KeycloakAuthenticationToken) {
KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) ctx.getRequest().getUserPrincipal();
return (RefreshableKeycloakSecurityContext) token.getCredentials();
}
return null;
}
private String buildBearerToken(RefreshableKeycloakSecurityContext securityContext) {
return "Bearer " + securityContext.getTokenString();
}
}
(Migrated from comment to answer)
I ended up making a Github project in order to explain my problem to the keycloak team, and got a pull request from one of the development team members trying to help me out. Following their recommendations, I came into the conclusion that zuul is good to hide stateless services (bearer only ones), but not the ones that user directly interacts with. Here it is the whole thread in the mailing list.