无法从其他来源呼叫安全的Spring Boot休息服务

时间:2018-08-23 20:10:24

标签: rest spring-boot spring-security cors csrf

我有3个Spring Boot应用程序:

localhost:8081(身份验证服务器) 本地主机:8083(UI) 本地主机:8101(上传服务)

当用户转到localhost:8083/app时,它会将其重定向到localhost:8081/login,以向他们显示一个表单以发布其凭据,然后将用户重定向到localhost:8081/app并显示网站。此时没有问题。

但是,我想添加一个上传功能,用户可以将一些文件拖放到具有以下类型文件的输入上:

<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);">

,然后它将调用localhost:8101/upload通过Axios调用上传文件。

为了避免我添加的CSRF问题

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

在HTML和JS中,我可以进行以下操作来设置Axios以使用令牌并发送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;

我在JS中的调用如下:

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

在我的上传服务后端中,我只需要提取文件并写下它们的名称以测试其是否有效:

@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());
        }
    }
}

要允许其他域(UI应用程序)访问上传服务,我按如下所示设置CORS映射:

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

我还读了here,需要为多部分文件上传添加此内容:

public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer {

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

最后,我的安全配置如下:

@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());

    }
}

当我尝试上传文件时,我看到2个具有OPTION状态的localhost:8101 / upload调用都具有相同的响应和请求标头:

响应:

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

请求:

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

但是POST不会发生。因此,为了进一步测试,我在/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());

    }
}

随着它开始工作,我可以在网络中看到一个OPTION和一个POST,并且具有以下请求和响应头:

请求:

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

响应:

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

还可以在日志中看到发布的文件名:

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

因此,呼叫成功完成。但是,由于我希望保护localhost:8101/upload的安全,以便只有授权用户才能上传文件,因此不允许任何人自由使用它。

经过几个小时的研究,我得出的结论是,Axios不会发送cookie。因为在安全的/upload场景中,我看不到请求中发送的任何cookie。

我的问题是:

  • 如果我的理论是正确的,我该如何将Cookies和 请求?
  • 还有其他我可以采用的解决方案/配置吗?

任何帮助将不胜感激。谢谢。

修改1: 我正在使用oauth2作为身份验证服务器,并且在UI和上载服务的application.properties中进行了以下设置:

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 个答案:

答案 0 :(得分:1)

localhost:8081(身份验证服务器)localhost:8083(UI)localhost:8101 所有这些都将被浏览器视为不同的域。因此,其中一个设置的cookie不会与请求一起发送给其他人。您在这里有2个选择

  1. 第一个选择是:如果您要坚持传统的身份验证方式,请让Auth服务器处理所有请求,让其将UI和Upload服务请求代理到相应的上游服务。而且,您可以使用其他方式,例如防火墙-iptables / firewalld或security-groups / ACL(如果您在云上)来控制对其他2个应用程序(UI和Upload)的访问

  2. 第二个选项是:现代化堆栈,将oauth2服务器作为auth服务器,该服务器将在对用户进行身份验证后发出oauth2 / jwt令牌。您的javascript(从在客户端浏览器上运行的网页)应将该令牌以及所有后续请求一起发送。您的其他服务(UI和上载)将提供条款以与Auth服务器对话以验证令牌的有效性。如果发现无效,请将它们重定向到相应的页面。

答案 1 :(得分:1)

我使用Spring Session解决了该问题。 Spring Session使用Redis,并且由于我在Windows上,因此我按照here的说明在Windows上运行Redis。

我从https://github.com/ServiceStack/redis-windows/raw/master/downloads/redis-latest.zip下载了Redis,并将其解压缩到C:\Redis中。 通过命令提示符导航到目录后,我运行了命令redis-server.exe redis.windows.conf(或Powershell中的./redis-server.exe redis.windows.conf

我在用户界面和上载服务的POM文件中都添加了以下内容

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

最后,我在下面将UI和Upload服务的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

这里的密码是空的,因为默认情况下Redis没有设置密码,我没有设置密码用于测试。

重新启动应用程序时,我看到当我登录UI应用程序时,可以使用UI应用程序中的会话成功调用上载服务。