Spring cloud Zuul pass JWT downstream - java

I've read a bunch of questions around this but none seem to be similar to my edge-case where I already have my JWT.
I'm using the Auth0 (note auth-zero, not Oauth) in my frontend to gain a JWT which is loaded with scopes and authentication for my backend. When I login to my frontend client I get a nice JWT with an access_token. If I copy that token I can make a direct curl request to my backend microservices
curl -X GET -H "Authorization: Bearer TOKEN_HERE" -H "Cache-Control: no-cache" "http://192.168.0.109:39885"
And this works as expected, I get a 200 response. Nice.
Now when I try the same curl request through my Zuul proxy I get a nasty 401.
The configuration I have for my gateway is:
#EnableHystrix
#EnableZuulProxy
#EnableEurekaClient
#SpringBootApplication
public class EdgeServiceApplication {
public static void main(String[] args) {
SpringApplication.run(EdgeServiceApplication.class, args);
}
}
Now reading the documentation and this conversation from the brilliant Dr Syer I know I need to allow the headers to go downstream which I've done:
zuul:
sensitiveHeaders:
routes:
instances:
path: /instances/**
serviceId: instance-service
restore:
path: /restore/**
serviceId: restore-service
Setting sensitiveHeaders empty should allow everything (for testing of course).
Looking further at the docs I see I need to add #EnableOAuth2Sso to my Zuul configuration. This is where I get confused/things break.
From what I know, #EnableOAuth2Sso is for generating and validating tokens. I don't want to do that. I already have my nice token ready for my microservice (which validates it down there).
How do I tell Zuul to not mess with my JWTs and just send them along?

I've solved this, there was to many things wrong with my code to detail but the gist of the issue was:
I was trying to send the entire JWT to the microservice when I should have just been sending the access_token
When I tried sending the access_token, ember-simple-auth0 actually sends the id_token by default
I needed to configure Zuul to pass CORS requests straight to the microservices
Once I started sending the access_token rather than the id_token it was easy to start debugging the issue.
To tell ember-simple-auth0 to use the access_token instead add a new authorizer with the following:
// app/authorizers/application.js
import Ember from 'ember';
import BaseAuthorizer from 'ember-simple-auth/authorizers/base';
const {
isPresent,
debug
} = Ember;
export default BaseAuthorizer.extend({
authorize(sessionData, block) {
let userToken = sessionData['accessToken'];
if (isPresent(userToken)) {
block('Authorization', `Bearer ${userToken}`);
} else {
debug('Could not find the authorization token in the session data for the jwt authorizer.');
}
}
});
Then remember to tell your adapter to use the new authorizer:
export default DS.JSONAPIAdapter.extend(DataAdapterMixin, {
authorizer: 'authorizer:application',
});
To forward CORS to your microservices use:
spring:
mvc:
dispatch-options-request: true
And making sure you're not stripping the headers from the request with:
zuul:
sensitiveHeaders:
routes:
instances:
path: /instances/**
serviceId: instance-service
stripPrefix: false
restore:
path: /restore/**
serviceId: restore-service
stripPrefix: false
Hopefully someone finds this useful.

Related

Getting the 307 Redirection Response instead of the target-location page content from an enpoint in Spring Boot

So I have a simple endpoint in my Spring Boot App, which just redirects to another website:
#Controller
public class FeedbackController {
#GetMapping(path = "/test")
public Mono<ResponseEntity<Void>> method() {
String redirectUrl = "https://www.google.com";
return Mono
.just(ResponseEntity.status(HttpStatus.TEMPORARY_REDIRECT).location(URI.create(redirectUrl)).build());
}
Making a GET request to this endpoint (e.g. in browser or with postman) gives me the page content of google as a response. However for testing purposes I want to make sure that the response is a TEMPORARY_REDIRECT with www.google.com as the Location Header.
How can I make a request to this endpoint so that the response is the 307 TEMPORARY_REDIRECT instead of the 200 with page content from the target website?
First
to test if its working, you could use simple tools like curl :
We add the -L flag to tell curl that we want to know if we are getting redirected
curl -L http://www.....
Go further
Then, you could simply use tools like MockMvc to automate this test
See this SO post

How to resolve CORS issue in Angular 12?

I am working on an Angular project and I have login page and during submitting login API, I am facing CORS error. I am attaching screenshot of it also. Any suggestion would be appreciated.
API Service.ts:
constructor( private http: HttpClient ) { }
httpOptionsPost = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Allow-Methods' : 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers' : 'Origin, Content-Type, Accept, X-Custom-Header, Upgrade-Insecure-Requests',
})
};
httpOptionsGet = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
})
};
_login(username, password) {
const url = this.domain + '/api/login?username='+username+'&password='+password;
return this.http.post(url, {}, this.httpOptionsPost);
}
Login Component:
this.apiService._login(data.username, data.password).subscribe((resp: any) => {
const resobj = JSON.parse(JSON.stringify(resp));
console.log(resobj);
})
You need to set up proxy configuration for running the app locally.
A short example is:
// create a file proxy.config.json
{
"/api": {
"target": "http://example.com/", // put your actual domain
"secure": false // true or false depending on your needs.
}
}
Then run your project passing the proxy conf to ng-serve or configure it in the package.json.
For instance:
ng serve --proxy-config proxy.conf.json
or
npm start -- --proxy-config proxy.conf.json
Or simply configure it in the angular.json.
Then, in your code, remove the usage of the domain, or assign the domain to localhost using environment.dev.ts if the domain is not the same as where you are going to deploy you app.
I'm referring to the domain variable in this block.
_login(username, password) {
const url = this.domain + '/api/login?username='+username+'&password='+password;
return this.http.post(url, {}, this.httpOptionsPost);
}
More details in https://www.positronx.io/setting-up-angular-proxy-configuration-via-angular-json/ and / or in the official webpack site for advanced proxy configurations
Cors preflight should be handled at server side code. In your case try adding below annotation onto your controller class.
#CrossOrigin("*")
#PostMapping("/user")
public User userController(){
}
And hope you have a configuration class for handling the CORS, if not try adding one. Sample code for reference.
#Configuration
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
List<String> allowedMethods=new ArrayList<>();
allowedMethods.add("GET");
allowedMethods.add("POST");
allowedMethods.add("PUT");
allowedMethods.add("DELETE");
CorsConfiguration cors=new CorsConfiguration();
cors.setAllowedMethods(allowedMethods);
http.cors().configurationSource(request -> cors.applyPermitDefaultValues());
http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests().requestMatchers(EndpointRequest.to("loggers")).hasRole("ADMIN").and().httpBasic();
}}
in your question , the pc_coder answer is right:
cors is related with backend. Be sure you allowed from api to reach requests
like the link:
How does Access-Control-Allow-Origin header work?
I'm not quite familiar with Angular. However, I had the same CORS issue with Vue.js and the solution is to change the add these lines :
module.exports = {
devServer: {
proxy: 'https://yourdomain'
}
}
and also replace the https://yourdomain/login to localhost when you do .get or .post, for example : 'http://localhost:8082/login'
I'm not sure if this would work for you since I am also very new at this, but it did work for me.
Before browser makes a call to your API URL from the angular client application, it will send a preflight request (An HTTP OPTIONS call). this will happen only when your client side application and server side applications are residing in different sub domains or domains. It is the responsibility of the server side application to control whether the cross origin request should be served or not. I see that you are setting the CORS headers in your request header. but it should be received from the API server. so remove that from the angular end and make the proper changes in the server side code.
By default, the Same-Origin-Policy (SOP) forbids request to a different resource. Everything should be loaded from the same source. This is to prevent websites to load data from different servers without the users knowledge. However, there are circumstances where this is required. To cover such case and still provide high safety measurements, CORS was created.
With CORS it is possible to send requests to a server different than the original one.
For this the webserver which is receiving the request must be configured accordingly. In your case this means, you have to adjust the CORS settings in the application which is providing the login API you are calling and allow localhost:4200 to be able to send CORS requests.

How to configure Thorntail 2.5.0.Final to authorize users with JWT token from Keycloak?

I have problems authorizing users via Bearer TOKEN that I receive from Keycloak.
The task is to authorize user requests that come from an Angular application to my back-end Thorntail 2.5.0.Final micro-service. I have the front-end part covered and the application appends Authorization: Bearer {TOKEN} to every request to my service.
I have tried following these 2 guides:
https://rieckpil.de/howto-microprofile-jwt-authentication-with-keycloak-and-react/
https://kodnito.com/posts/microprofile-jwt-with-keycloak/
with thorntail microprofile and keycloak-micropfofile-jwt-fractions, but none of them seem to work.
#Inject
#ConfigProperty(name = "message")
private String message;
#Inject
private JsonWebToken callerPrincipal;
#GET
#RolesAllowed("testrole")
#ApiOperation(value = "Pridobi uporabnike", notes = "Pridobi vse uporabnike iz baze.", response = Uporabnik.class)
public Response getUsers() {
return Response.ok(callerPrincipal.getRawToken() + " is allowed to read message: " + message).build();
}
and got the following response
null is allowed to read message: Very Secure 42!
The 2. thing I tried is adding the keycloak fraction and sending the token via header following this example https://github.com/thorntail/thorntail-examples/tree/master/security/keycloak
I added the resources/keycloak.json
{
"realm": "Intra",
"auth-server-url": "https://idm.ra.net/auth",
"ssl-required": "external",
"resource": "prenosOSBE",
"verify-token-audience": true,
"credentials": {
"secret": "e9709793-9333-40a7-bb95-2026ad98b568"
},
"use-resource-role-mappings": true,
"confidential-port": 0
}
and the KeycloakSecurityContextFilter.java from the example.
If I try to make a call to my endpoint I get 401 Unauthorized or 403 Forbidden if I don't send a token with my request.
So what I want to know is which fraction is meant to be used if my task is to authorize users via Bearer token on my Thorntail microservice?
microprofile-jwt, keycloak-microprofile-jwt or keycloak and what is the minimal required configuration for it to work?
The keycloak fraction is the Keycloak adapter for WildFly per https://www.keycloak.org/docs/4.8/securing_apps/index.html#jboss-eap-wildfly-adapter It lets you use the common security mechanisms from Java EE (<security-constraint>s in web.xml etc.) You can see an example here: https://github.com/rhoar-qe/thorntail-test-suite/tree/master/wildfly/keycloak
The microprofile-jwt lets you use bare MicroProfile JWT (that is, #RolesAllowed on JAX-RS resources, etc.). You have to configure the expected issuer, its public key etc., as described in MP JWT documentation. You can see an example here: https://github.com/rhoar-qe/thorntail-test-suite/tree/master/microprofile/microprofile-jwt-1.0
The keycloak-microprofile-jwt is a bit of a mix. It doesn't expose the Keycloak adapter, but uses it internally to validate tokens issued by Keycloak, and exposes the tokens via MicroProfile JWT. You can see an example here: https://github.com/thorntail/thorntail/tree/master/testsuite/testsuite-keycloak-mpjwt

Authorization header not passed by ZuulProxy starting with Brixton.RC1

In switching from Spring Cloud Brixton.M5 to Brixton.RC1 my ZuulProxy no longer passes Authorization headers downstream to my proxied services.
There's various actors in play in my setup, but most all of them are fairly simple:
- AuthorizationServer: runs separately; hands out JWTs to clients
- Clients: get JWTs from OAuth server; each with access to a subset of resources.
- ResourceServers: consume JWTs for access decisions
- MyZuulProxy: proxies various resource servers; should relay JWTs.
It should be noted that MyZuulProxy has no security dependencies whatsoever; It passed the Authorization: Bearer {JWT} header it receives to the ResourceServers, pre-RC1. MyZuulProxy is explicitly not a Client itself, and does not use #EnableOAuth2SSO or similar at the moment.
What could I do to get MyZuulProxy to relay the JWTs to the ResourceServers again when using Spring Cloud Brixton.RC1?
There's very little code to post: It's just #EnableZuulProxy, #EnableAuthorizationServer and #EnableResourceServer in three different jars. My Clients are not Spring applications.
Update: Fixed in https://github.com/spring-cloud/spring-cloud-netflix/pull/963/files
Sensitive headers can also be set globally setting zuul.sensitiveHeaders. If sensitiveHeaders is set on a route, this will override the global sensitiveHeaders setting.
So use:
# Pass Authorization header downstream
zuul:
sensitiveHeaders: Cookie,Set-Cookie
So pending a fix for https://github.com/spring-cloud/spring-cloud-netflix/issues/944, jebeaudet was kind enough to provide a workaround:
#Component
public class RelayTokenFilter extends ZuulFilter {
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
// Alter ignored headers as per: https://gitter.im/spring-cloud/spring-cloud?at=56fea31f11ea211749c3ed22
Set<String> headers = (Set<String>) ctx.get("ignoredHeaders");
// We need our JWT tokens relayed to resource servers
headers.remove("authorization");
return null;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 10000;
}
}
Set the sensitiveHeaders globally helped me solve the issue
zuul:
sensitiveHeaders: Cookie,Set-Cookie
Please note that the property name is sensitiveHeaders not sensitive-headers
[I use spring-cloud-starter-zuul version:1.3.1.RELEASE ]

Spring-Security-Oauth2: Full authentication is required to access this resource

I am trying to use spring-security-oauth2.0 with Java based configuration. My configuration is done, but when i deploy application on tomcat and hit the /oauth/token url for access token, Oauth generate the follwoing error:
<oauth>
<error_description>Full authentication is required to access this resource</error_description>
<error>unauthorized</error>
</oauth>
My configuration is on Git hub, please click on link
The code is large, so refer to git. I am using chrome postman client for send request. follwing is my request.
POST /dummy-project-web/oauth/token HTTP/1.1
Host: localhost:8081
Cache-Control: no-cache
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id=abc%40gmail.com&client_secret=12345678
The error is just like, the URL is secure by Oauth, but in configuration, i give the all permission for access this URL. What actual this problem is?
The client_id and client_secret, by default, should go in the Authorization header, not the form-urlencoded body.
Concatenate your client_id and client_secret, with a colon between them: abc#gmail.com:12345678.
Base 64 encode the result: YWJjQGdtYWlsLmNvbToxMjM0NTY3OA==
Set the Authorization header: Authorization: Basic YWJjQGdtYWlsLmNvbToxMjM0NTY3OA==
By default Spring OAuth requires basic HTTP authentication. If you want to switch it off with Java based configuration, you have to allow form authentication for clients like this:
#Configuration
#EnableAuthorizationServer
protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.allowFormAuthenticationForClients();
}
}
The reason is that by default the /oauth/token endpoint is protected through Basic Access Authentication.
All you need to do is add the Authorization header to your request.
You can easily test it with a tool like curl by issuing the following command:
curl.exe --user abc#gmail.com:12345678 http://localhost:8081/dummy-project-web/oauth/token?grant_type=client_credentials
With Spring OAuth 2.0.7-RELEASE the following command works for me
curl -v -u abc#gmail.com:12345678 -d "grant_type=client_credentials" http://localhost:9999/uaa/oauth/token
It works with Chrome POSTMAN too, just make sure you client and secret in "Basic Auth" tab, set method to "POST" and add grant type in "form data" tab.
You should pre authenticate the token apis "/oauth/token"
extend ResourceServerConfigurerAdapter and override configure function to do this.
eg:
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests().antMatchers("/oauth/token").permitAll().
anyRequest().authenticated();
This is incredible but real.
csrf filter is enabled by default and it actually blocks any POST, PUT
or DELETE requests which do not include de csrf token.
Try using a GET instead of POST to confirm that this is it.
If this is so then allow any HTTP method:
#Throws(Exception::class)
override fun configure(http: HttpSecurity) {
/**
* Allow POST, PUT or DELETE request
*
* NOTE: csrf filter is enabled by default and it actually blocks any POST, PUT or DELETE requests
* which do not include de csrf token.
*/
http.csrf().disable()
}
If you are obtaining a 401 the most intuitive thing is to think that in the request you have No Auth or you are missing something in the headers regarding authorization.
But apparently there is an internal function that is filtering the HTTP methods that use POST and returns a 401. After fixing it I thought it was a cache issue with the status code but apparently not.
GL
Disable csrf
Demo
I had the same problem, but I solve this with the following class:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
#Configuration
#EnableWebSecurity
public class OAuthSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Bean
#Override
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
#Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
If you have multiple services running on the same proxy server, make sure your proxy is correctly redirecting to servername/oauth/token rather than servername/servicename/oauth/token.

Categories

Resources