Angular PUT 请求通过 SSL 被禁止

Posted

技术标签:

【中文标题】Angular PUT 请求通过 SSL 被禁止【英文标题】:Angular PUT request is Forbidden over SSL 【发布时间】:2018-06-13 09:38:45 【问题描述】:

我使用 Angular 5 作为 Spring Boot REST 服务器的前端。如果不使用 SSL,一切正常。当我切换到 SSL 时,最终我得到了一切工作。它适用于 GET 请求,但到目前为止我无法通过 PUT 请求。

我的猜测是这是某种 CORS 问题,因为 GET 是一个简单的请求,而 PUT 显然不是 (CORS reference),但我不知道如何解决这个问题。

在我的 Spring Boot Rest Controller 上,我有注释 @CrossOrigin("*"),所以我认为这不是问题,但我不确定。

另一个难题是身份验证是通过 CAS 服务器处理的。我已将以下配置添加到 CAS 属性中。这些是允许 GET 请求工作的最后一块,但我不确定要对它们进行哪些更改(如果有的话)以处理 PUT 请求:

cas.httpWebRequest.cors.enabled=true
cas.httpWebRequest.cors.allowOrigins[0]=*
cas.httpWebRequest.cors.allowMethods[0]=*
cas.httpWebRequest.cors.allowHeaders[0]=*

这是我的请求标头和响应:

请求:

Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.5
Connection: keep-alive
Content-Length: 1975
Content-Type: application/json
Cookie: JSESSIONID=117D9345E985D824E46…BF32; io=gLhCcBoZrfNcppioAAAB
Host: localhost:4200
Referer: https://localhost:4200/sales/proposals/dashboard
User-Agent: Mozilla/5.0 (Windows NT 10.0; …) Gecko/20100101 Firefox/57.0

响应(状态代码 - 403 禁止):

access-control-allow-origin: *
cache-control: no-cache, no-store, max-age=0, must-revalidate
content-length: 56
content-type: application/json;charset=UTF-8
date: Wed, 03 Jan 2018 16:39:14 GMT
expires: 0
pragma: no-cache
strict-transport-security: max-age=31536000 ; includeSubDomains
x-content-type-options: nosniff
X-Firefox-Spdy: h2
x-frame-options: DENY
x-powered-by: Express
x-xss-protection: 1; mode=block

角度服务正在https://localhost:4200 上运行。

spring boot 服务正在https://localhost:8493上运行。

CAS 服务正在https://localhost:8443 上运行。

我在任何日志中都没有看到错误消息。我希望能够理解为什么禁止 PUT 请求,然后如何修复它以便 PUT 请求也可以工作。谢谢!

编辑:添加 Spring Boot 安全配置

<http pattern="/**" entry-point-ref="casEntryPoint">
            <intercept-url pattern="/api/holidays" access="permitAll"/>
            <intercept-url pattern="/api/unit**" access="permitAll"/>
            <intercept-url pattern="/**" access="isAuthenticated()" />

            <custom-filter ref="casAuthenticationFilter" before="CAS_FILTER"/>

            <csrf/>         
    </http>

    <global-method-security pre-post-annotations="enabled"/>

    <!-- CAS Config -->
    <beans:bean id="casEntryPoint" class="org.springframework.security.cas.web.CasAuthenticationEntryPoint">
            <beans:property name="loginUrl" value="$cas.server.host.login_url"/>
            <beans:property name="serviceProperties" ref="serviceProperties"/>
    </beans:bean>

    <beans:bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties">
            <beans:property name="service" value="$app.server.host.urllogin/cas"></beans:property>
    </beans:bean>

    <beans:bean id="casAuthenticationFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter">
            <beans:property name="authenticationManager" ref="authenticationManager"/>
    </beans:bean>

    <beans:bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
            <beans:property name="ticketValidator" ref="ticketValidator"></beans:property>
            <beans:property name="serviceProperties" ref="serviceProperties"></beans:property>
            <beans:property name="key" value="Key"></beans:property>
            <beans:property name="authenticationUserDetailsService" ref="userDetailsWrapper"/>
    </beans:bean>

    <beans:bean id="userDetailsWrapper" class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
        <beans:property name="userDetailsService" ref="userDetails"></beans:property>
    </beans:bean>

    <ldap-user-service id="userDetails"
        server-ref="ldapServer"
        group-search-base="ou=ERPGroups,OU=MyBusiness"
        group-search-filter="(member=0)"
        user-search-base="ou=SBSUsers,OU=Users,OU=MyBusiness"
        user-search-filter="(sAMAccountName=0)" />

    <ldap-server id="ldapServer" url="$ldap.urls/$ldap.base" manager-dn="$ldap.username" manager-password="$ldap.password" />

    <beans:bean id="ticketValidator" class="org.jasig.cas.client.validation.Cas30ServiceTicketValidator">
            <beans:constructor-arg value="$cas.server.host.url"></beans:constructor-arg>
    </beans:bean>

    <authentication-manager alias="authenticationManager">  
        <authentication-provider ref="casAuthenticationProvider" />
    </authentication-manager>

编辑:添加 Angular 代理配置


    "/api": 
        "target" : "https://localhost:8493",
        "changeOrigin": true,
        "secure" : false
    

【问题讨论】:

403 表示禁止,例如用户未经过身份验证/允许访问方法服务器端。我对CAS不太了解,但是PUT请求中发送的是CAS cookie吗? (我看到一个会话 cookie 和 'io' cookie)。如果这是一个 cors 问题,它将是客户端,如果您启动浏览器的开发工具/调试器,您应该会看到一些东西 能否提供您在spring boot server应用上使用的安全配置? 我刚刚发现 x-powered-by: Express x-xss-protection: 1; mode=block 你在使用 NodeJS Express 代理吗? 如果有CORS问题,参考这个链接***.com/questions/46788969/… 【参考方案1】:

在您的 Spring Boot 安全配置中,我看到 csrf 已启用。但是在您的请求中,我没有看到任何 cookie XSRF-TOKEN 和标头 X-XSRF-TOKEN。这就是为什么服务器不接受请求并且响应 403 响应。

您的浏览器可能没有创建 SSL 连接。当浏览器发现不正确的证书或自签名证书或由不受信任的证书颁发机构签名的证书时,通常会发生这种情况。

如果您使用的是 chrome 浏览器,您可以通过启用 allow-insecure-localhost 属性为 localhost 启用此功能以进行测试。在地址栏中输入此 URL,点击启用并重新启动 Chrome。

chrome://flags/#allow-insecure-localhost

有关在 chrome 和 firefox 中启用它的更多详细信息,请参阅此链接。 https://improveandrepeat.com/2016/09/allowing-self-signed-certificates-on-localhost-with-chrome-and-firefox/

【讨论】:

感谢您的想法。我尝试从 Spring Boot 配置中删除 csrf,并尝试从 Chrome 中启用不安全的 localhost(之前一直在使用 Firefox),但无论哪种方式,我都得到了相同的响应。 “从 Spring Security 4.0 开始,CSRF 保护默认通过 XML 配置启用”,因此仅删除它是不够的。您应该使用 &lt;csrf disabled="true"/&gt; 禁用它以使其正常工作。但是一旦你证明它是 CSRF,你应该重新打开它并修复你的 Angular 代码以正确处理 CSRF,这样你的网站就不会容易受到攻击。【参考方案2】:

我认为您的请求标头中应该有一个 X-XSRF-TOKEN 令牌,因为您的安全配置中启用了 csrf。 对我来说这很有效:

 constructor(private http: HttpClient, private tokenExtractor: HttpXsrfTokenExtractor) 
    

然后提取一个令牌

const token = this.tokenExtractor.getToken() as string;

并将其添加到标题

this.http.post<any>(url, body, headers: new HttpHeaders().set('X-XSRF-TOKEN', token))

希望这会有所帮助。

【讨论】:

【参考方案3】:

CORS 由浏览器强制执行,但在这种情况下,您会从服务器获得 403。正如其他人所提到的,您的请求似乎缺少 CSRF 标头,这可能是服务器拒绝您的请求的原因。我不知道为什么当您切换到 HTTPS 时开始发生这种情况,但我可以告诉您,您的配置中有 &lt;csrf/&gt;,但没有匹配的标头。

要验证这个理论,您可以通过更改禁用CSRF 保护:

<csrf/>

到:

<csrf disabled="true"/>

从 Spring 4 开始,CSRF 保护默认启用,因此仅删除 &lt;csrf/&gt; 是不够的。

一旦您确认 CSRF 是问题所在并且您不再收到 403,您应该以 Angular supports 的方式重新打开它。这是一项重要的保护措施,可以保护您的用户。 Angular 需要一个名为 XSRF-TOKEN 的 cookie,它将与 X-XSRF-TOKEN 标头一起发回。这些是可配置的,但如果您使用最新的 Spring 和最新的 Angular,则应该与 Spring 的默认值匹配。关注Spring documentation添加:

<http>
    <!-- ... -->
    <csrf token-repository-ref="tokenRepository"/>
</http>
<b:bean id="tokenRepository"
    class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"
    p:cookieHttpOnly="false"/>

【讨论】:

【参考方案4】:

可能修改服务器上某些内容的 HTTP 请求,如 PUT、POST、DELETE 和 PATCH,如果事先没有被告知,则不知道是否允许它们执行请求。

他们可以通过读取允许他们修改所请求资源的较早请求的标头来知道,或者他们将在之前触发一个 OPTIONS 请求。这称为“预检”请求。

如果您还没有实现 OPTIONS 方法来响应 CORS 标头,这就是您需要做的。

查看here了解更多详情。

【讨论】:

感谢您提供的信息资源。我读过的很多东西听起来像是浏览器应该自动发送飞行前请求。我在浏览器网络调试工具中没有看到任何类型的飞行前请求。我应该手动创建它,还是如何强制浏览器发送飞行前请求? 它是自动发送的,但您可能需要在服务器端处理它。我上面关于 express 的问题是因为如果你使用代理,你可能根本没有做 CORS 我在开发过程中使用了代理。在 package.json 中,我有 start: ng serve --proxy-config proxy.conf.json ...,在那个 conf 文件中我有 ...target: https://localhost:8493 ...。所以使用了不同的端口,我认为这可以作为不同的来源。 不,它不是浏览器查看的来源,它通过您的 Angular 开发服务器进行通信,该服务器转发请求并在返回之前添加自己的标头。 那么,如果 CORS 不是问题,为什么 GET 请求会起作用,但 PUT 请求会得到 Forbidden 响应?【参考方案5】:

我遇到了类似的问题,我的前端在 Angular4 中,后端在 Spring 中,我使用的身份验证是基于 JWT 的身份验证。我的 POST 请求出现此错误,但我想它也适用于 PUT,因为两者都属于“不那么简单的请求”(飞行前请求的别名)。

我的 UI 工作了一段时间,然后我收到以下错误:

无法加载https://localhost:8080/myApp/services/stationCollection/viewCollected。请求的资源上不存在“Access-Control-Allow-Origin”标头。

经过分析,我知道飞行前请求已通过,并且完全成功,这是拒绝访问服务器的主要请求(您可以在 chrome 开发工具的“网络”选项卡和获取详细信息)。发送的 CORS 请求的类型是“不那么简单的请求”。事实上,如果您使用 Rest Client/ curl 将相同的请求发送到同一台服务器,则一切正常。

当你在网络上浏览这个错误时,你会发现丢失了解决方案,所有这些都导致在服务器端配置 CORS,但事实上我已经实现了 CORS。

可能的解决方案:

所有这一切都让我找到了相同的解决方案,经过大量搜索,我发现这个问题有一个可能的破解/替代方案,正如以下博客中最好的定义:

https://laracasts.com/discuss/channels/laravel/cros-access-control-allow-origin-not-in-the-headers

博客基本上要求您创建一个中间件,在从 UI 传递到前端的所有请求之间,如果您知道您的应用程序是真实的,请添加

myOptions.headers.append('Access-Control-Allow-Origin', '*');

在中间件。希望对您有所帮助。

【讨论】:

谢谢,拉胡尔。当我观看网络工具时,没有发送预检请求。显然,当使用代理时,它会以某种方式得到不同的处理。但是没有预检请求,我认为这个选项不会起作用。【参考方案6】:

尝试在服务器端添加此标头Access-Control-Allow-Credentials: true

more info

如果这对您没有帮助并且其他发布请求按预期工作,恐怕您的代码的某些部分是错误的。

【讨论】:

我尝试将它添加到 Spring Boot 中的 Rest Controller 中,例如 HttpHeaders headers = new HttpHeaders(); headers.add("Access-Control-Allow-Credentials", "true"); return new ResponseEntity&lt;&gt;(service.update(input), headers, HttpStatus.OK);,但似乎没有任何区别。如果唯一的变化是它是否是 SSL,那么对我来说代码可能是错误的没有意义。另外,为什么回复是Forbidden【参考方案7】:

你为什么不在你的 Spring Boot 应用上试试这个?

HttpHeaders headers = new HttpHeaders();

headers.add("Access-Control-Allow-Origin", "*");
headers.add("Access-Control-Allow-Credentials", "true");
headers.add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS, HEAD");
headers.add("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With");

return new ResponseEntity<>(service.update(input), headers, HttpStatus.OK);

更多阅读可以在这里找到https://spring.io/blog/2015/06/08/cors-support-in-spring-framework

【讨论】:

【参考方案8】:

更改 Spring 配置以允许 CORS 可能不是一个好主意。我所做的是安装一个 nginx 服务器并将其用作反向代理。下面是我的示例 nginx.conf:

# Spring backend
location /api

  proxy_set_header Host $host;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_pass http://localhost:8493;


# Angular front end
location /

  root www/apps;
  try_files $uri $uri/ /index.html;

【讨论】:

以上是关于Angular PUT 请求通过 SSL 被禁止的主要内容,如果未能解决你的问题,请参考以下文章

为啥在我的 Angular 2 应用程序中发送 OPTIONS 请求而不是 PUT? [复制]

尝试使用 PUT 将 PDF 作为 blob 上传到 S3 存储桶时被禁止 403

通过 SSL 调用 Web Api 时出现 403 禁止错误

Angular 附加 id 到 put/delete 请求

带有正文示例的 Angular Put 请求

Laravel 5.6 DELETE 和 PUT 403 访问被禁止