Unable to call a secured spring boot rest service from a different origin

Unable to call a secured spring boot rest service from a different origin



I have 3 spring boot applications:



localhost:8081 (Authentication server)
localhost:8083 (UI)
localhost:8101 (Upload service)



When the users goes to localhost:8083/app it redirects it to localhost:8081/login to present them a form to post their credentials and it redirects users back to localhost:8081/app and displays the website. There is no issue at this point.


localhost:8083/app


localhost:8081/login


localhost:8081/app



However, I wanted to add an upload feature where the user drags/drops some files onto a input with type file like this:


<input type="file" multiple style="height: 100%; width: 100%; z-index: 100; opacity:0" v-bind:name="uploadFieldName" v-bind:disabled="isSaving" v-on:change="filesChange($event.target.name, $event.target.files);">



and it will then call localhost:8101/upload to upload the file via an Axios call.


localhost:8101/upload



To avoid CSRF issues I added


<meta th:name="_csrf" th:content="$_csrf.token"/>
<meta th:name="_csrf_header" th:content="$_csrf.headerName"/>



to the HTML and in JS I have the following to setup Axios to use the token and send the cookie:


var csrfHeader = $("meta[name='_csrf_header']").attr("content");
var csrfToken = $("meta[name='_csrf']").attr("content");
axios.defaults.headers =
'XSRF-TOKEN': csrfToken

axios.defaults.withCredentials = true;



My call in JS is as follows:


upload(formData)
var url = 'http://localhost:8101/upload';
return axios.post(url, formData);



In my upload service backend I simply take the files and write their names to test if it works:


@RestController
public class AssetController
@RequestMapping(value = "/upload", method = RequestMethod.POST)
public void importAssets(@RequestParam("upload") MultipartFile files)
for(MultipartFile file: files)
System.out.println(file.getOriginalFilename());





To allow other domains (the UI application) to access the upload service, I set up the CORS mapping as follows:


@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter
@Override
public void addCorsMappings(CorsRegistry registry)
registry.addMapping("/**");




I also read here that I need to add this for multipart file uploads:


public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer

@Override
protected void beforeSpringSecurityFilterChain(ServletContext servletContext)
insertFilters(servletContext, new MultipartFilter());




And finally I have my security configuration as follows:


@EnableOAuth2Sso
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter
protected void configure(HttpSecurity httpSecurity) throws Exception
httpSecurity.antMatcher("/**")
.authorizeRequests()
.antMatchers("/login**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());





When I try to upload files I see 2 localhost:8101/upload calls with OPTION status both having the same response and request headers:



Response:


HTTP/1.1 302
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Set-Cookie: XSRF-TOKEN=0ef5a80f-4ffc-49ab-bfb0-813ae4ca149e; Path=/
Location: http://localhost:8101/login
Content-Length: 0
Date: Thu, 23 Aug 2018 19:57:08 GMT



Request:


OPTIONS /upload HTTP/1.1
Host: localhost:8101
Connection: keep-alive
Access-Control-Request-Method: POST
Origin: http://localhost:8083
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36
Access-Control-Request-Headers: xsrf-token
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,tr;q=0.8



However the POST does not happen. So to test further I permitted /upload**in the SecurityConfig as follows:


/upload**


SecurityConfig


@EnableOAuth2Sso
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter
protected void configure(HttpSecurity httpSecurity) throws Exception
httpSecurity.antMatcher("/**")
.authorizeRequests()
.antMatchers("/login**","/upload**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());





With that it started working, I can see an OPTION and a POST in network and I have the following request and response headers:



Request:


POST /upload HTTP/1.1
Host: localhost:8101
Connection: keep-alive
Content-Length: 2507057
Origin: http://localhost:8083
X-XSRF-TOKEN: 4b070c11-9250-40d6-9d2a-d6587e814382
XSRF-TOKEN: 52f8fd81-2c80-493e-b7c6-f0a458b8c2e9
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7lG6mO10sPzrDU5l
Accept: */*
Referer: http://localhost:8083/toybox
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,tr;q=0.8
Cookie: JSESSIONID=0EF7E75D8A8187BC7B5FB74341207E76; TSESSION=4A8DE8A4A4A430DF2AC9AF14A0BD0E50; XSRF-TOKEN=4b070c11-9250-40d6-9d2a-d6587e814382



Response:


HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
X-Application-Context: toybox-asset-service:8101
Access-Control-Allow-Origin: http://localhost:8083
Vary: Origin
Access-Control-Allow-Credentials: true
Content-Length: 0
Date: Thu, 23 Aug 2018 20:05:47 GMT



Also in the log I can see the file names posted:


cat-pet-animal-domestic-104827.jpeg
kittens-cat-cat-puppy-rush-45170.jpeg



So the call is successfully made. However, since I want the localhost:8101/upload to be secured so that only authorized users can upload files, permitting it to be freely used by anyone is not an option.


localhost:8101/upload



After researching some hours, I come to the conclusion that, somehow, the cookies are not being sent by Axios. Because in the secure /upload scenario I see no cookies being sent in the request.


/upload



My questions are:



Any help would be appreciated. Thank you.



Edit 1:
I am using oauth2 as the authentication server and I have the following setup in application.properties in both UI and the upload service:


security.oauth2.client.client-id=client
security.oauth2.client.client-secret=secret
security.oauth2.client.access-token-uri=http://localhost:8081/oauth/token
security.oauth2.client.user-authorization-uri=http://localhost:8081/oauth/authorize
security.oauth2.resource.user-info-uri=http://localhost:8081/me




2 Answers
2



localhost:8081 (Authentication server) localhost:8083 (UI) localhost:8101
All these will get treated like a different domain by the browser. So a cookie set by one of them will not be sent along with the request to others. You have 2 options here



First option is: If you want to stick with the traditional way of authenticating, let your Auth server handle all the request, let it proxy the UI and Upload service request to the corresponding upstream services. And you can use other means such as firewall - iptables/firewalld or security-groups/ACLs(if you are on cloud) to control access to other 2 apps (UI and Upload)



Second Option is: Modernize your stack, have oauth2 server as the auth server which will issue oauth2/jwt token after authenticating the user. Your javascript (from your webpage running on client's browser) should send that token along with all subsequent requests. Your other services (UI and Upload) will have provisions to talk to Auth server to verify the validity of the token. If found invalid, redirect them to the corresponding page.





Actually I was already using oauth2 server as authentication server. UI service authenticates successfully but the upload service does not for some reason. I added the details to the question.
– Murat Aykanat
Aug 24 at 8:56




I resolved the issue by using Spring Session. Spring Session uses Redis, and since I am on Windows, I followed the instructions here for running Redis on Windows.



I downloaded Redis from https://github.com/ServiceStack/redis-windows/raw/master/downloads/redis-latest.zip and unzipped it into C:Redis.
After navigating to the directory via the command prompt, I ran the command redis-server.exe redis.windows.conf (or ./redis-server.exe redis.windows.conf in Powershell)


C:Redis


redis-server.exe redis.windows.conf


./redis-server.exe redis.windows.conf



I added the following to both UI and the Upload services' POM files


<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>



And finally I added below to application.properties files of both UI and Upload services:


application.properties


spring.session.store-type=redis
server.servlet.session.timeout=3600
spring.session.redis.flush-mode=on-save
spring.session.redis.namespace=spring:session

spring.redis.host=localhost
spring.redis.password=
spring.redis.port=6379



Here password is empty because by default Redis does not have a password set, I didn't set one for testing purposes.



When I restarted my applications, I see that when I login in the UI application, I can successfully call the Upload service using the session I have in the UI application.






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

Edmonton

Crossroads (UK TV series)