跨域请求
1.跨域解释
1.什么是跨域
-
浏览器出于安全的考虑,使用 XMLHttpRequest对象发起 HTTP请求时必须遵守同源策略,否则就是跨域的HTTP请求,默认情况下是被禁止的。 同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致。
-
常见的跨域场景包括:
- 主域名相同,子域名不同的站点间访问
- 不同域名下的站点相互访问,如淘宝和京东
- 不同端口下的站点相互访问,如localhost:8080和localhost:8081
- 协议不同的站点相互访问,如http和https
-
同源策略
请求的时候拥有相同的协议 域名 端口 只要有一个不同就属于跨域
主机(http://localhost:8080) 是否跨域 原因 https://localhost:8080 是 协议不同 http://localhost:8082 是 端口号不同 www.baidu.com 是 域名不同 http://localhost:8080/test.html 否 端口 协议 域名 都相同
2.怎样解决跨域
可使用前端/后端处理
2.前端处理
1.jsonp
-
原理就是通过script的src不受同源策略的限制,可以跨域请求数据 但是只能通过get请求
-
缺点:只能通过get请求,不安全,不容易维护
-
优点:兼容性好
-
后端返回的是一个函数,不是字符串/json,但是这个函数是在前端定义的,它会把后端返回的数据作为参数传入
<script>
//jsonp
//原理就是通过script的src不受同源策略的限制,可以跨域请求数据 但是只能通过get请求
//缺点: 1.只能通过get请求 2.不安全 3.不容易维护
//优点: 兼容性好
//后端返回的是一个函数 不是字符串/json 但是这个函数是在前端定义的 它会把后端返回的数据作为参数传入
// jsonp函数
const jsonp = (name) => {
// 创建script标签
let script = document.createElement('script')
// 设置src,拼接请求地址和回调函数名
script.src = `http://localhost:8081/api/user/findAll?callback=${name}`
// 插入文档发起请求
document.body.appendChild(script)
// 返回Promise
return new Promise((resolve) => {
// 在window上定义回调函数
window[name] = (data) => {
// 调用resolve获取数据
resolve(data)
// 删除script标签
document.body.removeChild(script)
}
})
}
// 生成回调函数名(时间戳)
jsonp(`callback${new Date().getTime()}`).then(res => {
// 输出结果
console.log(res)
})
</script>
2.前端处理
-
可以在vite中添加proxy代理来处理
import { fileURLToPath, URL } from 'node:url' import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import vueJsx from '@vitejs/plugin-vue-jsx' // https://vitejs.dev/config/ export default defineConfig({ plugins: [ vue(), vueJsx(), ], resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } }, //添加代理 server:{ proxy:{ '/api':{ target: 'http://localhost:8081', changeOrigin: true, //rewrite: (path) => path.replace(/^\/api/, '') } } } })
3.nginx代理
-
下载nginx
-
文件夹
打开nginx.conf
```
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 88;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
# location / {
# root html;
# index index.html index.htm;
# }
#}
# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
}
```
需要添加该语句
1. 配置nginx的反向代理,指向实际的服务器端接口地址
```json
//需要在此加上该语句
location /api {
proxy_pass http://192.168.48.1:8081;
}
http://此处为电脑本机的ip地址:端口号
```
2. 前端所有的请求都指向nginx的代理地址,不直接访问实际接口
```css
fetch('/api/user')
```
```css
axios.get('/api/user/findAll').then(res=>{
data = res.data
})
```
3. nginx收到请求后会将其转发给服务器端口,并获取响应
4. nginx将响应结果返回给前端
3.后端处理
1.添加@CrossOrigin注解
-
源码
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface CrossOrigin { @Deprecated String[] DEFAULT_ORIGINS = {"*"}; @Deprecated String[] DEFAULT_ALLOWED_HEADERS = {"*"}; @Deprecated boolean DEFAULT_ALLOW_CREDENTIALS = false; @Deprecated long DEFAULT_MAX_AGE = 1800; @AliasFor("origins") String[] value() default {}; @AliasFor("value") String[] origins() default {}; String[] originPatterns() default {}; String[] allowedHeaders() default {}; String[] exposedHeaders() default {}; RequestMethod[] methods() default {}; String allowCredentials() default ""; long maxAge() default -1; }
从注解中可以看出,@CrossOrigin可以加在
方法
和类
上 -
示例
类
package jiasen.controller; import jiasen.RestBean.RestBean; import jiasen.entity.User; import jiasen.server.UserService; import org.apache.ibatis.annotations.Param; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/api") @CrossOrigin public class UserController { @Autowired private UserService userService; //查询所有用户 @RequestMapping(value = "/user/findAll",method = RequestMethod.GET) public RestBean findAllUser(){ List<User> userList=userService.findAllUser(); //判断是否查询到用户 if (userList.isEmpty()) return new RestBean(500,"查询失败"); else return new RestBean(200,"查询成功",userList); } //通过id查找用户 @RequestMapping(value = "/user/findById",method = RequestMethod.GET) public RestBean findUserById(@RequestParam("id") int id){ User user=userService.findUserById(id); //判断是否查询到用户 if (user==null) return new RestBean(500,"查询失败"); else return new RestBean(200,"查询成功",user); } //插入用户 @RequestMapping(value = "/user/insert",method = RequestMethod.POST) public RestBean insertUser( User user){ int result=userService.insertUser(user); Integer id = user.getId(); //判断是否插入成功 if (result==0) return new RestBean(500,"插入失败"); else return new RestBean(200,"插入成功",userService.findUserById(id)); } //删除用户 @RequestMapping(value = "/user/delete",method = RequestMethod.DELETE) public RestBean deleteUser(@RequestParam("id") int id){ User user=userService.findUserById(id); int result=userService.deleteUser(id); //判断是否删除成功 if (result==0) return new RestBean(500,"删除失败"); else return new RestBean(200,"删除成功",user); } }
方法
@CrossOrigin //查询所有用户 @RequestMapping(value = "/user/findAll",method = RequestMethod.GET) public RestBean findAllUser(){ List<User> userList=userService.findAllUser(); //判断是否查询到用户 if (userList.isEmpty()) return new RestBean(500,"查询失败"); else return new RestBean(200,"查询成功",userList); }
2.CORS解决
-
可以使用匿名内部类
@Configuration public class CORSconfig { @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { registry //允许跨域的路径 .addMapping("**") //允许的请求头 .allowedHeaders("*") //允许的请求方法 .allowedMethods("*") //允许的请求源 .allowedOrigins("*") //是否允许发送cookie .allowCredentials(true) //暴露给客户端的响应头 .exposedHeaders("*") //预检请求的有效期 .maxAge(3600) } }; } }
-
通过继承实现
package jiasen.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class CORSconfig2 implements WebMvcConfigurer { // 跨域配置 @Override public void addCorsMappings(CorsRegistry registry) { registry // 允许跨域访问的路径 .addMapping("/**") /* 要注意配置的先后顺序 * */ //配置允许访问的源 .allowedOriginPatterns("*") // 允许跨域访问的请求头 .allowedHeaders("*") // 允许跨域访问的方法 .allowedMethods("*") // 是否允许跨域Cookie .allowCredentials(true) // 预检间隔时间 .maxAge(3600); } }
注意:
要注意配置的先后顺序,顺序不可出错
1. 配置addMapping,设置允许跨域访问的路径
2. 配置allowedOriginPatterns,设置允许的请求来源
3. 配置allowedHeaders,allowedMethods...跨域访问的具体参数
如果配置了allowCredentials(true),那么就不可以使用allowedOrigins('\*'),不然会报错
When allowCredentials is true, allowedOrigins cannot contain the special value "*" since that cannot be set on the "Access-Control-Allow-Origin" response header. To allow credentials to a set of origins, list them explicitly or consider using "allowedOriginPatterns" instead.
```java
package jiasen.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CORSconfig2 implements WebMvcConfigurer {
// 跨域配置
@Override
public void addCorsMappings(CorsRegistry registry) {
registry
// 允许跨域访问的路径
.addMapping("/**")
/*
要注意配置的先后顺序
*/
//错误,修改为下面的方法
//.allowedOrigins("*")
//配置允许访问的源
.allowedOriginPatterns("*")
// 允许跨域访问的请求头
.allowedHeaders("*")
// 允许跨域访问的方法
.allowedMethods("*")
// 是否允许跨域Cookie
.allowCredentials(true)
// 预检间隔时间
.maxAge(3600);
}
}
```