Using RestTemplate to access ZuulProxy resources
ddewaele opened this issue · comments
The way I understand it, if you have a gateway component with a @EnableZuulProxy
and @EnableOAuth2Sso
annotation it is able to proxy requests to Oauth2 protected backends by
- looking into the http session for an Oauth2Authentication (result of the OAuth2 flow / authentication)
- Extract the access token from that OAuth2Authentication object
- Pass that access token as a bearer token to the underlying backend via the Authorization header
So with the following zuul rules
zuul:
routes:
ui:
path: /ui/**
url: http://localhost:8080/ui
backend:
path: /backend/**
url: http://localhost:8800/api
backend2:
path: /backend2/**
url: http://localhost:8083/api
server:
port: 8888
I can access (@EnableResourceServer) backends directly (outside of Zuul) with a valid access token :
curl -v -H "Authorization: Bearer 8cbb47bb-6596-445e-8c0e-b930065d2137" http://localhost:8083/api/
And I can access the same backend through Zuul providing I have a valid JSESSION ID that Spring can use to extract the OAuth2Authentication / Access token
curl -v -H "Cookie:JSESSIONID=88850010D7C53051FEDC201579A6C7FD; XSRF-TOKEN=c7e400ca-74be-4b1a-aeec-d34b6f8a541d" http://localhost:8888/backend2/
Now suppose I have the following flow
- The /ui (Angular app) does a REST call to /backend (using a valid JSESSION)
- /backend calls /backend2 via a REST template (problem .... how does the RestTemplate authenticate the request? )
The server-side code in /backend can call /backend2 via Zuul but it needs to know the JSESSIONID.
By default RestTemplate doesn't send cookies so /backend2 gets called without a valid JESSIONID and it is unable to authenticate the request.
What would be the proper way to solve this ? The /ui would be able to call /backend2 just fine via javascript / REST (as it has a valid session in the browser). But how would a backend component call /backend2 ? I don't want to rely on too many hacks to extract the JSessionID from the original request and sending it as a cookie / header value. Perhaps using RestTemplate isn't the way to go ?
yes it would be nice if we have a more elaborated example for this use case.
if we have an edge server as the only gateway to our resource servers, using oauth2..
I'll publish something to github. (this is part of a production app so I'll try to create a skeleton structure)
Here's already an overview
(calls to the auth server have been omitted... we assume we are dealing with a logged-in user (Oauth2Authentication is tied to the http session).
- This triggers the OAuth2 authentication / authorization flow if user isn't logged in
- Gateway allows access to the /ui app
- Here the angular UI does a REST call through Zuul (works because there is an http session / oauth2authentication that zuul can use)
- Zuul has extracted an access token and passes that on the resource server
- Here's where it gets tricky. Should resource1 go via the GW, or should it just call resource2 directly ?
ATM, the idea is that everything is accessed via the gateway component (various javascript UIs / resource servers). Everything is secured using oauth2 (SSO). The resource servers accept bearer tokens but as everything needs to be access via the gateway the architecture relies heavily on http sessions.
- The UIs can call the resource servers fine, provided there is an authenticated user associated with the http session ( = the case when the user has logged in)
- I assume the Backends (resource servers) will be able to call each-other directly (bypassing Zuul) by extracting the access token and using the Oauth2 Rest Template
- The main question is , is it a good architecture to have all access go through the gateway (not only UI -> resource server , but also resource server -> resource server)
we need to think about load balancing ribbon or so; talking directly to a resource (step5) will be a bad idea. or does ribbon talk to eureka and not to resource component!!!!!
i would love to work on a side (ready to deploy) project.
I'd be happy to go via the GW but have no idea how to make the rest call from resource1 ---> GW ---> resource2.
@zirconias I've added a sample in this github repo in sample2
The resource2 backend service calls the resource1 backend service with the following code
Although i can acces the OAuth2Authentication, I cannot extract the oauth2 key, and if I want to pass through the gateway I need to have the correct JSESSIONID. In this backend call I don't have an easy access to this session. Current workaround
- injecting the http request
- the JSESSIONID from the cookie
- sending Cookie with the JSESSIONID in the header of the request to the backend
It works, but there has to be a better way.
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
System.out.println("____ FOUND USER = " + SecurityContextHolder.getContext().getAuthentication());
//TODO: Need to find a cleaner way to pass on these credentials
headers.add("Cookie","JSESSIONID=" + request.getCookies()[0].getValue());
HttpEntity<String> requestEntity = new HttpEntity<String>("parameters", headers);
ResponseEntity rssResponse = restTemplate.exchange(
"http://localhost:8888/resource1",
HttpMethod.GET,
requestEntity,
Map.class);
rssResponse.getBody();
@ddewaele I am facing the same scenario with the same problem, communication between resources through zuul gateway, did you find any solution other than getting JSESSIONID cookie.
I am in the similar boat.
I have OAuth2 server, Zuul Proxy server(UI Application) and a Microservice(CatalogService).
Let's say Zuul Proxy server is running on localhost:8080 and CatalogService is running on localhost:8181/catalog.
From Zuul Proxy I am able to invoke CatalogService endpoint using OAuth2RestTemplate which propagates OAuth Token.
//This is working fine
restTemplate.getForEntity("http://localhost:8181/catalog/products", Products.class)
But, when I configure CatalogService in Zuul routes as follows:
zuul.routes.catalog-service.path=/api/catalog/**
zuul.routes.catalog-service.url=http://localhost:8181/catalog/
and try to invoke CatalogService endpoint thru Zuul Proxy it is redirecting to Login page.
//This is NOT working
restTemplate.getForEntity("http://localhost:8080/api/catalog/products", Products.class)
One more observation is if I invoke the same REST endpoint (http://localhost:8080/api/catalog/products) via AJAX using jQuery from Zuul Proxy (UI app)it is working fine.
I will most likely use Eureka Discovery and invoke it like http://catalog-service/api/catalog/products which might work. But I would like to know why calling catalog-service via Zuul Proxy URL with OAuth2RestTemplate is not working.
My understanding is, after authentication OAuth2RestTemplate should include auth token even for invoking REST endpoints on same Zuul proxy server.
Any thoughts?
@sivaprasadreddy I'm not sure you are describing the same problem. I wouldn't expect your use case to work without setting the sensitive headers (per the user guide).
@dsyer I have set zuul.sensitiveHeaders=(blank) which should send all headers. In case you want to take a look at my code here is the repo https://github.com/sivaprasadreddy/spring-security-oauth2-demo.
The problem i am describing is here https://github.com/sivaprasadreddy/spring-security-oauth2-demo/blob/master/ui-zuul-app/src/main/java/com/sivalabs/uizuulapp/HomeController.java.
ResponseEntity<String> resp = restTemplate.getForEntity("http://localhost:8080/ui/api/catalog/products", String.class);
System.err.println("Rsp: "+resp.getBody());
This is returning Login page html as response.