dev 模式
  • 利用vite 配置代理,可以解决前端浏览器限制跨域问题(当然后端要自己配置好)

    export default defineConfig({
        // 配置在打包时,保障所有css\js能被找到
        base: './',
        
        //自带配置
        plugins: [vue()],
        resolve: {
            alias: {
                '@': fileURLToPath(new URL('./src', import.meta.url))
            }
        },
        
        // 配置/api代理
        server: {
            proxy: {
                '/api': {
                    target: 'http://localhost:8080',
                    changeOrigin: true,
                    rewrite: (path) => path.replace(/^\/api/, '')
                }
            }
        },
    
        //打包前先清空原有打包文件
        build: {
            emptyOutDir: true,
        }
    })
    

    配置.env

    在dev 环境,默认会读取这个里面的内容

    # .env.development
    
    VITE_BASE_API=/api
    VITE_BASE_URL=/vaccinationInfo
    VITE_BASE_ENV=dev
    

    配置axios

    import axios from "axios";
    
    const service = axios.create({
        baseURL: import.meta.env.VITE_BASE_API as string,
        timeout: 3600
    })
    

    这样在dev环境下,就可以使用代理来访问后端了

pro模式

打包时,会自动识别 .env.production

# .env.production 
VITE_BASE_API=http://localhost:8080
VITE_BASE_URL=/vaccinationInfo
VITE_BASE_ENV=pro

由于生产模式时,代理配置时不生效的,所以VITE_BASE_API直接指向服务器地址

history模式 时 还要进行以下配置

router\index.ts

history: createWebHistory(import.meta.env.VITE_BASE_URL as string),

用一个指定的url地址

nginx 配置

当然,打包后放到nginx也需要配置

  location /vaccinationInfo {
        alias  /usr/share/nginx/html/vaccinationInfo;
        index  index.html index.htm;
        try_files $uri $uri/ /vaccinationInfo/index.html; # 解决页面刷新404
    }	

然后在html中新建vaccinationInfo文件夹,把打包好的文件丢进去就行

hash模式

如果不想每写一个项目都配置一次nginx后端,则可以用hash模式

  • 子项目情况
    例如把项目放在nginx的html/test/文件夹下
    vite.config.ts
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue()
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
  // 配置为要放的文件夹
  base: '/test/'
})

router文件夹下index.ts

import { createRouter, createWebHashHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

const router = createRouter({
  // 把createWebHistory替换为createWebHashHistory即可,其他什么都不用配置
  // 同样也适用于createWebHistory模式,即只修改vite的base为nginx文件夹,而不修改createWebHistory()括号里的内容
  // 如果vite的base改为/test/,createWebHistory('kk'),访问http://localhost/test/会自动跳转为http://localhost/kk/,首页内容不会加载,不要这么写
  // import.meta.env.BASE_URL指向的就是vite配置中的base,默认是'/',配置为''或者'./'才可以在打包的index.html中指向./相对路径
  history: createWebHashHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView
    },
    {
      path: '/about',
      name: 'about',
      component: () => import('../views/AboutView.vue')
    }
  ]
})

export default router

hash模式下,
访问about,网页为 http://localhost/test/#/about

后端配置

写一个配置类

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    /**
     * 允许跨域,以及自行配置
     *
     * @param registry 跨域注册器
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOriginPatterns("*") // SpringBoot2.4.0以上[allowedOriginPatterns]代替[allowedOrigins]
                .allowedMethods("*")
                .maxAge(3600)
                .allowedHeaders("*")
                .allowCredentials(true);
    }
}

如果需要配置拦截器拦截JWT,可以采取以下操作

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    private JWTTokenInterceptor jwtTokenInterceptor;

    private InterceptorPathBean interceptorPathBean;

    @Autowired
    public void setJwtTokenInterceptor(JWTTokenInterceptor jwtTokenInterceptor) {
        this.jwtTokenInterceptor = jwtTokenInterceptor;
    }

    @Autowired
    public void setInterceptorPathBean(InterceptorPathBean interceptorPathBean) {
        this.interceptorPathBean = interceptorPathBean;
    }

    /**
     * 允许跨域,以及自行配置
     *
     * @param registry 跨域注册器
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOriginPatterns("*") // SpringBoot2.4.0以上[allowedOriginPatterns]代替[allowedOrigins]
                .allowedMethods("*")
                .maxAge(3600)
                .allowedHeaders("*")
                .allowCredentials(true);
    }

    /**
     * 添加API拦截器,对所有数据API进行拦截
     *
     * @param registry 拦截器注册器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册拦截规则
        InterceptorRegistration interceptorRegistration = registry.addInterceptor(jwtTokenInterceptor);
        // 拦截配置
        interceptorRegistration.addPathPatterns(interceptorPathBean.getInclude());
    }

}

重写addInterceptors 方法

配置JWT拦截器

@Component
public class JWTTokenInterceptor implements HandlerInterceptor {

    @Resource
    private JWTResult jwtResult;

    /**
     * 拦截对数据API的请求,判断jwt令牌是否有效,没有则返回401
     *
     * @param request  请求
     * @param response 响应
     * @param handler  处理器
     * @return 是否继续执行,true继续执行,false不继续执行
     * @throws Exception 异常
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //非简单请求会预先使用OPTIONS方法请求一次,这里直接返回true
        if (request.getMethod().equals("OPTIONS")) {
            response.setStatus(200);
            //在拦截器中设置允许跨域
            response.setHeader("Access-Control-Allow-Origin", "*");
            response.setHeader("Access-Control-Allow-Headers", "*");
            response.setHeader("Access-Control-Allow-Methods", "*");
            response.setHeader("Access-Control-Allow-Credentials", "true");
            response.setHeader("Access-Control-Max-Age", "3600");
            return true;
        }

        //业务逻辑,自行发挥
        //这才是真正的请求,需要验证jwt令牌
        //请求数据API时的目标url
        String path = request.getRequestURI();
        String jwt = request.getHeader("Authorization");

        //对每次数据API请求进行拦截,如果jwt令牌不合法,则返回401;通过则继续放行,因此数据controller不需要再次判断jwt令牌是否合法
        boolean verify = jwtResult.verify(jwt,path);
        //如果校验不通过
        if (!verify) {
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().write("{\"code\":401,\"msg\":\"jwt校验失败\"}");
        }
        return verify;
    }

}

以上是重点处理 OPTIONS 预先请求,这个在非简单请求时会预先发出,典型场景就是打包后的前端工程,在请求后端是就会发出这个OPTIONS请求。

后面那些就是业务逻辑,自行发挥即可

补充几个文件

InterceptorPathBean

@Data
@Component
@ConfigurationProperties(prefix = "interceptor-config.path") // 配置文件的前缀
public class InterceptorPathBean {
    /*
     * 需要拦截的路径
     */
    private List<String> include;
}

application.yml

# 拦截器路径拦截
interceptor-config:
  path:
    #该路径下任何类型请求均拦截
    include:
      - /telInfo/**
      - /vaccinationInfo/**

以上,就可以在vue中站着用history模式了

解决私有网络请求

chrome升级94版本以后,禁止私有网络请求

  • 私有网络请求是其目标服务器的 IP 地址比获取请求发起者的 IP 地址更私有的请求。例如,从公共网站 ( http://example.com) 到私有(private)网站 (http://192.168.0.1) 的请求,或从私有网站到 localhost 的请求

  • 具体划分,不在下面表格的都属于public网站,等级由public-》private-》local

    Address blockNameReferenceAddress space
    127.0.0.0/8IPv4 Loopback[RFC1122]local
    10.0.0.0/8Private Use[RFC1918]private
    100.64.0.0/10Carrier-Grade NAT[RFC6598]private
    172.16.0.0/12Private Use[RFC1918]private
    192.168.0.0/16Private Use[RFC1918]private
    198.18.0.0/15Benchmarking[RFC2544]local
    169.254.0.0/16Link Local[RFC3927]private
    ::1/128IPv6 Loopback[RFC4291]local
    fc00::/7Unique Local[RFC4193]private
    fe80::/10Link-Local Unicast[RFC4291]private
    ::ffff:0:0/96IPv4-mapped[RFC4291]see mapped IPv4 address
  • 不受影响的请求方式

    • public请求public
    • private、local请求public
    • local请求public、private
  • 受影响的请求方式

    • public请求private、local
    • private请求local
  • 解决方案

    • 将发起请求的页面升级为https

      • 发起请求页面升级方式,利用nginx,单独创建一个conf

        #default_ssl.conf
        server {
            listen       443 ssl;
            server_name  localhost;
         
            
            # ssl证书地址
            ssl_certificate    /etc/nginx/cert/68.1.1.1.crt;
            ssl_certificate_key /etc/nginx/cert/68.1.1.1.key;		
        
            # ssl配置
            # 客户端重用会话缓存时间
            ssl_session_timeout 12h;
            # 协议类型
            ssl_protocols TLSv1.2 TLSv1.3;
            #请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。
            ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
            # 用服务端协议优先
            ssl_prefer_server_ciphers on;
            # 共用ssl缓存的大小
            ssl_session_cache shared:SSL:5m;
        
            # 监听的前端页面
            location /smpki {
                alias  /usr/share/nginx/html/smpki;
                index  index.html index.htm;
                try_files $uri $uri/ /smpki/index.html; # 解决页面刷新404
            }
        
        }
        
      • 生成自签证书gencert.sh

        #!/bin/sh
        
        # create self-signed server certificate:
        
        read -p "Enter your domain [www.example.com]: " DOMAIN
        
        echo "Create server key..."
        
        openssl genrsa -des3 -out $DOMAIN.key 2048
        
        echo "Create server certificate signing request..."
        
        SUBJECT="/C=US/ST=Mars/L=iTranswarp/O=iTranswarp/OU=iTranswarp/CN=$DOMAIN"
        
        openssl req -new -subj $SUBJECT -key $DOMAIN.key -out $DOMAIN.csr
        
        echo "Remove password..."
        
        mv $DOMAIN.key $DOMAIN.origin.key
        openssl rsa -in $DOMAIN.origin.key -out $DOMAIN.key
        
        echo "Sign SSL certificate..."
        
        openssl x509 -req -days 3650 -in $DOMAIN.csr -signkey $DOMAIN.key -out $DOMAIN.crt
        
        echo "TODO:"
        echo "Copy $DOMAIN.crt to /etc/nginx/ssl/$DOMAIN.crt"
        echo "Copy $DOMAIN.key to /etc/nginx/ssl/$DOMAIN.key"
        echo "Add configuration in nginx:"
        echo "server {"
        echo "    ..."
        echo "    listen 443 ssl;"
        echo "    ssl_certificate     /etc/nginx/ssl/$DOMAIN.crt;"
        echo "    ssl_certificate_key /etc/nginx/ssl/$DOMAIN.key;"
        echo "}"
        
        • linux 服务器中,sh gencert.sh即可,随便属于域名都可以,密码重复4次,,用 D O M A I N . c r t , DOMAIN.crt, DOMAIN.crtDOMAIN.key这两个
    • 调整chrome的Block insecure private network requests(不推荐)

  • 注意禁止混合请求

    • 由于chrome的block:mixed-content存在,禁止https请求http网站。因此即使不受私有网络请求的限制,也需要把被请求的网站升级为https

    • http->http、http->https不受限制

    • 并不是所有的http资源都受到这个禁止混合请求的影响,主要是以下几种

      类型常用的几个使用场景
      mixed passive content<img><audio><video><object>
      mixed active content<script><link><iframe>XMLHttpquestfetch()
      • 大部分浏览器直接阻止mixed active content,有部分浏览器会阻止mixed passive content
    • 解决方案

      • 将被请求的网站升级https

        • 方案一:springboot直接升级为https

          • 明显缺陷就是在自签SSL证书的情况下,证书不被浏览器信任,由于没有点击浏览器继续浏览这一步,axios发送的请求会报net::ERR_CERT_AUTHORITY_INVALID错误,无论http->https,https->https,都无法发送成功

          • 无论是配置axios忽略证书校验

            如图
            在这里插入图片描述

          • 同时在vite下,还不能用node中的模块https,还必须polyfill, 参考,但是在vite4.4.1版本下,dev环境成功过,打包还出现问题,放弃

          • 或者是配置vue信任本地证书
            在这里插入图片描述

          • 都无法成功,究其原因是证书不能在本地主机上工作,所有的ssl证书都必须在域名上工作

        • 方案二:用一个https页面来做代理

          • 如https://68.1.1.1:443的nginx前端(就是原本发起页的同域,共用一个端口443),代理spring boot http://68.1.1.1:8080

            # default_ssl.conf
            server {
                listen       443 ssl;
                server_name  localhost;
             
                
                # ssl证书地址
                ssl_certificate    /etc/nginx/cert/68.1.1.1.crt;
                ssl_certificate_key /etc/nginx/cert/68.1.1.1.key;		
            
                # ssl配置
                # 客户端重用会话缓存时间
                ssl_session_timeout 12h;
                # 协议类型
                ssl_protocols TLSv1.2 TLSv1.3;
                #请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。
                ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
                # 用服务端协议优先
                ssl_prefer_server_ciphers on;
                # 共用ssl缓存的大小
                ssl_session_cache shared:SSL:5m;
            
                # 监听的前端页面
                location /smpki {
                    alias  /usr/share/nginx/html/smpki;
                    index  index.html index.htm;
                    try_files $uri $uri/ /smpki/index.html; # 解决页面刷新404
                }
                
                # 代理后端的监听页面
                location ^~ /proxy_8080/ {
            		proxy_pass  http://68.1.1.1:8080/;
            	}
            
            }
            
            • 请求 https://68.1.1.1:443/proxy_8080/就是请求http://68.1.1.1:8080/

            • 这样后端spring boot就不用改造为http,打包时只需要判断是https还是http,就知道用https代理,还是直接http请求

              # 在.env.production中新增
              VITE_BASE_API_HTTPS = https://68.1.1.1:443/proxy_8080
              
              # axios可以如下操作
                  let isHttps = 'https:' == document.location.protocol
                  let url = import.meta.env.VITE_BASE_API as string
                  // dev环境肯定不是https,所以.env.dev即使没有定义VITE_BASE_API_HTTP也没关系
                  // pro环境如果不是https,就直接走.env.pro中预定义的http后端
                  if (isHttps){
                    url = import.meta.env.VITE_BASE_API_HTTPS as string
                  }
              
            • 注意,同一个页面用代理,必须在 location 后面加上 ^~

            • 适用于springboot后端接口

            或者单独开一个端口来监听代理,创建新的.conf,监听441端口

            # default_ssl_proxy.conf
            server {
                listen       441 ssl;
                server_name  localhost;
             
                
                # ssl证书地址
                ssl_certificate    /etc/nginx/cert/68.50.10.87.crt;
                ssl_certificate_key /etc/nginx/cert/68.50.10.87.key;		
            
                # ssl配置
                # 客户端重用会话缓存时间
                ssl_session_timeout 12h;
                # 协议类型
                ssl_protocols TLSv1.2 TLSv1.3;
                #请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。
                ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
                # 用服务端协议优先
                ssl_prefer_server_ciphers on;
                # 共用ssl缓存的大小
                ssl_session_cache shared:SSL:5m;
            
                # 用根路径代理后端8080端口
                location / {
                    proxy_pass http://68.1.1.1:8080/;
                }
            
                # 代理后端8080端口
            #    location /proxy_8080/ {
            #        proxy_pass http://68.1.1.1:8080/;
            #    }
            }	
            
            • 请求 https://68.1.1.1:441/就是请求http://68.1.1.1:8080/
            • 请注意,新代理的ssl证书不要又用其他SSL证书,因为只有用与前端相同的证书,才能让浏览器信任此自签证书,否则AXIOS时又因为新的自签证书而导致net::ERR_CERT_AUTHORITY_INVALID
            • 如果时新的端口代理,location不需要加^~
            • 如果是代理普通网页最好用域名做代理,虽然请求内容是没问题。但可以原网页有些静态资源如JS、css走相对路径,会加载不完全。
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐