Spring Boot Zuul Mulitpart request - spring-boot

I am working on FileStore application, FileStore is a Spring boot application.
we want didn't to publish the URL of FileStore application, We wanted to put Zuul application,which will work like proxy.
I tried basic Zuul gateway example. It all works good for normal request following below example
https://spring.io/guides/gs/routing-and-filtering/
But it fails for mulitpart request. Can anyone have example for support multipart request using Zuul proxy.
We are trying to upload large files via Zuul proxy...
Exception stacktrace
PreFilter Code:
public class MyPreFilter extends ZuulFilter{
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 1;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
log.info(String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString()));
return null;
}
}
Thanks in advance.

You can use following configuration on zuul API gateway:
zuul:
routes:
user-service-1:
path: /api/userserv1/**
url: http://localhost:9090
strip-prefix: false
user-service-2:
path: /api/userserv2/**
url: http://localhost:9091
strip-prefix: false
user-service-3:
path: /api/userserv3/**
url: http://localhost:9092
strip-prefix: false

Related

OAuth2 with Google and Spring Boot - I can't log out

I've been trying to get a successful Oauth2 login with Google and Spring Boot for a while now. This only works partially. Why partly - because I can't manage the logout or when I pressed the logout button I see an empty, white browser page with my URL (http://localhost:8181/ben/"). After a refresh of the page I get error from google, but if I open a new tab, enter my url, I'm still logged in to google, because I can see my user, which I'm outputting to my react application.
#SpringBootApplication
#EnableOAuth2Sso
#RestController
#CrossOrigin
public class SocialApplication extends WebSecurityConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(SocialApplication.class, args);
}
#RequestMapping("/user")
public Principal user(Principal principal) {
return principal;
}
#RequestMapping("/logout")
public String fetchSignoutSite(HttpServletRequest request, HttpServletResponse response) {
Cookie rememberMeCookie = new Cookie("JSESSIONID", "");
rememberMeCookie.setMaxAge(0);
response.addCookie(rememberMeCookie);
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
new SecurityContextLogoutHandler().logout(request, response, auth);
}
auth.getPrincipal();
return "redirect:/ben/login";
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests().antMatchers("/ben/*").permitAll().anyRequest().authenticated().and()
.logout().logoutSuccessUrl("http://localhost:8181/ben/login").invalidateHttpSession(true)
.clearAuthentication(true).deleteCookies("JSESSIONID");
}
My application.yml file looks like this:
# Spring Boot configuration
spring:
profiles:
active: google
# Spring Security configuration
security:
oauth2:
client:
clientId: 415772070383-3sapp4flauo6iqsq8eag7knpcii50v9k.apps.googleusercontent.com
clientSecret: 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
accessTokenUri: https://www.googleapis.com/oauth2/v4/token
userAuthorizationUri: https://accounts.google.com/o/oauth2/v2/auth
clientAuthenticationScheme: form
scope:
- email
- profile
resource:
userInfoUri: https://www.googleapis.com/oauth2/v3/userinfo
preferTokenInfo: true
# Server configuration
server:
port: 8181
servlet:
context-path: /ben
That fetchSignoutSite only emptying the JsessionId and logging out from Spring Security context. So you would still need to add part where you go to google and sign out from there which I have no experience on implementation.

Spring Gateway on localhost:8000

Running a service on localhost:8000. I route to it with Spring Gateway and only get a white page instead of the login page of the service.
I've tried routing using java, and also through a yml file.
Java
#SpringBootApplication
public class GsGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GsGatewayApplication.class, args);
}
#Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder){
return builder.routes()
.route(p->p
.path("/get")
.uri("localhost:8000/login"))
.build();
}
}
application.yml
server:
port: 8080
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: weblogin
uri: localhost:8000/login
predicates:
- Path=/get
I expect a login page, but get only a blank white page.
By your configuration, when you access localhost/get you will be route to localhost:8080/get

Spring Cloud RestTemplate add auth token

How to correctly implement restTemplate with authorisation token?
I have a Zuul gateway which passes a JWT downstream to other services correctly, assuming I don't want to do anything on the gateway first, using a config like:
zuul:
sensitive-headers:
routes:
instance-service:
path: /instances/**
strip-prefix: false
And using a token relay filter:
#Component
public class TokenRelayFilter extends ZuulFilter {
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
Set<String> headers = (Set<String>) ctx.get("ignoredHeaders");
headers.remove("authorization");
return null;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 10000;
}
}
Which just forwards everything to the instance-service, works a treat.
However if I remove the routes config from the config.yml file because I want to handle some things on the gateway before manually calling the service I loose the access token and get a 401 back from the downstream services
#ApiOperation(value = "List all instances and their properties.")
#GetMapping("/instances")
public ResponseEntity<String> instances() {
ParameterizedTypeReference<String> reference = new ParameterizedTypeReference<String>() {
};
return restTemplate.exchange("http://instance-service", HttpMethod.GET, null, reference);
}
My RestTemplate is just wired up generically
#Configuration
public class MyConfiguration {
#LoadBalanced
#Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
}
How do I correctly get the JWT back into the new RestTemplate without having to manually create and add a header in each request?
Am I supposed to be using OAuth2RestTemplate?
After some discussion, it seems like you have two options:
Implement and endpoint and dig the Auth header out via #RequestParam on request. From there, you can add it back on for the subsequent outbound request via RestTemplate to your downstream service.
Use Zuul to proxy your request (Auth header included, make sure its excluded from the sensitive-headers config) and implement a pre filter to include any additional logic you might need.
If I had to pick, it sounds like something Zuul should be doing since it's likely acting as your gateway for both your queue and other services, and it looks like you are trying to implement a proxy request, which Zuul can already do, but it's tough to say without knowing the full scope of the architecture.

Spring Zuul Eureka Security Authentication get user info from Zuul

I'm using #EnableOAuth2Sso to authenticate a user with a third party authentication server on the Zuul server. I need to pass user info from Zuul to the routed servers. I've set up the request endpoint /userinfo to return a jsonified representation of a flattened version of the userinfo from the third party. How do I get this userinfo to one of the resource servers?
What I've tried so far:
I've tried making a request using the #LoadBalanced #Bean RestTemplate been. However, I get redirected to the third party for authorization. The sensitive-headers is set to none. I've checked which headers were going through:
["upgrade-insecure-requests","user-agent","accept","accept-language","cookie",
"authorization","x-forwarded-host","x-forwarded-proto",
"x-forwarded-prefix","x-forwarded-port","x-forwarded-for","accept-encoding",
"content-length", "host","connection"]
So, then I tried using #LoadBalanced #Bean OAuth2RestTemplate. I had to set the config security.basic.enabled=false to prevent the Authentication User Login Prompt from appearing. This produces UserRedirectRequiredException
Resource Server
#RequestMapping(value = "/test", method = RequestMethod.GET)
public ResponseEntity<String> test3() {
return restTemplate.getForEntity("http://zuul-server/userinfo", String.class);
}
Zuul Server
#RequestMapping(value = "/userinfo", produces = MediaType.APPLICATION_JSON_VALUE)
public User getInfo(OAuth2Authentication auth) {
return service.getUser(auth); // Returns User Object
}
Additional Notes
The Resource Server has not been annotated with #EnableResourceServer. If it was, a forwarded request will result in Invalid access token error message.
This is what I have working on our system for passing Oauth2 JWT tokens.
#Configuration
#EnableResourceServer
public class JwtSecurityConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/oauth/**").permitAll()
.antMatchers("/**").hasAuthority("ROLE_API")
.and()
.csrf().disable();
}
}
And the config portion you might need.
services:
path: /services/**
serviceId: services
stripPrefix: false
sensitiveHeaders: true
auth:
path: /oauth/**
serviceId: saapi-auth-server
stripPrefix: false
sensitiveHeaders: true
There was very little documentation on this. So it was really just hacking away at it until I could get it to pass tokens on.

spring security + oauth2 + reactjs + restful http client

I am doing spring boot 1.5+ security with auth2 authentication and reactjs. for http calls using restful http client. Authentication is working perfectly and I am successfully accessing data from resource server. The issue is logout code is not working and I am getting this error on console:
POST http://localhost:8080/logout 403 ()
error: "Forbidden"
message: "Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-XSRF-TOKEN'.
I am sharing my code also.
1) ReactJs code
handleLogout = (e) => {
client({
method: 'POST',
path: '/logout',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}}).then(response => {
console.log(response);
});
}
2) restful http client
'use strict';
// client is custom code that configures rest.js to include support for HAL, URI Templates,
// and other things. It also sets the default Accept request header to application/hal+json.
// get the rest client
var rest = require('rest');
// provides default values for the request object. default values can be provided for the method, path, params, headers, entity
// If the value does not exist in the request already than the default value utilized
var defaultRequest = require('rest/interceptor/defaultRequest');
// Converts request and response entities using MIME converter registry
// Converters are looked up by the Content-Type header value. Content types without a converter default to plain text.
var mime = require('rest/interceptor/mime');
// define the request URI by expanding the path as a URI template
var uriTemplateInterceptor = require('./uriTemplateInterceptor');
// Marks the response as an error based on the status code
// The errorCode interceptor will mark a request in error if the status code is equal or greater than the configured value.
var errorCode = require('rest/interceptor/errorCode');
var csrf = require('rest/interceptor/csrf');
// A registry of converters for MIME types is provided. Each time a request or response entity needs to be encoded or
// decoded, the 'Content-Type' is used to lookup a converter from the registry.
// The converter is then used to serialize/deserialize the entity across the wire.
var baseRegistry = require('rest/mime/registry');
var registry = baseRegistry.child();
registry.register('text/uri-list', require('./uriListConverter'));
registry.register('application/hal+json', require('rest/mime/type/application/hal'));
// wrap all the above interceptors in rest client
// default interceptor provide Accept header value 'application/hal+json' if there is not accept header in request
module.exports = rest
.wrap(mime, { registry: registry })
.wrap(uriTemplateInterceptor)
.wrap(errorCode)
.wrap(csrf)
.wrap(defaultRequest, { headers: { 'Accept': 'application/hal+json' }});
3) application.yml of client application
debug: true
spring:
aop:
proxy-target-class: true
security:
user:
password: none
oauth2:
client:
access-token-uri: http://localhost:9999/uaa/oauth/token
user-authorization-uri: http://localhost:9999/uaa/oauth/authorize
client-id: acme
client-secret: acmesecret
resource:
user-info-uri: http://localhost:9999/uaa/user
jwt:
key-value: |
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgnBn+WU3i6KarB6gYlg40ckBiWmtVEpYkggvHxow74T19oDyO2VRqyY9oaJ/cvnlsZgTOYAUjTECjL8Ww7F7NJZpxMPFviqbx/ZeIEoOvd7DOqK3P5RBtLsV5A8tjtfqYw/Th4YEmzY/XkxjHH+KMyhmkPO+/tp3eGmcMDJgH+LwA6yhDgCI4ztLqJYY73gX0pEDTPwVmo6g1+MW8x6Ctry3AWBZyULGt+I82xv+snqEriF4uzO6CP2ixPCnMfF1k4dqnRZ/V98hnSLclfMkchEnfKYg1CWgD+oCJo+kBuCiMqmeQBFFw908OyFKxL7Yw0KEkkySxpa4Ndu978yxEwIDAQAB
-----END PUBLIC KEY-----
zuul:
routes:
resource:
path: /resource/**
url: http://localhost:9000/resource
user:
path: /user/**
url: http://localhost:9999/uaa/user
logging:
level:
org.springframework.security: DEBUG
4) CorsFilter configuration in authorization server
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
System.out.println("*********** running doFilter method of CorsFilter of auth-server***********");
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
response.addHeader("Access-Control-Allow-Origin", "*");
response.addHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
response.addHeader("Access-Control-Allow-Headers", "x-auth-token, x-requested-with");
response.addHeader("Access-Control-Max-Age", "3600");
if (request.getMethod()!="OPTIONS") {
try {
chain.doFilter(req, res);
} catch (IOException e) {
e.printStackTrace();
} catch (ServletException e) {
e.printStackTrace();
}
} else {
}
}
public void init(FilterConfig filterConfig) {}
public void destroy() {}
}
5) AuthrorizationServerConfigurerAdapter of authentication server
#Configuration
#EnableAuthorizationServer
public class OAuth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Bean
public #Autowired JwtAccessTokenConverter jwtAccessTokenConverter() throws Exception {
System.out.println("*********** running jwtAccessTokenConverter ***********");
// Setting up a JWT token using JwtAccessTokenConverter.
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
// JWT token signing key
KeyPair keyPair = new KeyStoreKeyFactory(
new ClassPathResource("keystore.jks"), "suleman123".toCharArray())
.getKeyPair("resourcekey");
converter.setKeyPair(keyPair);
return converter;
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
System.out.println("*********** running configure(ClientDetailsServiceConfigurer clients) ***********");
clients.inMemory()
.withClient("acme") // registers a client with client Id 'acme'
.secret("acmesecret") // registers a client with password 'acmesecret'
.authorizedGrantTypes("authorization_code", "refresh_token",
"password") // We registered the client and authorized the “password“, “authorization_code” and “refresh_token” grant types
.scopes("openid") // scope to which the client is limited
.autoApprove(true);
}
/**
*
*/
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
System.out.println("*********** running configure(AuthorizationServerEndpointsConfigurer endpoints) ***********");
// we choose to inject an existing authentication manager from the spring container
// With this step we can share the authentication manager with the Basic authentication filter
endpoints.authenticationManager(authenticationManager)
.accessTokenConverter(jwtAccessTokenConverter());
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer)
throws Exception {
System.out.println("*********** running configure(AuthorizationServerSecurityConfigurer oauthServer) ***********");
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess(
"isAuthenticated()");
}
}
Finally got this working. What I have done to make it work:
1) I have installed 'react-cookie' library
npm install react-cookie --save
2) In my reactjs code I have imported react-cookie library and in method where I am using restful http client to generate logout request I am fetching Csrf-Token from cookie and sending it as request header.
handleLogout = (e) => {
client({
method: 'POST',
path: 'logout',
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf8',
'X-Requested-With': 'XMLHttpRequest',
'X-Csrf-Token': Cookie.load('XSRF-TOKEN')
}
}).then(response => {
this.setState({authenticated: false});
console.log(response);
});
}
3) In authorization server instead of using my custom Cors Filter class which I have mentioned in my question, now I am using Spring Cors Filter code
#Configuration
public class CorsFilterConfig {
#Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(0);
return bean;
}
}
4) In application.properties file of Authorization Server I have added this property, so CorsFilter will run before SpringSecurityFilterChain
security.filter-order=50

Resources