跨域 以及 shiro解决

跨域 以及 shiro解决

同源策略:协议、域名、端口

作用:能帮助阻隔恶意文档,减少可能被攻击的媒介。

# 但也导致了跨域的问题的出现:前后端分离
# 前端访问后端,不满足同源策略,所以不能访问

1、反向代理解决

# 配置反向代理来实现跨域
# 前端访问代理
# 代理访问后端
# 代理将结果返回给前端
# 从而实现了跨域

2、CORS跨域

CORS(Cross-Origin Resource Sharing)跨域资源共享,定义了必须在访问跨域资源时,浏览器与服务器应该如何沟通。CORS的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

服务器端对于CORS的支持,主要就是通过设置Access-Control-Allow-Origin来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。

服务端允许CORS,服务端需要针对接口设置的一系列响应头 (Response Headers)

该字段必需。设置允许请求的域名,多个域名以逗号分隔,也可以设置成 * 即允许所有源访问
Access-Control-Allow-Origin:  http://www.YOURDOMAIN.com
该字段必需。设置允许请求的方法,多个方法以逗号分隔
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
该字段可选。设置允许请求自定义的请求头字段,多个字段以逗号分隔
Access-Control-Allow-Headers: Authorization
该字段可选。设置是否允许发送 Cookies
Access-Control-Allow-Credentials: true               

2.1请求分类(坑)

客户端向服务器发送请求,分为两种请求:简单请求、复杂请求

简单请求:只能请求一次

复杂请求:会先发送一次预检测请求,服务器返回204(表示预检测请求通过),才会真正发出请求,然后返回200

重点(坑):复杂请求会发送两次请求到服务器

预检测请求的作用

跨域资源共享标准新增了一组 HTTP 首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。另外,规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是GET以外的 HTTP 请求,或者搭配某些 MIME 类型的POST请求),浏览器必须首先使用OPTIONS方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括Cookies和 HTTP 认证相关数据)。

简单请求

目前大多数情况都采用这种方式。简单请求只需要设置Access-Control-Allow-Origin即可。满足以下两个条件,就属于简单请求。

  • 请求方法:GET、POST、HEAD
  • 除了以下的请求头字段之外,没有自定义的请求头
  • Content-Type的值只有以下三种(Content-Type一般是指在post请求中,get请求中设置没有实际意义)
    • text/plain
    • multipart/form-data
    • application/x-www-form-urlencoded
  • 请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器 (未验证)
    • XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问
  • 请求中没有使用 ReadableStream 对象 (未验证)
复杂请求

非简单请求即为复杂请求。复杂请求我们也可以称之为在实际进行请求之前,需要发起预检请求的请求。

非简单请求需要根据不同情况配置不同的响应头,一系列响应头配置项见上方

2.2简单请求复杂请求的跨域设置

针对简单请求,在进行CORS设置的时候,我们只需要设置

Access-Control-Allow-Origin:*
// 如果只是针对某一个请求源进行设置的话,可以设置为具体的值
Access-Control-Allow-Origin: 'http://www.yourwebsite.com'

针对复杂请求,我们需要设置不同的响应头。因为在预检请求的时候会携带相应的请求头信息

Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-CUSTOMER-HEADER, Content-Type

相应的响应头信息为:

Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
// 设置max age,浏览器端会进行缓存。没有过期之前真对同一个请求只会发送一次预检请求
Access-Control-Max-Age: 86400

如果发送的预检请求被进行了重定向,那大多数的浏览器都不支持对预检请求的重定向。我们可以通过先发送一个简单请求的方式,获取到重定向的url XHR.responseURL,然后再去请求这个url。

附带身份凭证的请求

一般而言,对于跨域 XMLHttpRequestFetch 请求,浏览器不会发送身份凭证信息。如果要发送凭证信息,需要设置 XMLHttpRequest 的某个特殊标志位。
如果在发送请求的时候,给xhr 设置了withCredentials为true,从而向服务器发送 Cookies,如果服务端需要想客户端也发送cookie的情况,需要服务器端也返回Access-Control-Allow-Credentials: true响应头信息。

对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin的值为“*”。

这是因为请求的首部中携带了Cookie信息,如果 Access-Control-Allow-Origin的值为“*”,请求将会失败。而将 Access-Control-Allow-Origin的值设置为 http://foo.example(请求源),则请求将成功执行。

// 提示OPTIONS预检时,后端需要设置的两个常用自定义头
response.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With");

总结

普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求:前后端都需要设置。

需注意的是:由于同源策略的限制,所读取的cookie为跨域请求接口所在域的cookie,而非当前页。

如果想实现当前页cookie的写入,用方法一:反向代理来解决

3、引入shiro权限认证导致的跨域问题

shiro使用cookie + session来进行权限认证,cookie的加入,就会使得简单请求变为复杂请求,从而导致跨域失败

解决步骤:

1、给预检请求(OPTION请求)返回204,别把它给拒绝了,设置可以可以跨域

package com.realguo.warehouse.config.shiro;

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.web.filter.authc.AuthenticationFilter;
import org.apache.shiro.web.filter.authc.UserFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
@Configuration
public class CROSUserFilter extends UserFilter {

    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        HttpServletRequest httpRequest = (HttpServletRequest) request;

        if (httpRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            setHeader(httpRequest, httpResponse);
            return true;
        }

        return super.preHandle(request, response);
    }

    private void setHeader(HttpServletRequest request, HttpServletResponse response){
        //跨域的header设置
        response.setHeader("Access-control-Allow-Origin", request.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Methods", request.getMethod());
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
        //防止乱码,适用于传输JSON数据
        response.setHeader("Content-Type","application/json;charset=UTF-8");
        response.setStatus(HttpStatus.OK.value());
    }
}

2、将上面这个对象放到到ShiroFilterFactoryBean中:这样OPTION请求就不会失败

@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
    ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
    bean.setSecurityManager(defaultWebSecurityManager);
    // 在这里放进去
    bean.getFilters().put("authc", new CROSUserFilter());

    Map<String, String> filters = new LinkedHashMap<>();
    filters.put("/user/login", "anon");
    filters.put("/user/add", "anon");
    filters.put("/index.html", "anon");
    // 放行swagger
    filters.put("/swagger-ui/*", "anon");
    filters.put("/v2/api-docs", "anon");
    filters.put("/swagger-resources/**", "anon");
    filters.put("/webjars/**", "anon");
    // 拦截
    filters.put("/**", "authc");
    bean.setFilterChainDefinitionMap(filters);
    bean.setLoginUrl("/index.html");
    return bean;
}

3、正常请求的跨域设置

package com.realguo.warehouse.config.shiro;

import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class CorsFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        //跨域设置,谁来都放行,与设置成*效果相同,但是这里设置成*行不通,因此用该方法代替
        response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Credentials", "true");
        //不能设置成*,否则跨域请求会失败
        response.setHeader("Access-Control-Allow-Methods", "POST,PUT, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        //我这里需要放行这三个header头部字段
        response.setHeader("Access-Control-Allow-Headers", "content-type,x-requested-with,token");
        try {
            filterChain.doFilter(request, response);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ServletException e) {
            e.printStackTrace();
        }
    }

    public void destroy() {
    }
}

4、补充另外小问题

spring boot 接口接收application/x-www-form-urlencoded 的数据为null

解决方法1:

# qs.stringify()将对象 序列化成URL的形式,以&进行拼接


# qs.parse()将URL解析成对象的形式

解决方法2:

# 使用form-data格式 传输数据

参考文档

web前端跨域的一些解决方案

什么是简单请求和复杂请求

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 Spring Boot 和 Shiro 中,可以通过自定义过滤器来解决跨域问题。具体步骤如下: 1. 创建自定义跨域过滤器 创建一个类,继承 org.apache.shiro.web.filter.authc.FormAuthenticationFilter,实现 doFilterInternal 方法。代码如下: ```java public class CorsAuthenticationFilter extends FormAuthenticationFilter { @Override protected void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { HttpServletResponse httpServletResponse = (HttpServletResponse) response; HttpServletRequest httpServletRequest = (HttpServletRequest) request; // 允许跨域访问的域名 String[] allowedOrigins = {"http://localhost:8080"}; // 允许的请求类型 String allowedMethods = "GET,POST,PUT,DELETE,OPTIONS"; // 允许的头信息 String allowedHeaders = "Content-Type, Authorization"; httpServletResponse.setHeader("Access-Control-Allow-Origin", String.join(",", allowedOrigins)); httpServletResponse.setHeader("Access-Control-Allow-Methods", allowedMethods); httpServletResponse.setHeader("Access-Control-Allow-Headers", allowedHeaders); // 如果是预检请求,直接返回成功 if (httpServletRequest.getMethod().equalsIgnoreCase("OPTIONS")) { httpServletResponse.setStatus(HttpServletResponse.SC_OK); return; } super.doFilterInternal(request, response, chain); } } ``` 这里的 allowedOrigins 配置是允许的跨域地址,allowedMethods 配置是允许的 HTTP 方法,allowedHeaders 配置是允许的 HTTP 头信息。如果收到的请求是预检请求,直接返回成功即可。 2. 在 Shiro 配置中添加自定义过滤器 在 Shiro 的配置文件中添加自定义过滤器。例如: ```java @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean(); // ... Map<String, Filter> filters = new LinkedHashMap<>(); filters.put("corsAuthenticationFilter", new CorsAuthenticationFilter()); filterFactoryBean.setFilters(filters); // ... return filterFactoryBean; } ``` 这里将自定义过滤器 CorsAuthenticationFilter 添加到了 Shiro 的过滤器链中。 3. 配置 Spring Boot 的跨域设置 在 Spring Boot 的配置文件中添加 CORS 的配置,跟上面的一样。 这样配置完后,就可以跨域访问 Shiro 中的接口了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值