We want to have an 'auth' service, using spring security, which:
authenticates incoming request,
if successful, adds userid to http header
forwards request (with header) to another service
What is the easiest way to achieve this kind of forwarding using Spring?
For this use case, we can create a new Spring Boot application (majorly know as API Gateway) that enables Zuul proxy (EnableZuulProxy). Create a custom Zuul PRE_TYPE filter that parses all the incoming requests. Its objective would be to check if an incoming request contains token, then call Authentication Service, retrieve the caller's information and add the user's identifier in the custom header.
Once the incoming request is passed through all the filters, then use zuul.routes.* properties to forward a request to appropriate services.
To call remote service (in this case, Authentication Service) we can use Spring's RemoteTokenServices class. And, BearerTokenExtractor class for extracting the token from incoming requests. Here's the sample code to get you started:
#Override
public Object run() {
final RequestContext requestContext = RequestContext.getCurrentContext();
final HttpServletRequest request = removeCustomHeaders(requestContext.getRequest());
requestContext.setRequest(request);
final Authentication authentication = tokenExtractor.extract(request);
if (Objects.nonNull(authentication)) {
try {
final OAuth2Authentication oAuth2Authentication = tokenServices.loadAuthentication(authentication.getPrincipal().toString());
final Map<String, String> userAuthDetails = (Map<String, String>) oAuth2Authentication.getUserAuthentication().getDetails();
requestContext.addZuulRequestHeader(USERNAME_HEADER, oAuth2Authentication.getPrincipal().toString());
// Add other required information in headers.
}
catch(final AuthenticationException | RestClientException |OAuth2Exception e){
Related
I am new to Spring Cloud Gateway and I am struggling to debug a 404. The current gateway flow has our web app sending an HTTP request to our Gateway with a SESSION Cookie value. It takes this cookie value and sends it to micro-service A and micro-service b to have them both authenticate the request. Then once the request is authenticated it makes a request again to micro-service A to obtain the data that requested. This request fails instantly at micro service A with the following error org.springframework.web.serverlet.PageNotFound: No mapping for SES ION=<session cookie value>. It seems that somewhere in the flow the HTTP Request from Spring Cloud Gateway to micro-service A changes from a GET request to the cookie value. Has anyone ran into this before or know how I could debug this?
The Filter:
#Component
public class Filter {
private final AuthFilter authFilter;
public Filter(final AuthFilter authFilter) {
this.authFilter = authFilter;
filters =
f ->
f.filter(authFilter)
.rewritePath(
"/api/v1/(?<segment>.*)",
"/${segment}"
)
}
}
#Component
#RequiredArgsConstructor
public class AuthFilter implements GatewayFilter {
#Override
public Mono<Void> filter(final ServerWebExchange exchange, final GatewayFilterChain chain) {
// user validation request
// just a rest template get and checks for a 200
// permission check
// just a rest template post and checks for a 200 and the response is not null
// requests pass both auth checks
return chain.filter(exchange);
}
I am unsure what other code I can provide as in the other micro-service it does not reach any of our code. Thanks for any help in advance
I am making service to service requests using Spring's WebClient that require an OAuth2 bearer token to be added as a header to the request. I Can do this relatively easily by creating an ExchangeFilterFunction that intercepts the request, retrieves an access token, adds it to the header, and continues on. Since this is not a user request, the SecurityContextHolder does not contain an Authentication that would hold an access token for me, so instead of retrieving from that, I would like to get an access token based on my Spring security configuration (currently defined in the spring.security.oauth2.client.registration and provider properties).
The way I'm doing this now is by Autowiring an OAuth2ClientContext and then getting the AccessToken from it. Reducing the code only to what I care about for this question, I have:
#Component
public class OAuth2WebClientFilter implements ExchangeFilterFunction {
#Autowired
private OAuth2ClientContext oAuth2ClientContext;
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
// simple retrieval of the token
String oAuth2Token = oAuth2ClientContext.getAccessToken().getValue();
// adding the token to the header of the request
request = ClientRequest.from(request).header(HttpHeaders.AUTHORIZATION, "Bearer " + oAuth2Token).build();
return next.exchange(request);
}
}
This does exactly what I want it to. However, I have recently upgraded spring-security-oauth2 to 2.5.0.RELEASE, and it is saying the OAuth2ClientContext is deprecated, but I haven't found a simple replacement for this process. So is there still a way to get an access token in a relatively simple fashion like above, and if so, how?
Also note: this concept is used elsewhere in the project and not just for the WebClient, so I'm looking to see how to properly replace an injected OAuth2ClientContext. Thanks!
Spring Security provides an exchange filter function called ServletOAuth2AuthorizedClientExchangeFilterFunction.
The ServletOAuth2AuthorizedClientExchangeFilterFunction provides a
simple mechanism for requesting protected resources by using an
OAuth2AuthorizedClient and including the associated OAuth2AccessToken
as a Bearer Token. It directly uses an OAuth2AuthorizedClientManager
and therefore inherits the following capabilities:
An OAuth2AccessToken will be requested if the client has not yet been
authorized.
authorization_code - triggers the Authorization Request redirect to
initiate the flow
client_credentials - the access token is obtained directly from the
Token Endpoint
password - the access token is obtained directly from the Token
Endpoint
If the OAuth2AccessToken is expired, it will be refreshed (or renewed)
if an OAuth2AuthorizedClientProvider is available to perform the
authorization
See https://docs.spring.io/spring-security/reference/servlet/oauth2/client/authorized-clients.html#oauth2Client-webclient-servlet for details.
I am trying to make request between microservices in order to retrieve a list of users with the same roles. For this, first I make a request between FrontEnd and Backend inside the microservice 1. Following, I call an endpoint in the microservice 2 from Microservice 1 backend, but the session Id is lost in it, and I can retrieve the context.
I am using spring security and Redis for the session Control.
Manually, I retrieve the session Id from the microservice 1 and I add it as an attribute of the header of the second call, to the microservice 2. But it does not work.
String sessionID= RequestContextHolder.currentRequestAttributes().getSessionId();
RestTemplate rest = new RestTemplate();
HttpHeaders headers= new HttpHeaders();
headers.set("Session",sessionID);
HttpEntity<ResponseData> entity = new HttpEntity<ResponseData>(headers);
ResponseEntity<ResponseData> responseEntity =rest.exchange(targetApi, HttpMethod.GET, entity,ResponseData.class);
Finally, I resolved the problem adding an interceptor as a component:
#Component
public class SpringSessionClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
request.getHeaders().add("Cookie", "SESSION=" + sessionId);
return execution.execute(request, body);
}
}
And I created a #Bean to configure the rest template:
#Bean
public RestTemplate restTemplate(){
RestTemplate rest = new RestTemplate();
ClientHttpRequestInterceptor interceptor= new SpringSessionClientHttpRequestInterceptor();
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();
interceptors.add(interceptor);
rest.setInterceptors(interceptors);
return rest;
}
I don't know the exact answer to your question but in terms of your design I'd question if you really want to make your microservice1 depending on microsevice2. A microservice should be autonomous in the way it works and being able to be deployed on it's own (in theory anyway!). May be you could have an orchestrating microservice that receives your session information and then calls the 2 other microservices to pass that information on via 'standard' attributes.
headers.set("Session",sessionID);
I assume that the problem is that you are using the wrong identifier. As far as I know, it is JSESSIONID by default.
Another problem that I can see here is that JSESSIONID expected to be in cookies. Try to put it in cookies when sending a request to your 'microservice2'.
I have a class which implements ContainerRequestFilter and ContainerResponseFilter. I am using this class to log the request and the corresponding response to it.I note the start_time in request filter and propagate this to the response filter using MDC.put("start-time",start_time).
I can see that since the response filter takes the ContainerRequestContext as the argument , it is able to map the response to the correct request.
But I am not able to see how the logging filter class is able to distinguish between multiple request calls which would be coming simultaneously. Does each request make a different instance of the logging filter class ?
Does each request make a different instance of the logging filter class ?
No. So trying to store member state is not an option. What you should do is use the ContainerRequestContext to set a property on the request side. On the response side just get the property from the same context.
// request filter
filter(ContainerRequestContext request) {
request.setProperty("key", value);
}
// response filter
filter(ContainerRequestContext request, ContainerResponseContext response) {
Object value = request.getProperty("key");
}
Each request will get its own Container(Request|Response)Context.
I'm building REST services using spring-mvc and what I'm looking for now is a way to proxy HTTP request to external REST service from inside Spring MVC controller.
I'm getting HttpServletRequest object and want to proxy it making as few changes as possible. What is essential for me is keeping all the headers and attributes of incoming request as they are.
#RequestMapping('/gateway/**')
def proxy(HttpServletRequest httpRequest) {
...
}
I was trying simply to send another HTTP request to external resource using RestTemplate but I failed to find a way to copy REQUEST ATTRIBUTES (which is very important in my case).
Thanks in advance!
I wrote this ProxyController method in Kotlin to forward all incoming requests to remote service (defined by host and port) as follows:
#RequestMapping("/**")
fun proxy(requestEntity: RequestEntity<Any>, #RequestParam params: HashMap<String, String>): ResponseEntity<Any> {
val remoteService = URI.create("http://remote.service")
val uri = requestEntity.url.run {
URI(scheme, userInfo, remoteService.host, remoteService.port, path, query, fragment)
}
val forward = RequestEntity(
requestEntity.body, requestEntity.headers,
requestEntity.method, uri
)
return restTemplate.exchange(forward)
}
Note that the API of the remote service should be exactly same as this service.
You can use the spring rest template method exchange to proxy the request to a third party service.
#RequestMapping("/proxy")
#ResponseBody
public String proxy(#RequestBody String body, HttpMethod method, HttpServletRequest request, HttpServletResponse response) throws URISyntaxException {
URI thirdPartyApi = new URI("http", null, "http://example.co", 8081, request.getRequestURI(), request.getQueryString(), null);
ResponseEntity<String> resp =
restTemplate.exchange(thirdPartyApi, method, new HttpEntity<String>(body), String.class);
return resp.getBody();
}
What is the restTemplate.exchange() method for?
if you think of applying the API gateway pattern for microservices,
have a look at Netflix zuul which is a good alternative in the spring boot ecosystem. A good example is provided here.