无法使用 Angular 和 Spring Security 设置身份验证标头

Posted

技术标签:

【中文标题】无法使用 Angular 和 Spring Security 设置身份验证标头【英文标题】:Can't set Authentication Header with Angular and Spring Security 【发布时间】:2015-09-05 07:41:15 【问题描述】:

我无法让 Angular、CORS、SpringSecurity 和基本身份验证正常运行。我有以下 Angular Ajax 调用,我试图将标头设置为在请求中包含基本授权标头。

var headerObj = headers: 'Authorization': 'Basic am9lOnNlY3JldA==';
return $http.get('http://localhost:8080/mysite/login', headerObj).then(function (response) 
    return response.data;
);

我在 localhost:8888 上运行我的 Angular 应用程序,在 localhost:8080 上运行我的后端服务器。所以我不得不在我的服务器端添加一个 CORS 过滤器,即 Spring MVC。所以我创建了一个过滤器并将其添加到我的 web.xml 中。

web.xml

<filter>
    <filter-name>corsFilter</filter-name>
    <filter-class>com.abcinsights.controller.SimpleCorsFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>corsFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

CorsFilter

public class SimpleCorsFilter extends OncePerRequestFilter 

@Override
public void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException 
    Enumeration<String> headerNames = req.getHeaderNames();
    while(headerNames.hasMoreElements())
        String headerName = headerNames.nextElement();
        System.out.println("headerName " + headerName);
        System.out.println("headerVal " + req.getHeader(headerName));
                   
    HttpServletResponse response = (HttpServletResponse) res;
    response.setHeader("Access-Control-Allow-Origin", "*"); 
    response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
    response.setHeader("Access-Control-Max-Age", "3600");
    response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Range, Content-Disposition, Content-Type, Authorization");
    chain.doFilter(req, res);
   

这一切都很好,当我查看标头名称/值时,我看到我的授权标头带有基本值和硬编码的 Base64 字符串。

然后我尝试添加 SpringSecurity 它将使用 Authorization 标头进行基本身份验证。这是我添加了 Spring Security 的 web.xml 和 spring security 配置。

更新的 web.xml

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/config/security-config.xml</param-value>
</context-param>

<filter>
  <filter-name>springSecurityFilterChain</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
  <filter-name>springSecurityFilterChain</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

安全配置.xml

<http auto-config="true" use-expressions="false">
    <intercept-url pattern="/**" access="ROLE_USER" />
    <http-basic/>
    <custom-filter ref="corsHandler" before="SECURITY_CONTEXT_FILTER"/>
</http>

<beans:bean id="corsHandler" class="com.abcinsights.controller.SimpleCorsFilter"/>

<authentication-manager>
    <authentication-provider>
        <jdbc-user-service data-source-ref="dataSource"/>
    </authentication-provider>
</authentication-manager>

如您所见,我必须将我的 CORS 过滤器添加到 spring 安全过滤器链的前面(我还从 web.xml 中删除了它)。这似乎在我发出请求时仍会调用 CORS 过滤器的意义上起作用。但是来自我的 AJAX 调用的请求不再添加 Authorization 标头。当我删除 SpringSecurity 过滤器链并将我的 CORS 过滤器放回 web.xml 时,Authorization 标头又回来了。我不知所措,但想知道当我的 CORS 过滤器在 web.xml 中独立时,它是否与预检通信工作有关,而当它在 Spring Security 过滤器链中时,它是否与另一种方式有关?任何帮助将不胜感激。

谢谢

【问题讨论】:

尝试在您的过滤器中添加@Order(Ordered.HIGHEST_PRECEDENCE) 注释。 感谢您的建议。不幸的是,这似乎没有帮助。 是的,因为您使用的是不同的配置。我的另一个建议是更改安全过滤器链中的位置:&lt;custom-filter ref="corsHandler" before="HEADERS_FILTER"/&gt; 【参考方案1】:

问题在于 Angular 发送的 HTTP OPTIONS 请求没有 HTTP 授权标头。 Spring security 然后处理该请求并发送回 401 Unauthorized 响应。因此,我的 Angular 应用程序在 OPTIONS 请求上收到了 spring 的 401 响应,而不是 200 响应告诉 angular 继续发送包含 Authorization 标头的 POST 请求。所以解决方法是

a) 将我的 CORSFilter 和 SpringSecurity 过滤器放在 web.xml 中(而不是在 Spring 中调用我的 CORS 过滤器)。不是 100% 确定这会产生影响,但这就是我最终的 web.xml 的样子。

b) 将以下链接中的代码添加到我的 CORS 过滤器中,这样它就不会委托 SpringSecurity 处理 OPTIONS 请求类型。

这篇文章让我明白了:Standalone Spring OAuth2 JWT Authorization Server + CORS

这是我的代码:

web.xml

<display-name>ABC Insights</display-name>
<servlet>
    <servlet-name>abcInsightsServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/config/servlet-config.xml</param-value>
    </init-param> 
</servlet>
<servlet-mapping>
    <servlet-name>abcInsightsServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

<!--  Stand Alone Cross Origin Resource Sharing Authorization Configuration -->
<filter>
    <filter-name>corsFilter</filter-name>
    <filter-class>com.abcinsights.controller.SimpleCorsFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>corsFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>       
<!--  Spring Security Configuration -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/config/security-config.xml</param-value>
</context-param>    
<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

这是我的 CORS 过滤器:

public class SimpleCorsFilter extends GenericFilterBean 
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException 
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;       
        response.setHeader("Access-Control-Allow-Origin", "*"); 
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Range, Content-Disposition, Content-Type, Authorization, X-CSRF-TOKEN");  

        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) 
            response.setStatus(HttpServletResponse.SC_OK);
         else 
            chain.doFilter(req, res);
                   
    

这是我的 Angular 代码:

var login = function (credentials) 
    console.log("credentials.email: " + credentials.email);
    console.log("credentials.password: " + credentials.password);

    var base64Credentials = Base64.encode(credentials.email + ':' + credentials.password);

    var req = 
        method: 'GET',
        url: 'http://localhost:8080/abcinsights/loginPage',
        headers: 
            'Authorization': 'Basic ' + base64Credentials
        
    

    return $http(req).then(function (response) 
        return response.data;
    );        
;

希望对您有所帮助。

【讨论】:

【参考方案2】:

聚会有点晚了,但我遇到了类似的问题,并认为我的解决方案可能会对某人有所帮助。 Joe Rice 的解决方案确实帮助了我,但我不得不做一些修改。我还要提一下,我不是 Spring 或 Tomcat 方面的专家,所以这可能不是一个完美的解决方案。

我有一个 React 客户端(在 localhost:3000 上运行)、Tomcat 服务器(localhost:8080)和 Spring Security 的设置。我必须让 Tomcat 处理 CORS 标头,因为我在应用程序服务器上运行了两个 .war 文件:我的应用程序和 Geowebcache。让 Spring Security 处理标头对我来说是不够的,因为这意味着当客户端直接与 Geowebcache 通信时不会添加标头。

我还使用了一个用 Spring Security 实现的登录页面。使用 Tomcat 'CorsFilter' 对于所有常规请求都可以正常工作,但由于某种原因,CORS 标头未添加到来自客户端的登录请求中,导致响应因缺少 'Access-Control 而丢失而被浏览器阻止-Allow-Origin' 标题。我对此的解决方案是手动将标头添加到客户端的登录调用中,并让 Tomcat 配置处理其余部分。这是我的设置。

Spring Security - web.xml:

<filter>
    <filter-name>corsFilter</filter-name>
    <filter-class>path.to.cors.filter.security.SimpleCorsFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>corsFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Spring 安全配置:

 http.csrf().disable()
            .formLogin().loginPage("/login").permitAll()
            .loginProcessingUrl("/perform_login")
            .defaultSuccessUrl("/index.html", true)
            .failureUrl("/login?error=true");

SimpleCorsFilter.java:

public class SimpleCorsFilter extends GenericFilterBean 
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException 
    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) res;
    if (request.getRequestURI().endsWith("perform_login")) 
        response.setHeader("Access-Control-Allow-Origin", "http://localhost:3000");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Range, Content-Disposition, Content-Type, Authorization, X-CSRF-TOKEN");
    

    chain.doFilter(req, res);

Tomcat - web.xml

<filter>
  <filter-name>CorsFilter</filter-name>
  <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
  <init-param>
    <param-name>cors.allowed.origins</param-name>
    <param-value>http://localhost:3000</param-value>
  </init-param>
  <init-param>
    <param-name>cors.support.credentials</param-name>
    <param-value>true</param-value>
  </init-param>
  <init-param>
    <param-name>cors.exposed.headers</param-name>
    <param-value>Content-Disposition,Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value>
  </init-param>
</filter>
<filter-mapping>
  <filter-name>CorsFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

我正在使用 Axios 发出来自客户端的请求,我必须添加“withCredentials: true”以使服务器接受请求。这是我的 Axios 配置:

axios.create(
  baseURL: process.env.NODE_ENV === 'development' ? 'http://localhost:8080/api/' : '/api/',
  timeout: 300000,
  withCredentials: true
);

希望这可以帮助别人!

【讨论】:

以上是关于无法使用 Angular 和 Spring Security 设置身份验证标头的主要内容,如果未能解决你的问题,请参考以下文章

无法使用 Spring Boot 和 Angular 修复 CORS 问题 [重复]

Angular 6 和 Spring boot - 无法使用简单的 HttpClient 调用获取数据

在 Angular 5 应用程序中响应 Spring Boot 时无法读取授权标头

Spring Boot 2 和 Security With JWT 无法提供 Angular 构建的静态内容

我无法向自己的 Spring 引导服务器发出 Angular Http 请求

无法在 Java Spring Boot / Angular 应用程序中获取 cookie