如何解决跨域问题

41 阅读5分钟

跨域请求

1.跨域解释

1.什么是跨域

  1. 浏览器出于安全的考虑,使用 XMLHttpRequest对象发起 HTTP请求时必须遵守同源策略,否则就是跨域的HTTP请求,默认情况下是被禁止的。 同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致。

  2. 常见的跨域场景包括:

    • 主域名相同,子域名不同的站点间访问
    • 不同域名下的站点相互访问,如淘宝和京东
    • 不同端口下的站点相互访问,如localhost:8080和localhost:8081
    • 协议不同的站点相互访问,如http和https
  3. 同源策略

    请求的时候拥有相同的协议 域名 端口 只要有一个不同就属于跨域

    主机(http://localhost:8080)是否跨域原因
    https://localhost:8080协议不同
    http://localhost:8082端口号不同
    www.baidu.com域名不同
    http://localhost:8080/test.html端口 协议 域名 都相同

2.怎样解决跨域

​ 可使用前端/后端处理

2.前端处理

1.jsonp

  1. 原理就是通过script的src不受同源策略的限制,可以跨域请求数据 但是只能通过get请求

  2. 缺点:只能通过get请求,不安全,不容易维护

  3. 优点:兼容性好

  4. 后端返回的是一个函数,不是字符串/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.前端处理

  1. 可以在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代理

  1. 下载nginx

    nginx下载

  2. 文件夹

image-20230809002120071.png

image-20230809002151316.png

打开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地址:端口号

```

1691554580691.png

2.  前端所有的请求都指向nginx的代理地址,不直接访问实际接口

```css
fetch('/api/user')
```

```css
axios.get('/api/user/findAll').then(res=>{
  data = res.data
})
```

3.  nginx收到请求后会将其转发给服务器端口,并获取响应

4.  nginx将响应结果返回给前端

3.后端处理

1.添加@CrossOrigin注解

  1. 源码

    @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可以加在方法

  2. 示例

    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解决

  1. 可以使用匿名内部类

    @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)
                }
            };
        }
    }
    
  2. 通过继承实现

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

image-20230809111631195.png

注意:

要注意配置的先后顺序,顺序不可出错

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);
    }
}
```