I am using Angular 5 as a front end with a Spring Boot REST server. Everything was working properly if not using SSL. When I switch to SSL, eventually I got everything sort of working. It works for GET requests, but so far I cannot get a PUT request to go through.
My guess is that this is some kind of a CORS issue, given that GET is a simple request and PUT is apparently not (CORS reference), but I cannot figure out how to fix the problem.
On my Spring Boot Rest Controllers, I have the annotation @CrossOrigin("*")
, so I don't think that is the problem, but I'm not sure.
The other piece of the puzzle is that authentication is handled through a CAS server. I have added the following configuration to the CAS properties. These were the final piece that allowed GET requests to work, but I'm not sure what to change on them (if anything) to handle PUT requests:
cas.httpWebRequest.cors.enabled=true
cas.httpWebRequest.cors.allowOrigins[0]=*
cas.httpWebRequest.cors.allowMethods[0]=*
cas.httpWebRequest.cors.allowHeaders[0]=*
Here are my request headers and the response:
Request:
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
Response (Status Code - 403 Forbidden):
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
The angular service is running on https://localhost:4200
.
The spring boot service is running on https://localhost:8493
.
The CAS service is running on https://localhost:8443
.
There are no error messages that I have seen in any of the logs. I would like to be able to understand why a PUT request is forbidden, and then how to fix it so that a PUT request will also work. Thanks!
EDIT: Add Spring Boot Security Configuration
<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.url}login/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>
EDIT: Add Angular Proxy Config
{
"/api": {
"target" : "https://localhost:8493",
"changeOrigin": true,
"secure" : false
}
}
In your spring boot security configuration, I see csrf has been enabled. But in your request I don't see any cookie XSRF-TOKEN
and header X-XSRF-TOKEN
. That is why the server is not accepting the request and the responding with 403 response.
Your browser probably is not creating SSL connection. This usually happens when the browser finds an incorrect certificate or a self signed certificate or a certificate signed by a Certification Authority which is not trusted.
If you are using chrome browser you can enable this for localhost for testing purpose by enabling allow-insecure-localhost
property. Enter this URL in the address bar, click on Enable and restart Chrome.
chrome://flags/#allow-insecure-localhost
Refer this link for more details for enabling it in chrome and firefox. https://improveandrepeat.com/2016/09/allowing-self-signed-certificates-on-localhost-with-chrome-and-firefox/
I think you should have an X-XSRF-TOKEN token in your request header since csrf is enabled in your security conf. For me this worked :
constructor(private http: HttpClient, private tokenExtractor: HttpXsrfTokenExtractor) {
}
Then extract a token
const token = this.tokenExtractor.getToken() as string;
and add it to the header
this.http.post<any>(url, body, {headers: new HttpHeaders().set('X-XSRF-TOKEN', token)})
Hope this could help.
CORS is enforced by the browser, but in this case you are getting 403 from the server. As others have mentioned, your request seems to be missing the CSRF header and that's probably why the server is rejecting your request. I can't tell why this started happening when you switched to HTTPS, but I can tell you have <csrf/>
in your configuration but no header to match.
To verify this theory, you can disable CSRF protection by changing:
<csrf/>
to:
<csrf disabled="true"/>
Starting with Spring 4 CSRF protection is enabled by default so simply removing <csrf/>
will not be enough.
Once you've confirmed CSRF was the issue and you no longer get 403, you should turn it back on in a way that Angular supports. It's an important protection that will protect your users. Angular expects a cookie named XSRF-TOKEN
which it will send back with X-XSRF-TOKEN
header. Those are configurable, but should match Spring's default if you're using latest Spring and latest Angular. Follow Spring documentation to add:
<http>
<!-- ... -->
<csrf token-repository-ref="tokenRepository"/>
</http>
<b:bean id="tokenRepository"
class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"
p:cookieHttpOnly="false"/>
HTTP requests that potentially modify something on the server, like PUT, POST, DELETE and PATCH does not know whether they are allowed to execute the request if they have not been told in beforehand.
They can know by reading the headers of an earlier request that have allowed them to modify the requested resource, or they will fire an OPTIONS request just before. This is called a 'preflight' request.
If you have not implemented the OPTIONS method to respond with CORS headers, this is what you need to do.
Have a look here for more details.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With