无法使用 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)
注释。
感谢您的建议。不幸的是,这似乎没有帮助。
是的,因为您使用的是不同的配置。我的另一个建议是更改安全过滤器链中的位置:<custom-filter ref="corsHandler" before="HEADERS_FILTER"/>
【参考方案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 构建的静态内容