SpringBoot+Shiro放行OPTIONS请求,解决跨域问题

本文介绍了在集成Shiro后遇到的一个问题,即配置了放行的接口能正常访问,但需要登录验证的接口在OPTIONS请求时被拦截,导致POST请求出现跨域问题。解决方法是创建自定义的ShiroUserFilter,重写preHandle方法,检查请求类型为OPTIONS时直接放行,并设置相应响应头以实现跨域。在Shiro配置类中添加自定义Filter并配置过滤链,确保OPTIONS请求被正确处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题:

集成shiro之后发现配置放行的接口可以正常访问,而需要登录验证的接口会报错

其中OPTIONS类型的接口会报302

导致后续的post请求报错提示跨域问题


Shiro登录流程

首先Shiro是根据请求中cookie携带的JSESSIONID判断是否登录的
当调用登录接口登录成功时,后端的响应头会添加一个set-cookie的参数
JSESSIONID代表当前登录的用户,前端只要在请求中携带这个参数Shiro就可以识别出用户并放行。
但是post请求会先发送一个OPTIONS类型的探测请求,由于这个请求没有携带JSESSIONID,Shiro就会判断为未登录,进行拦截。
所以解决的思路就是放行OPTIONS类型的请求。

解决

  1. 创建ShiroUserFilter
package com.school.service.config;

import com.school.service.entity.Result;
import lombok.extern.slf4j.Slf4j;
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;
import java.io.IOException;
import java.io.PrintWriter;

@Slf4j
public class ShiroUserFilter extends UserFilter {
    /**
     * 在访问过来的时候检测是否为OPTIONS请求,如果是就直接返回true
     */
    @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())) {
            log.info("OPTIONS放行");
            setHeader(httpRequest,httpResponse);
            return true;
        }
        return super.preHandle(request,response);
    }

    /**
     * 该方法会在验证失败后调用,这里由于是前后端分离,后台不控制页面跳转
     * 因此重写改成传输JSON数据
     */
    @Override
    protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
        saveRequest(request);
        setHeader((HttpServletRequest) request,(HttpServletResponse) response);
        PrintWriter out = response.getWriter();
        //自己控制返回的json数据
        out.println(new Result("500",null,"Shiro验证失败"));
        out.flush();
        out.close();
    }

    /**
     * 为response设置header,实现跨域
     */
    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());
    }
}
  1. 在Shiro配置类的shiroFilter中添加进去
package com.school.service.config;

import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Shiro配置类
 */
@Configuration
public class ShiroConfig {

    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        shiroFilterFactoryBean.setLoginUrl("/school/goToLogin");//设置登录页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/school/goToLogin");//权限不足跳转页面,这个在Default过滤器中设置无效,具体看 https://blog.youkuaiyun.com/bicheng4769/article/details/86680955

        //添加自定义Filter,放行OPTIONS请求-----------------------------------------这里✨✨✨✨✨✨✨✨
        Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();
        filters.put("authc", new ShiroUserFilter());
        shiroFilterFactoryBean.setFilters(filters);
        //----------------------------------------------------------------------------------------------✨

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
        filterChainDefinitionMap.put("/service/school/**", "anon");
        filterChainDefinitionMap.put("/swagger-ui.html", "anon");
        filterChainDefinitionMap.put("/swagger-resources", "anon");
        filterChainDefinitionMap.put("/v2/api-docs", "anon");
        filterChainDefinitionMap.put("/webjars/springfox-swagger-ui/**", "anon");
        filterChainDefinitionMap.put("/configuration/security", "anon");
        filterChainDefinitionMap.put("/configuration/ui", "anon");

        filterChainDefinitionMap.put("/service/article/**", "authc");
        filterChainDefinitionMap.put("/service/chat/**", "authc");
        filterChainDefinitionMap.put("/service/diary/**", "authc");
        filterChainDefinitionMap.put("/service/file/**", "authc");
        filterChainDefinitionMap.put("/service/problem/**", "authc");
        filterChainDefinitionMap.put("/service/team-article/**", "authc");
        filterChainDefinitionMap.put("/service/team/**", "authc");
        filterChainDefinitionMap.put("/service/user/**", "authc");
        filterChainDefinitionMap.put("/service/user-friend/**", "authc");
        filterChainDefinitionMap.put("/service/user-info/**", "authc");
        filterChainDefinitionMap.put("/service/widget/**", "authc");

        //主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截 剩余的都需要认证
        filterChainDefinitionMap.put("/**", "anon");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        defaultWebSecurityManager.setRealm(myRealm());
        return defaultWebSecurityManager;
    }

    @Bean
    public MyRealm myRealm (){
        MyRealm myRealm = new MyRealm();
        return myRealm;
    }


}

一切正常

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值