Authorization header not passed by ZuulProxy starting with Brixton.RC1 - java

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 ]

Related

How to create a Spring Boot Actuator endpoint that delegates all calls to a different port?

I'm using Spring Boot 2.7.5 and I want to create an actuator endpoint, that acts as a proxy, and forwards all requests to a different server, running on the same JVM instance, but on a different port (say 8082). Here's the gist of it:
#Component
#RestControllerEndpoint(id = "myEndpoint", enableByDefault = true)
public class MyEndpoint {
#RequestMapping("**")
public Object myEndpoint() {
// TODO Forward everything to port 8082
return ...
}
}
What do I need to do in order to achieve this?
Update 1:
The port (8082) is not available from the internet so I can't do a simple redirect.
Update 2:
I don't want to forward the request to a Spring Controller or Spring Bean. Port 8082 is a separate server started in the same process.
What if instead of a redirect (as suggested in other replies) your actuator would perform a call to localhost:8082 and return it's return it's response? You could also return a ResponseEntity instead of just an Object to cascade the HTTP codes of the performed request.
#Component
#RestControllerEndpoint(id = "myEndpoint", enableByDefault = true)
public class MyEndpoint {
private static final String TARGET_HOST = "localhost";
private static final int TARGET_PORT = 8082;
#RequestMapping("**")
public Object myEndpoint(HttpServletRequest originalRequest) {
Uri targetUri = UriComponentsBuilder.fromHttpRequest(originalRequest)
.host(TARGET_HOST)
.port(TARGET_PORT)
.build();
Object responseBody = /* code to perfom the call using your preferred HTTP client*/;
return responseBody;
}
}
Spring Boot Actuator endpoint that delegates all calls to a different port.
For different port use this in your application.properties file
management.server.port=8081
management.endpoints.web.exposure.include=*
This will expose all actuator endpoints on port 8081.
The official document describes :
Base path for Web endpoints. Relative to the servlet context path (server.servlet.context-path) or WebFlux base path (spring.webflux.base-path) when the management server is sharing the main server port. Relative to the management server base path (management.server.base-path) when a separate management server port (management.server.port) is configured.
You can configure the following
management:
server:
port: 8081
servlet:
context-path:
endpoints:
web:
base-path: /
path-mapping:
prometheus: metrics
exposure:
include: [ "prometheus" ]
Tip : This configuration must be different with the server.ports must be different.
For more details, please check appendix.application-properties.actuator

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.

Spring Cloud Gateway YML routing - is there a way to check permissions?

I have some microservices, and a Gateway using Spring Cloud. I'm trying to set up the routing in the Gateway. Ideally, I would like to set up the routing in the YML file, or with a RouteLocator bean.
But currently, in my Gateway, I have REST endpoints for every route, which is just sending the request onwards using a WebClient.
(Prior to this, I have a ReactiveAuthenticationManager filter which validates a JWT token. It returns a UsernamePasswordAuthenticationToken which includes some user authorities it gets from the token).
Here's an example where I have set up a route to a microservice called the tracking service:
Controller:
#RestController
#RequestMapping("/tracking-service/tracking")
public class TrackingController {
#Autowired
private TrackingService trackingService;
#GetMapping
public Flux getAllTracking() {
return trackingService.getAllTracking();
}
}
Service:
#Service
public class TrackingService {
private WebClient webClient;
#PreAuthorize("hasAuthority('MANAGER')")
public Flux getAllTracking() {
//Make HTTP call to the tracking service
}
}
The reason I have done it this way is because of the #PreAuthorize annotation. If the client's JWT token does not include the 'MANAGER' token, then this will return a 403 forbidden status. Not all of the endpoints require the MANAGER authority and some of the endpoints require other authorities.
My question - is it possible to do this when routing with the YML? I was hoping to see something that looks like this, but I'm not sure it is possible? I have read the Spring docs and looked at all the available filters, and there was nothing that does this job that I could see.
spring:
cloud:
gateway:
routes:
- id: tracking
uri: http://tracking-service
predicates:
- Path=/tracking-service/**
filters:
- StripPrefix=1
- PreAuthorize=hasAuthority('MANAGER')
Thanks.
API Gateway and authorization are two separation of concern. Ideally authorization should not be mixed in gateway application. For permission you can directly map it to service like this.
#RestController
public class ResourceREST {
#RequestMapping(value = "/resource/", method = RequestMethod.GET)
#PreAuthorize("#customPermissionEvaluator.hasPrivilege(authentication,'somepermission')")
public Mono<ResponseEntity<?>> user() {
return Mono.just(ResponseEntity.ok(new Message("Access allowed")));
}
}
And define custom permission evaluator
#Service
public class CustomPermissionEvaluator {
public boolean hasPrivilege(Authentication auth, String permission) {
String requiredPermissionCheck = permission.toLowerCase();
for (GrantedAuthority grantedAuth : auth.getAuthorities()) {
if (grantedAuth.getAuthority().contains(requiredPermissionCheck)) {
return true;
}
}
return false;
}
}
As such gateway does not provide any authorization in the routing which is ideal.

Reject http requests handled by endpoints that don't specify security restrictions

I'm trying to protect against accidentally publishing an endpoint with no security in place. #Secured, #PreAuthorize, #PostAuthorize, etc.
I would want requests made to endpoints that don't specify any security requirements to be automatically rejected (403 Forbidden)
If an endpoint should indeed be open to the public it would need to be tagged by an annotation or similar.
#RestController
public class Controller {
#GetMapping("/endpoint1")
public void accidentallyOpen() {
}
#GetMapping("/endpoint2")
#UnrestrictedEndpoint
public void intentionallyOpen() {
}
}
In the above example I'm expecting
Requests made to /endpoint1 should return 403 Forbidden.
Requests made to /endpoint2 should return 204 No Content
I'm not sure where to start. Also if there are better ways of doing this I'm also happy to learn.

Spring Cloud Zuul: Apply filter only to specific route

I'm using Spring Cloud's Zuul to proxy some API requests to a few external servers. The proxying itself works well, but each service requires a (different) token provided in the request header.
I've successfully written a simple pre filter for each token that applies the appropriate header. However, I now have a problem. Even after pouring through the documentation, I can't figure out how to make each filter apply only to the proper route. I don't want to perform url-matching as the url changes across environments. Ideally, I'd have some way to get the name of the route in the filter.
My application.yml:
zuul:
routes:
foo:
path: /foo/**
url: https://fooserver.com
bar:
path: /bar/**
url: https://barserver.com
Ideally I'd like to do something like this in FooFilter.java (a prefilter):
public bool shouldFilter() {
return RequestContext.getCurrentContext().getRouteName().equals("foo");
}
but I can't seem to find any way to do this.
You can use proxy header in RequestContext to distinguish routed server like below. If you are using ribbon, you can also use serviceId header. But if you specify url direclty like above your example, you should use proxy header. One thing you have to know is that proxy header is set in PreDecorationFilter, so your pre-filter must have bigger value of filter order than the value that PreDecorationFilter has (it is 5 at this moment).
#Override
public int filterOrder() {
return 10;
}
#Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
if ((ctx.get("proxy") != null) && ctx.get("proxy").equals("foo")) {
return true;
}
return false;
}

Categories

Resources