element-ui+springboot文件上传时报403错误
问题
正如标题所述,在使用element-ui的文件上传组件向springboot后端上传文件时报403forbidden错误,而使用axios向后端发送请求时并未出现错误,且springboot后端并未使用spring security或设置拦截器。并且在后端直接使用html页面进行文件上传时并未出现错误,所以猜测是跨源(cross-origin)导致的问题。也就是说问题在于某个跨源请求被禁止了,所以解决问题的关键在于解决跨源问题。
如上图,浏览器的控制台具体的报错信息也印证了上面的猜测,确实是跨源请求导致的错误。
我的相关代码
Vue解决跨源请求的代码
在config/index.js中修改proxyTable为:
proxyTable: {
'/api': {
target: 'http://localhost:8080/',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
在src/main.js中设置axios:
// 设置全局的axios
Vue.prototype.$axios = axios
// 设置基本路径
axios.defaults.baseURL = '/api'
前端上传文件代码
<template>
<div>
<div>
<el-upload
:before-remove="beforeRemove"
:file-list="fileList"
:limit="1"
:on-exceed="handleExceed"
:on-preview="handlePreview"
:on-remove="handleRemove"
action="http://localhost:8080/admin/upload/course"
class="upload-demo"
multiple
name="file">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传excel文件</div>
</el-upload>
</div>
</div>
</template>
<script>
export default {
data () {
return {
fileList: []
}
},
methods: {
handleRemove (file, fileList) {
console.log(file, fileList)
},
handlePreview (file) {
console.log(file)
},
handleExceed (files, fileList) {
this.$message.warning(`当前限制选择 1 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`)
},
beforeRemove (file, fileList) {
return this.$confirm(`确定移除 ${file.name}?`)
}
}
}
</script>
<style scoped>
</style>
后端接收代码
@RequestMapping("/admin/upload/course")
public boolean uploadCourse(@RequestParam("file") MultipartFile file) throws IOException {
log.warn("{}", file);
return true;
}
解决办法
方法一
既然是跨源问题,那么简单粗暴地直接粘贴以下代码解决跨源问题即可。
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@SpringBootConfiguration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
.allowCredentials(true)
.maxAge(3600)
.allowedHeaders("*");
}
}
简单说明一下上面的方法做了什么:
首先,addCorsMappings()
方法用于配置“全局”跨源请求处理。然后,配置了什么呢?配置内容如下:
.addMapping()
:该方法配置可以被跨源请求的路径。支持精确的路径映射URI(如“/admin”)以及Ant风格的路径模式(path patterns)(如“/admin/**”)。.allowedOriginPatterns()
:该方法是allowedOrigins()方法的替代方案,allowedOriginPatterns它支持更灵活的模式来指定允许从浏览器进行跨源请求的源(即配置允许请求的前端URL)。.allowedMethods()
:该方法用于设置允许用“以下方法”来请求访问跨域(cross-domain)资源。“以下方法”指的是HTTP方法,例如“GET”、“POST”等。而特殊值“*”指允许所有方法。默认情况下,允许使用“简单”方法GET、HEAD和POST。.allowCredentials()
:官方对于这个方法的注释是:(没看懂注释中“the annotated endpoint”具体指的是哪,求教)
Whether the browser should send credentials, such as cookies along with cross domain requests, to the annotated endpoint. The configured value is set on the Access-Control-Allow-Credentials response header of preflight requests.
NOTE: Be aware that this option establishes a high level of trust with the configured domains and also increases the surface attack of the web application by exposing sensitive user-specific information such as cookies and CSRF tokens.
By default this is not set in which case the Access-Control-Allow-Credentials header is also not set and credentials are therefore not allowed.
.maxAge()
:该方法可以配置客户端可以缓存预检请求(pre-flight request)的响应的时间(单位是秒)。默认设置为1800秒(30分钟)。.allowedHeaders()
:该方法可以设置允许指定的请求header访问,可以自定义设置任意请求头信息,如:"X-YAUTH-TOKEN"。特殊值“*”可用于允许所有header。默认情况下,允许使用所有header。
方法二
既然我已经在Vue中使用proxyTable来解决了跨源问题(见上文“Vue解决跨源请求的代码”),那为什么依然会出现跨源问题呢?
分析代码发现,在element-ui的上传组件中是通过一个“action”属性发送HTTP请求的,而我的action属性是这么写的:action="http://localhost:8080/admin/upload/course"
(注:前端为localhost:8081,后端为localhost:8080)。这样写显然不对,直接请求后端就不会使用webpack为我们提供的http代理服务器(即proxyTable的配置),就必然会出现跨源问题。
综上分析,只需把action属性改为:action="/api/admin/upload/course"
。其中,/api
是我在Vue中配置的“baseURL”;/admin/upload/course
是后端的@RequestMapping
。(见上文“我的相关代码”)
问题解决!
若文中有错漏,欢迎评论指出。