Spring Cloud Zuul gateway 401 basic authentication - spring-cloud

I'm working on a Spring Cloud Zuul gateway to put in front of my spring boot application.
I use basic authorization on the applications side.
When I do a call to the gateway with the proper authorization header I always get 401 Unauthorized
"status": 401,
"error": "Unauthorized",
"message": "Full authentication is required to access this resource"
But when I do the request directly towards the application it works.

Specifying sensitive-headers property without Authorization value in Zuul routes will forward the Authorization header towards the application.
By default it has hese values: Cookie,Set-Cookie,Authorization
bootstrap.yml:
zuul:
ignoredServices: '*'
routes:
application:
path: /application/**
serviceId: application
sensitive-headers: Cookie,Set-Cookie
More info:
https://github.com/spring-cloud/spring-cloud-netflix/blob/master/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ZuulProperties.java#L118

Related

Springboot 2.7 with oidc bearer token always redirects to login page

I have a client Springboot app which needs to access an oidc-protected REST service, so no UI component or UI login. I have the following yaml in the client:
spring:
security:
oauth2:
client:
registration:
my-service:
client-id: client-id
client-secret: client-secret
authorization-grant-type: client_credentials
provider:
my-service:
token-uri: https://mytokenhost.mydomain/token
which points to a Keycloak server on which I have configured an oidc client with a service account and enabled it. I use a WebClient to connect to the REST service which is configured like:
#Bean
public WebClient webClient(final OAuth2AuthorizedClientManager authorizedClientManager) {
final ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth2Client.setDefaultClientRegistrationId("my-service");
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build();
}
I also needed to manually define an OAuth2AuthorizedClientManager for the WebClient to work:
#Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
final ClientRegistrationRepository clientRegistrationRepository,
final OAuth2AuthorizedClientService authorizedClientService) {
final OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder().refreshToken().clientCredentials().build();
final AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
new AuthorizedClientServiceOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientService);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
And then use the WebClient to make a call to the REST service:
final String s = webClient
.method(HttpMethod.GET)
.uri("http://localhost:8080/my-rest-service/service?param1=value")
.attributes(ServletOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId(
"catalogue-services"))
.retrieve()
.bodyToMono(String.class)
.block();
Via IntelliJ I can see I get a token back, however no matter what I do Springboot redirects to the login page of the REST service. The REST service has yaml:
spring:
security:
oauth2:
client:
registration:
my-service:
client-id: client-id
client-secret: my-secret
authorization-grant-type: client_credentials
provider:
my-service:
token-uri: https://mytokenhost.mydomain/token/openid-connect/token
and a security configuration in the REST service:
#Bean
public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception {
http.authorizeRequests(
a ->
a.antMatchers("/", "/error", "/someUrl")
.permitAll()
.anyRequest()
.authenticated())
.oauth2Login();
return http.build();
}
What is happening is that after successfully authenticating against KeyCloak, the REST service security filter is flagging the authentication as an anonymous login, presumably because the role is ROLE_ANONYMOUS and/or principal is anonymousUser. I can see the service account user name come back, the roles are included as well but maybe not being picked up. I have a realm role which is exposed in the token realm_access.roles and a client role which is exposed in resource_access.my-service.roles. When I debug the decision voting in the AffirmativeBase class I get:
AnonymousAuthenticationToken [Principal=anonymousUser,
Credentials=[PROTECTED], Authenticated=true,
Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=null],
Granted Authorities=[ROLE_ANONYMOUS]]
I haven't used the Keycloak adapter since they look to be deprecated and the documentation still uses the WebSecurityConfigurerAdapter which is also deprecated in the Spring Security model.
It's likely I'm missing something really simple, but if anyone has done machine-to-machine oidc with Springboot and Keycloak and knows any tricks, any help would be appreciated.
If by "REST service" you mean a spring #RestControtroller (or #Controller with #ResponseBody), then it is an OAuth2 resource-server, not a client (like you configured in your "REST service" yaml file).
You can have a look at those tutorials which provide with OAuth2 concepts you need and sample configurations for resource-servers.

Register confidential OIDC client through registration endpoint

I would like to programmatically register confidential OIDC client (the client is a backend service). I checked the keycloak document about it. And I use the “Initial access token“ approach as recommended.
After I created a "initial access token" on the UI console, I register a new client by:
POST https://my-keycloak-host/auth/realms/MyRealm/clients-registrations/default
Headers: Authorization: Bearer <initial access token>
{ "clientId": "my-client" }
The response contains a registrationAccessToken. But I expect to get a client secret. How can I get it? And what is the usage for that registrationAccessToken?
For some unknown reason Keycloak doesn't set secret properly. But you can define own secret with secret property in the payload (tested with Keycloak 16.1.1), e.g.:
{
"clientId": "my-client",
"secret": "my-secret"
}
Doc:
https://openid.net/specs/openid-connect-registration-1_0.html
https://www.keycloak.org/docs/latest/securing_apps/index.html#_client_registration

Hoxton.SR6 with Spring Boot 2.3.0 cause weird content type issue

When i updated my Spring Boot 2.3.0.RELEASE Application from Hoxton.SR4 to Hoxton.SR6 (the only change), i started facing weird issues with Content Type from various endpoints.
For example, with Hoxton.SR4, any unauthorized endpoint was returning this error:
{
"error": "unauthorized",
"error_description": "Full authentication is required to access this resource"
}
Same authorization server error in Hoxton.SR6 comes in XML
<UnauthorizedException>
<error>unauthorized</error>
<error_description>Full authentication is required to access this resource</error_description>
</UnauthorizedException>
many other endpoints are returning content in XML rather than application/json.
Can someone help with fixing the default content type for Spring Cloud Hoxton.SR6?

Spring Boot OAuth 2 SSO how to extract token to pass back to thick client application

I have a thick client application (C# but that should not matter).
All the users already exist in an authentication/authorization (3rd party) system that provides OAuth 2 API (authorize/access_token plus a user_info service).
I have a Spring Boot web service tier that will have RESTful web services that will be called by the thick client application that must only be called by authenticated users for protected web services.
To authenticate the thick client will launch a Web Browser (OS installed default) and will open https to restful.web.server:8443 /login of the Spring Boot web service tier. This will do the OAuth 2 (authorization_code) interaction. Once redirected back with a valid token I want to redirect to a custom URI passing the token and for the browser to close (if possible) so an OS registered application can extract the token and pass it via an IPC mechanism to the thick client application.
The thick client application can then pass the token to the Web Services in the header (Authorize: TOKEN_TYPE TOKEN_VALUE).
The Web Services must then validate the authenticity of the token.
The Web Services if called with an invalid token must just return an HTTP error and JSON error content (e.g. code+message) and not try and redirect to the login screen. This will be orchestrated by the thick client application.
I have no concern with any of the custom URI handling, IPC development, or thick client web service calls. It is all the Spring/SSO magic in getting the token to be sent to my thick client and returning the relevant error from protected web services without returning a redirect to the SSO login.
I appear to be authenticating and being sent a token but then I get an exception.
I have made some progress and it appears that by manually launching a browser and hitting my web service tier https to restful.web.server:8443 /login it redirects to the SSO site https to 3rdparty.sso.server /oauth/authorization (passing in client_id, redirect_uri, response_type=code, state). I can log in, and Spring is calling the https to 3rdparty.sso.server /oauth/access_token endpoint (I had to create a custom RequestEnhancer to add in Authorization: Basic ENCODED_CLIENT_ID_AND_CLIENT_SECRET to satisfy the access_token SSO API requirement).
This returns 200 OK but then I get exceptions and do not know how to extract the token. The access_token returned may not be using the standard property names but unsure when to go and check if this is the case. I done the authentication this way to keep the client id and client secret out of the thick client application and my web services must do the authorisation anyway. If there is a better way or pointers to someone else doing this already it would be greatly appreciated. I find so many examples that are either not quite relevant or more towards web applications.
server:
port: 8443
ssl:
key-store: classpath:keystore.p12
key-store-password: **********
keyStoreType: PKCS12
keyAlias: tomcat
servlet:
context-path: /
session:
cookie:
name: UISESSION
security:
basic:
enabled: false
oauth2:
client:
clientId: *******
clientSecret: *****************
accessTokenUri: https://3rdparty.sso.server/oauth2/access_token
userAuthorizationUri: https://3rdparty.sso.server/oauth2/authorize
authorizedGrantTypes: authorization_code,refresh_token
scope:
tokenName: accessToken
redirectUri: https://restful.web.server:8443/login
authenticationScheme: query
clientAuthenticationScheme: header
resource:
userInfoUri: https://3rdparty.sso.server/oauth2/userinfo
logging:
level:
org:
springframework: DEBUG
spring:
http:
logRequestDetails: true
logResponseDetails: true
#Configuration
#EnableOAuth2Sso
#Order(value=0)
public class ServiceConectWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// From the root '/' down...
.antMatcher("/**")
// requests are authorised...
.authorizeRequests()
// ...to these url's...
.antMatchers("/", "/login**", "/debug/**", "/webjars/**", "/error**")
// ...without security being applied...
.permitAll()
// ...any other requests...
.anyRequest()
// ...the user must be authenticated.
.authenticated()
.and()
.formLogin().disable()
.logout()
.logoutSuccessUrl("/login")
.permitAll()
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
;
}
I expect that the secured web services would be accessible once authenticated via the browser whilst testing without the client and would not expect exceptions to be thrown. I need to be able to extract the returned token and pass it back to my thick client.
Redirects to 'https://3rdparty.sso.server/oauth2/authorize?client_id=***HIDDEN_CLIENT_ID***&redirect_uri=https://localhost:8443/login&response_type=code&state=***HIDDEN_STATE_1***'
Then FilterChainProxy : /login?code=***HIDDEN_CODE_1***&state=***HIDDEN_STATE_1*** at position 6 of 12 in additional filter chain;
Request is to process authentication
RestTemplate : HTTP POST https://3rdparty.sso.server/oauth2/access_token
RestTemplate : Response 200 OK
IllegalStateException: Access token provider returned a null access token, which is illegal according to the contract.
at OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:223) ```
Then end up at an error page
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
There was an unexpected error (type=Internal Server Error, status=500).
Access token provider returned a null access token, which is illegal according to the contract.
The access_token service was returning non-standard JSON names.
I created a MyOwnOAuth2AccessToken with the relevant non-standard JSON names the necessary de/serialisation classes.
I created a MyOauth2AccesTokenHttpMessageConverter class for returning my OAuth2AccessToken.
The MyOauth2AccesTokenHttpMessageConverter was plumbed in from an
#Configuration
public class ServiceConnectUserInfoRestTemplateFactory implements UserInfoRestTemplateFactory
within the
#Bean
#Override
public OAuth2RestTemplate getUserInfoRestTemplate()
method with the following code:
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
messageConverters.add(new ItisOAuth2AccessTokenHttpMessageConverter());
messageConverters.addAll((new RestTemplate()).getMessageConverters());
accessTokenProvider.setMessageConverters(messageConverters);
There is probably a better way to do this but this worked for me.

Access headers in AWS API Gateway using HTTP Proxy?

I'm using AWS API Gateway and it's HTTP Proxy,
I need to pass Authorization header to my endpoint through AWS API Gateway
Things I've tried:
Setting Method Request like so,
Integration Request setup
This doesn't work, my app doesn't receive the Authorization header,
Also I've tried using mapping template
{
"method": "$context.httpMethod",
"body" : $input.json('$'),
"headers": {
#foreach($param in $input.params().header.keySet())
"$param": "$util.escapeJavaScript($input.params().header.get($param))" #if($foreach.hasNext),#end
#end
},
"queryParams": {
#foreach($param in $input.params().querystring.keySet())
"$param": "$util.escapeJavaScript($input.params().querystring.get($param))" #if($foreach.hasNext),#end
#end
},
"pathParams": {
#foreach($param in $input.params().path.keySet())
"$param": "$util.escapeJavaScript($input.params().path.get($param))" #if($foreach.hasNext),#end
#end
}
}
This also doesn't work.
How can this be accomplished?
API Gateway strips the AWS SigV4 Authorization header due to security reasons. If you are using other Authorization mechanism like OAuth, the header wouldn't be stripped.
Recently I had to try using an API Gateway HTTP proxy to pass an AWS SigV4 HTTP request to an endpoint.
After testing and debugging found that the Authorization is being consumed and not passed!
So while sending the request to the API Gateway - I sent Authorization and a copy of the Authorization as another header "myauth". (I was able to do this since the request is coming from my own client.)
In the method request I added Authorization and myauth as HTTP Headers
Method Request - HTTP Headers
In the Integration Request - HTTP Headers I mapped myauth to Authorization before it was forwarded to the endpoint
Integration Request - HTTP Headers
Dont know if this is the best way to do this or if there could be any potential issues but this worked! Hope this helps someone or gives some ideas.