AngularJS $Http CORS with backend in Spring Rest & Security - spring

I have a problem with AngularJS. When I call a Rest Services from another domain, the authorization Header is not sending on the Request, so the Spring Security is not recognizing the authentication credentials. Attached the configuration files.
web.xml
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
<filter>
<filter-name>cors</filter-name>
<filter-class>com.axcessfinancial.web.filter.CorsFilter</filter-class>
<filter-mapping><filter-name>cors</filter-name><url-pattern>/*</url-pattern></filter-mapping>
Context-Security.xml
<http use-expressions="true">
<intercept-url pattern="/**" access="isAuthenticated()" />
<http-basic/>
</http>
<authentication-manager>
<authentication-provider>
<user-service>
<user name="admin" password="admin" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
CorsFilter
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
response.addHeader("Access-Control-Allow-Origin", "*");
if (request.getHeader("Access-Control-Request-Method") != null && "OPTIONS".equals(request.getMethod())) {
response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
response.addHeader("Access-Control-Allow-Headers", "Authorization, Accept, Content-Type, X-PINGOTHER");
response.addHeader("Access-Control-Max-Age", "1728000");
}
filterChain.doFilter(request, response);
}
app.js
var app = angular.module('app', ['app.controller','app.services']);
app.config(function($httpProvider) {
$httpProvider.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];
/* $httpProvider.defaults.headers.common['Authorization'] = 'Basic YWRtaW46YWRtaW4='; */
});
service.js
angular.module('app.services',[]).service('Service', function ($http,$q,UtilHttp) {
$http.defaults.headers.common = {"Access-Control-Request-Headers": "accept, origin, authorization"};
$http.defaults.headers.common['Authorization'] = 'Basic YWRtaW46YWRtaW4=';
return {
listCutomer: function(){
var defer=$q.defer();
$http.post('http://localhost:8088/rest-template/soa/listCustomer',{withCredentials: true})
.success(function(data){
defer.resolve(data);
})
.error(function(data){
defer.reject(data);
});
return defer.promise;
}
};
});
Problem:
Response Headersview source
Content-Length 1134
Content-Type text/html;charset=utf-8
Date Wed, 21 May 2014 14:39:44 GMT
Server Apache-Coyote/1.1
Set-Cookie JSESSIONID=5CD90453C2CD57CE111F45B0FBCB0301; Path=/rest-template
WWW-Authenticate Basic realm="Spring Security Application"
Request Headers
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding gzip, deflate
Accept-Language en-US,en;q=0.5
Access-Control-Request-He... authorization,content-type
Access-Control-Request-Me... POST
Cache-Control no-cache
Connection keep-alive
Host localhost:8088
Origin null
Pragma no-cache
User-Agent Mozilla/5.0 (Windows NT 6.1; WOW64; rv:24.0) Gecko/20100101 Firefox/24.0

I think your problem is the following:
when
use HTTP verb other than GET or POST
need Custom headers to be sent (e.g. Authentication, X-API-Key etc)
need The request body has a MIME type other than text/plain
your browser (following CORS specs) adds an extra step to the request:
it first sends a particular request with method "OPTIONS" to the URL, if the server respond approving the actual request you want your real request will start.
Unfortunately in your scenario spring returns 401 (unauthorized) to the OPTIONS request because auth token is not present in this request, consequently your real request will never start
Solution:
you can put your cors filter before the spring security filter in your web.xml and avoid calling next filter in the chain (spring security) if request method is OPTIONS
this exaple filter works for me:
public class SimpleCORSFilter implements Filter {
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-Credentials", "true");
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
public void init(FilterConfig filterConfig) {
}
public void destroy() {
}
}
remember to declare your cors filter before the spring security filter in your web.xml

Add your filter to the front of the Spring Security filter chain...
<http>
<custom-filter before="CHANNEL_FILTER" ref="myFilter" />
</http>
<beans:bean id="myFilter" class="com.mycompany.MySpecialAuthenticationFilter"/>
see http://docs.spring.io/spring-security/site/docs/3.0.x/reference/ns-config.html

I had the same problem when I used Spring Security using web.xml to configure the project and I did what has been said . The if condiction in the filter bellow solved my problem. My project using Web.xml with REST and Spring Security is hosted in
https://github.com/leandrocprates/projetoSpring-Rest-JDBC-Security-Configuracao-WebXML.
The database is in :
projetoSpring-Rest-JDBC-Security-Configuracao-WebXML/src/main/resources/script_tabelas/
And the file angularjs.html is used to execute the project . It passes the authorization and return a json. Click on the button "busca empresa".
projetoSpring-Rest-JDBC-Security-Configuracao-WebXML/src/main/webapp/angularjs.html
This project is in Portuguese guys but serve as a model just to see how to configure the project .
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}

Related

Ionic / Angular App http requests fail to connect to Spring REST API using an Android emulator / real Android device when targeting the host ip

I am unable to perform any http requests from my Ionic App to my Spring API when I am targeting the actual host adress (192.168...). Using the localhost in my Chrome Browser works fine, aswell as using the Android emulator gateway (10.0.2.2) in my virtual device. However, as soon as i use my host ip, only the Chrome Browser is able to send requests. I also made sure that my real device is in the same network as my host and it actually is able to reach the Spring API through its Browser. Unfortunately, if I then try to send a http request in the App, I get a really vacuous error message:
{
"headers":{
"normalizedNames":{},
"lazyUpdate":null,
"headers":{}
},
"status":0,
"statusText":"Unknown Error",
"url":"http://hostip:8080/authentication",
"ok":false,
"name":"HttpErrorResponse",
"message":"Http failure response for http://hostip:8080/authentication: 0 Unknown Error",
"error":{
"isTrusted":true
}
}
This is what my login method looks like:
login(email: string, password: string) {
const httpOptions = {
headers: new HttpHeaders({
'Authorization': 'Basic ' + base64string
}),
observe: 'response' as 'body',
withCredentials: true
};
return this.http.get(
'http://hostip:8080/authentication', httpOptions
);
}
At any other point in my App I am just calling the http.get method with the url and withCredentials: true as an argument.
I also made sure that these lines are included in my config.xml:
<access origin="*" />
<allow-intent href="http://*/*" />
<allow-intent href="https://*/*" />
I also added a CORS Filter to my Spring API since I am sending an authorization header and want to set
Access-Control-Allow-Origin: * but combining these two things is forbidden by CORS-policy which is why my filter takes the request´s origin and returns it in the Access-Control-Allow-Origin-Header. Therefore every origin is allowed without using the wildcard *.
#Component
public class SimpleCORSFilter implements Filter {
public SimpleCORSFilter() {}
#Override
public void init(FilterConfig filterConfig) {}
#Override
public void destroy() {}
#Order(Ordered.HIGHEST_PRECEDENCE)
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
System.out.println("Origin: " + request.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
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", "X-Requested-With, Authorization, Origin, Content-Type, Version");
response.setHeader("Access-Control-Expose-Headers", "X-Requested-With, Authorization, Origin, Content-Type");
chain.doFilter(req, res);
}
}
My Spring Security Config is as simple as this:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.antMatchers(HttpMethod.GET).permitAll()
.antMatchers("/logout").permitAll()
.anyRequest().hasAnyRole("USER")
.and()
.httpBasic()
.and()
.logout();
}
}
I really appreciate all the help i can get and I am looking forward for your answers.
Android changed its http architecture from version 9.
to make it working on Android go to your project root folder.
yourAppFolder > resources > android > xml >
network_security_config.xml
Change your network security config to blow code.
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
</network-security-config>

Spring Rest Controller - Can I allow allow CORS in the interceptor?

I have an interceptor:
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RequestInterceptor());
}
Can I add my CORS configuration in the RequestInterceptor.java above? Like this:
public class RequestInterceptor extends HandlerInterceptorAdapter
{
...
public boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
...
...
if (request.getMethod().equals("OPTIONS")) {
response.setHeader("Access-Control-Allow-Origin", "http://localhost:9090");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
response.setHeader("Access-Control-Expose-Headers", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Max-Age", "4800");
}
...
return true;
}
}
I do not want any of these methods because for some reason they are not working for me:
#CrossOrigin
OR
WebMvcConfigurerAdapter -> addCorsMapping()
The only thing works for me is having a CORSFilter and mapping it in the web.xml. But that got me thinking that Interceptors are also kind of Filter, aren't they? Then why to add a Filter AND an interceptor when interceptor itself will do.
Anyway, I tried with the above piece of code and my requests are landing in the server and the rest controller method is running, but I see this response on my ReactJs page:
SEC7120: Origin http://localhost:9090 not found in Access-Control-Allow-Origin header.
SCRIPT7002: XMLHttpRequest: Network Error 0x80070005, Access is denied.
What am I missing? Is there anything else I have to do in the RequestInterceptor.java class for CORS?
Thanks.

How to forbid `DELETE` http request in Springboot?

The security department ask us to forbid DELETE and some other http request method if we don't need to use it in our applications. In SpringMVC I can add the security-constraint in web.xml like this:
<security-constraint>
<display-name>delete-method</display-name>
<web-resource-collection>
<web-resource-name>unsafe-method</web-resource-name>
<url-pattern>/*</url-pattern>
<http-method>DELETE</http-method>
</web-resource-collection>
<auth-constraint/>
But I don't know how to add in Springboot. The server is tomcat8.x and run at CentOS.
You can use CORS filter for it. You cas specify allowed HTTP request types there.
Example from the Spring docs:
<mvc:cors>
<mvc:mapping path="/api/**"
allowed-origins="http://domain1.com, http://domain2.com"
allowed-methods="GET, PUT"
allowed-headers="header1, header2, header3"
exposed-headers="header1, header2" allow-credentials="false"
max-age="123" />
<mvc:mapping path="/resources/**"
allowed-origins="http://domain1.com" />
</mvc:cors>
OR
You can do it with Java.
Here's the nice implementation
#Component
public class CorsFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, OPTIONS");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "authorization, content-type, xsrf-token");
response.addHeader("Access-Control-Expose-Headers", "xsrf-token");
if ("OPTIONS".equals(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
filterChain.doFilter(request, response);
}
}
}

Spring 4.2.2 CORS Support not working

#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
}
I'm following the guide in https://spring.io/blog/2015/06/08/cors-support-in-spring-framework and added the above code to my WebConfig
However, CORS is not working, log show
20151103 183823 [http-bio-8080-exec-3] WARN org.springframework.web.servlet.DispatcherServlet (DispatcherServlet.java:1136) - No mapping found for HTTP request with URI [/my/url] in DispatcherServlet with name 'dispatcher'
I can successfully call my api without CORS(same domain), so it is not the api problem.
In Chrome console, log show
XMLHttpRequest cannot load http://localhost:8080/my/url.
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'http://localhost:8100' is therefore not allowed access. The response had HTTP status code 404.
Just add this filter for CROS in package which is contain other configuation
#Component
public class RequestFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
chain.doFilter(req, res);
}
public void init(FilterConfig filterConfig) {
}
public void destroy() {
}
}
It will work fine for you.

SimpleCORSFilter not working

I am using Spring Rest as my backend, when I sent request by $.ajax{}, I got error message:
XMLHttpRequest cannot load http://121.40.249.129:8080/user/login. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:8000' is therefore not allowed access.
So, I added SimpleCORSFilter in my Spring Project:
SimpleCORSFilter:
#Component
public class SimpleCORSFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Headers", "X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version");
chain.doFilter(req, res);
}
#Override
public void init(FilterConfig filterConfig) {}
#Override
public void destroy() {}
}
The since I don't have web.xml, so I didn't add the code to web.xml:
<filter>
<filter-name>simpleCORSFilter</filter-name>
<filter-class>xxx.xxx.SimpleCORSFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>simpleCORSFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
So, I still get the error, how can I fix it.
As I have experienced, * won't work for Access-Control-Allow-Origin when Access-Control-Allow-Credentials is set to true.
So, you should instead have
response.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:8000");
Actually, instead of hardcoding the url, you can have that as a property. If you want to allow multiple origins, this answer would help.
Also:
It's advised to annotate the class with #Order(Ordered.HIGHEST_PRECEDENCE), because this filter should come first.
If you are using CSRF, the corrosponding header should also be added to the list in Access-Control-Allow-Headers.
Update:
As #RTzhong did (see his comments below), replacing * with request.getHeader("Origin") seems like the ideal fix. However, a better security practice in general would be to specify the actual url or first checking with a whitelist, unless one must expose his API publicly to unknown websites.
Refer to Spring Lemon's source code for a concrete example.

Resources