与Spring Security的跨域资源共享

2022-09-01 10:23:26

我试图让CORS与Spring Security很好地配合,但它不符合要求。我进行了本文中描述的更改,并在中更改了此行,使POST和GET请求适用于我的应用程序(暂时公开控制器方法,因此我可以测试CORS):applicationContext-security.xml

  • 以前:<intercept-url pattern="/**" access="isAuthenticated()" />
  • 后:<intercept-url pattern="/**" access="permitAll" />

不幸的是,以下允许Spring Security通过AJAX登录的URL没有响应:.我正在从 向 发出 AJAX 请求。http://localhost:8080/mutopia-server/resources/j_spring_security_checkhttp://localhost:80http://localhost:8080

在铬

尝试访问时,我会在Chrome中为OPTIONS预检请求,AJAX调用返回HTTP状态代码0和消息“错误”。j_spring_security_check(pending)

在火狐中

预检成功,HTTP 状态代码为 302,之后我仍然直接收到 AJAX 请求的错误回调,HTTP 状态为 0,消息为“error”。

enter image description here

enter image description here

AJAX 请求代码

function get(url, json) {
    var args = {
        type: 'GET',
        url: url,
        // async: false,
        // crossDomain: true,
        xhrFields: {
            withCredentials: false
        },
        success: function(response) {
            console.debug(url, response);
        },
        error: function(xhr) {
            console.error(url, xhr.status, xhr.statusText);
        }
    };
    if (json) {
        args.contentType = 'application/json'
    }
    $.ajax(args);
}

function post(url, json, data, dataEncode) {
    var args = {
        type: 'POST',
        url: url,
        // async: false,
        crossDomain: true,
        xhrFields: {
            withCredentials: false
        },
        beforeSend: function(xhr){
            // This is always added by default
            // Ignoring this prevents preflight - but expects browser to follow 302 location change
            xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
            xhr.setRequestHeader("X-Ajax-call", "true");
        },
        success: function(data, textStatus, xhr) {
            // var location = xhr.getResponseHeader('Location');
            console.error('success', url, xhr.getAllResponseHeaders());
        },
        error: function(xhr) {
            console.error(url, xhr.status, xhr.statusText);
            console.error('fail', url, xhr.getAllResponseHeaders());
        }
    }
    if (json) {
        args.contentType = 'application/json'
    }
    if (typeof data != 'undefined') {
        // Send JSON raw in the body
        args.data = dataEncode ? JSON.stringify(data) : data;
    }
    console.debug('args', args);
    $.ajax(args);
}

var loginJSON = {"j_username": "username", "j_password": "password"};

// Fails
post('http://localhost:8080/mutopia-server/resources/j_spring_security_check', false, loginJSON, false);

// Works
post('http://localhost/mutopia-server/resources/j_spring_security_check', false, loginJSON, false);

// Works
get('http://localhost:8080/mutopia-server/landuses?projectId=6', true);

// Works
post('http://localhost:8080/mutopia-server/params', true, {
    "name": "testing",
    "local": false,
    "generated": false,
    "project": 6
}, true);

请注意 - 我可以通过CORS发布到应用程序中的任何其他URL,除了Spring Security登录。我已经看过很多文章,所以任何对这个奇怪问题的见解将不胜感激。


答案 1

我能够通过扩展UsernamePasswordAuthenticationFilter来做到这一点...我的代码在Groovy中,希望没关系:

public class CorsAwareAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    static final String ORIGIN = 'Origin'

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response){
        if (request.getHeader(ORIGIN)) {
            String origin = request.getHeader(ORIGIN)
            response.addHeader('Access-Control-Allow-Origin', origin)
            response.addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
            response.addHeader('Access-Control-Allow-Credentials', 'true')
            response.addHeader('Access-Control-Allow-Headers',
                    request.getHeader('Access-Control-Request-Headers'))
        }
        if (request.method == 'OPTIONS') {
            response.writer.print('OK')
            response.writer.flush()
            return
        }
        return super.attemptAuthentication(request, response)
    }
}

上面的重要部分:

  • 仅当检测到 CORS 请求时才将 CORS 标头添加到响应中
  • 使用简单的非空 200 响应(其中还包含 CORS 标头)响应预检 OPTIONS 请求。

您需要在 Spring 配置中声明此 Bean。有很多文章展示了如何做到这一点,所以我不会在这里复制。

在我自己的实现中,我使用源域白名单,因为我允许CORS仅供内部开发人员访问。以上是我正在做的事情的简化版本,因此可能需要调整,但这应该给你一个大思路。


答案 2

好吧,这是我的代码工作得很好,对我来说很完美:我花了两天时间研究它并了解弹簧安全性,所以我希望你接受它作为答案,哈哈

 public class CorsFilter extends OncePerRequestFilter  {
    static final String ORIGIN = "Origin";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        System.out.println(request.getHeader(ORIGIN));
        System.out.println(request.getMethod());
        if (request.getHeader(ORIGIN).equals("null")) {
            String origin = request.getHeader(ORIGIN);
            response.setHeader("Access-Control-Allow-Origin", "*");//* or origin as u prefer
            response.setHeader("Access-Control-Allow-Credentials", "true");
           response.setHeader("Access-Control-Allow-Headers",
                    request.getHeader("Access-Control-Request-Headers"));
        }
        if (request.getMethod().equals("OPTIONS")) {
            try {
                response.getWriter().print("OK");
                response.getWriter().flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }else{
        filterChain.doFilter(request, response);
        }
    }
}

那么,您还需要设置要调用的过滤器:

<security:http use-expressions="true" .... >
     ...
     //your other configs
    <security:custom-filter ref="corsHandler" after="PRE_AUTH_FILTER"/> // this goes to your filter
</security:http>

好吧,您需要为您创建的自定义过滤器创建一个bean:

<bean id="corsHandler" class="mobilebackbone.mesoft.config.CorsFilter" />