I am currently try to develop RESTful application using Angular on font-end and Spring MVC on back-end(#RestController). I already have implemented few GET and POST method to fetch or save data, however now I am facing a problem with one POST method:
angular.module("app").factory("BorrowerService", function($http, $log){
var borrowBook = function(borrow){
$log.debug("Borrowing book: " );
$log.debug(borrow);
$http({
method: 'POST',
url: 'Zadanie3/borrow',
data: borrow
}).then(function successCallback(response) {
$log.debug("success");
}, function errorCallback(response) {
$log.error("failed to borrow the book");
$log.error(response);
});
};
return{
borrowBook: borrowBook
};
});
POST result is always errorCallback with output:
failed to borrow the book
Object {data: null, status: 0, config: Object, statusText: ""}
Interesting part is that on most browsers (except Firefox) the book actually do borrow. I have already searched about status:0 response (also on stackoverflow) and unfortunatelly have not found the correct solution to my problem. I would appreciate any help. Here my RestController method:
#RestController
public class BookRestPostController {
#RequestMapping(value = "/borrow", method=RequestMethod.POST)
public BorrowDTO borrow(#RequestBody BorrowDTO borrow ) {
borrowerService.borrowBook(borrow);
return borrow;
}
}
Edit: I forgot to mention that I use #RestController annotation on my controller class. It automatically includes #ResponseBody.
For security reasons some of the browsers don't allow to make ajax request that are not in the same origin. If you want read more on CORS I'm recommending this article. You have several choices to resolve your problem.
The first one is if you want to keep your current projects structure, both projects
to exists on different servers. You should enable CORS (Cross origin resource sharing) support on server side and I see that you are using newer Spring Framework so you can just add one
annotation that will enable CORS support. The annotation is #CrossOrigin see this article.
#RestController
#CrossOrigin(maxAge = 3600)
public class BookRestPostController {
#RequestMapping(value = "/borrow", method=RequestMethod.POST)
public BorrowDTO borrow(#RequestBody BorrowDTO borrow ) {
borrowerService.borrowBook(borrow);
return borrow;
}
}
If you are using older version of Spring Framework you can do in the manual way - add Filter which will add needed response headers. See this article
#Component
public class SimpleCORSFilter implements Filter {
#Override
public void init(FilterConfig arg0) throws ServletException {}
#Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
// TODO Auto-generated method stub
HttpServletResponse response=(HttpServletResponse) resp;
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", "x-requested-with");
chain.doFilter(req, resp);
}
#Override
public void destroy() {}
}
This is probably the cleanest way to achieve your goal.
Other two alternatives are to create server side proxy or to deploy both client and server side code in one server but my suggestion is not use proxy or same server !.
With simple words the browser will preflight request, it will send before every request, one extra request of type OPTIONS with specific headers (Access-Control-Request-Method and Access-Control-Request-Headers). These request headers will ask the server for permissions to make the actual request. Your preflight response needs to acknowledge these headers in order for the actual request to work.
Damn annoying bugOrFeature...
add #ResponseBody annotation
public #ResponseBody BorrowDTO borrow
Related
I'm posting data from react to spring boot backend using axios. Please refer the below react code.
const handleSubmit = (e) =>{
e.preventDefault()
alert( "Username "+ username + " Password "+password);
axios.post('http://localhost:8080/sign-up',{
username:username,
password:password})
};
Below is my spring code.
#PostMapping("/sign-up")
public String signUp(#RequestBody User user) {
if(userRepository.findByUsername(user.getUsername())==null) {
Set<Role> roles= new HashSet<>();
Role role= roleRepository.findByName("USER");
roles.add(role);
user.setRoles(roles);
user.setPassword(bcryptEncoder.encode(user.getPassword()));
userRepository.save(user);
return "Success";
}
else{
return "Username Already Exsist";
}
}
When I post data from front end i'm getting below response in the browser console.
Access to XMLHttpRequest at 'http://localhost:8080/sign-up' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
xhr.js:178 POST http://localhost:8080/sign-up net::ERR_FAILED
createError.js:16 Uncaught (in promise) Error: Network Error
at createError (createError.js:16)
at XMLHttpRequest.handleError (xhr.js:83)
I've inserted following line in the controller of the spring boot.
#CrossOrigin(origins = "http://localhost:3000")
But the above solution didn't work. I suspect during the post call option call is happening to check the resource and in that case only this cors issue is occuring. Appreciate any input to this issue.
I had a similar issue recently.
I got a first step further by removing spring-boot-starter-security from my POM temporarily, because it was blocking the call giving unauthorized. So I figured out that that was my problem. I had to configure CORS for Spring security and then I got it to work. Apologies for the vague answer, but I hope it can help you look in another direction that could help you.
I have found the solution for the above problem. This cors issue is coming when there is a communication happening between different domains. So I have added below line in the configure() method of SecurityConfig class.
http.cors();
Then I have implemented the following CustomCorsFilter class.
#Component
public class CustomCorsFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, PATCH, PUT, DELETE, OPTIONS");
httpServletResponse.setHeader("Access-Control-Max-Age", "3600");
httpServletResponse.setHeader("Access-Control-Allow-Headers", "Origin, Content-Type, Allow, authorization, content-type, xsrf-token");
httpServletResponse.addHeader("Access-Control-Expose-Headers", "xsrf-token");
if ("OPTIONS".equals(httpServletRequest.getMethod())) {
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
} else {
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
}
Now there is no cors issue.
I tried to add CSRF/XSRF protection to my application, but ran into strange behavior. All get requests work fine, but on all post/put/delete I'm getting 403 Unauthorized. And the strangest thing is that when I tried to debug my CSRF filter, requests do not reach it, they are rejected somewhere earlier. They do not even reach my authentication filter, so I can not figure out what the problem may be.
My security config:
#Override
public void configure(HttpSecurity http) throws Exception {
http
...
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService()), UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class)
.csrf().csrfTokenRepository(csrfTokenRepository());
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
I do not add the filters since as I said, the requests do not reach them. But if needed I will complete my question. I hope for your help, thank you in advance!
In principle, the CSRF mechanism in Spring stores the CSRF token in a HTTP only cookie. Because JavaScript cannot access a HTTP only cookie, you need to tell spring to disable HTTP only:
.and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
Then you can read the cookie from Angular and add it to the XSRF-TOKEN header with each request.
This is the general case. I am not sure if this fits your special case.
Assuming that the rest of your config/filters working properly, you're facing this issue because of this: SessionCreationPolicy.STATELESS.
You can have a look under the hood of Spring CsrfFilter. You'll see that it needs to remember the value of each CSRF-token for each user inside a session, and since you are not using sessions it can't be done.
What to do next - is really up to you. Some people saying that if you app is stateless there is actually no need for CSRF protection. Spring docs saying that CSRF attacks are still relevant. I think it really depends on your authentication mechanism.
You might also want to look at this nice article, for example.
Hope it helps.
Many thanks for the answers, they really helped me to find a solution. And I want to share my solution if in the future someone will face the same issue.
As noted in the answers I used SessionCreationPolicy.STATELESS and did not have sessions so instead of HttpSessionCsrfTokenRepository I had to use CookieCsrfTokenRepository with withHttpOnlyFalse() to allow AngularJS to read cookies.
As a result, I have a configuration like this:
#Override
public void configure(HttpSecurity http) throws Exception {
http
...
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService()), UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class)
.csrf().csrfTokenRepository(csrfTokenRepository());
}
If someone is interested in how the CsrfHeaderFilter looks:
public class CsrfHeaderFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie==null || token!=null && !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
}
My second problem was CORS. AngularJS documentation says:
"The header will not be set for cross-domain requests."
To solve this problem, I had to use an HTTP Interceptor:
.factory('XsrfInterceptor', function ($cookies) {
return {
request: function (config) {
var headerName = 'X-XSRF-TOKEN';
var cookieName = 'XSRF-TOKEN';
config.headers[headerName] = $cookies.get(cookieName);
return config;
}
};
});
.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('XsrfInterceptor');
}]);
I hope my answer will be useful.
I'm developing an Angular application with a Spring based REST backend. Now I have the following issue.
I can login and get the Token from my angular application without an issue. But subsequent requests fail with 401 error. When I try the same rest endpoint in Postman with the token obtained from angular login component, it works fine.
I'm using HttpSessionStrategy, not OAuth, JWT.
I suspect some issue in my spring security config though I cannot figure it out. Could you please help me resolve this issue.
Following is the relevant part of my Spring security config.
#Bean
public SessionRepository<ExpiringSession> sessionRepository() {
return new MapSessionRepository();
}
#Bean
public HttpSessionStrategy httpSessionStrategy() {
HeaderHttpSessionStrategy strategy = new HeaderHttpSessionStrategy();
strategy.setHeaderName(headerName);
return strategy;
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD"));
configuration.setExposedHeaders(Arrays.asList("x-auth-token"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().disable();
http.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.antMatchers("/**").hasAnyAuthority("ADMIN", "OPERATOR")
.anyRequest().authenticated()
.and().exceptionHandling().authenticationEntryPoint(new RESTAuthenticationEntryPoint())
.and().cors().configurationSource(corsConfigurationSource())
.and().csrf().disable()
.formLogin()
.successHandler(new RESTAuthenticationSuccessHandler(objectMapper, userDetailService))
.failureHandler(new RESTAuthenticationFailureHandler())
.and()
.logout().logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler());
}
And, following is the failed request and response headers of my Angular application.
Instead of using the wildcard value (*) for Access-Control-Allow-Headers, enumerate the allowed headers:
configuration.setAllowedHeaders(Arrays.asList("x-auth-token"));
From the Docs:
Access-Control-Allow-Headers
Compatibility notes
The wildcard value (*) that is mentioned in the latest specification, is not yet implemented in browsers:
Chromium: Issue 615313
Firefox: bug 1309358
Servo: Issue 13283
— MDN HTTP Reference (Access-Control-Allow-Headers)
I'm trying to submit a login form to a rest Api, translating my Jquery/Javascript code into AngularJS. I try to use $http service for send the request, but when I submit the form, the POST request turns into OPTIONS and no request params are passed to. This is my code:
controller.formData = {
username : $scope.formData.username,
password : $scope.formData.password,
};
$http({
method : 'POST',
url : 'http://localhost:8080/multe-web/signin',
data : controller.formData,
headers : { 'Content-Type': 'application/json' }
})
.success(function(data) {
console.log(data);
});
This is a screenshoot of browser's console
Why no parameters are passed to the HTTP POST request?
Can someone help me?
If you are trying to do a Cross Origin Request, this could be a 'preflight request':
See this post: How to disable OPTIONS request?
This request is called "preflight request". This appends when you try to access a resource that is from another domain. For more information you can search for Cross-origin resource sharing (CORS). During the preflight request, you should see headers starting with Access-Control-Request-* These request headers are asking the server for permissions to make the actual request. Your preflight response needs to acknowledge these headers in order for the actual request to work.
The OPTIONS that you see is a pre-flighted request that is initiated by the browser. This happens due to CORS that stands for "CROSS ORIGIN RESOURCE SHARING`.
Pre-flighted requests
In your case, your backend server needs to acknowledge the OPTIONS request and send a 200 back. Only then the browser fires the real POST request
Here are some links to get you started
Same Origin policy
AngularJS and CORS
Similar question on SO
You must define a CORS filter on your backend.
I don't know which language you are using but an example in spring framework (java) would be the following.
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
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, DELETE, PUT");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with, authorization, cache-control, content-type, Origin, key");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
#Override
public void init(FilterConfig filterConfig) {
}
#Override
public void destroy() {
}
}
Basically you are stating that response.setHeader("Access-Control-Allow-Origin", "*"); can come from any domain. ( in a production environment you would limit this offcourse to your known domains ).
When implementing this filter your options request will pass through without problems.
The options call is something the browser does automatically and you really don't want to disable this as some other answers are suggesting.
Kind regards
I am trying to make cross-domain requests with Angularjs 1.4.5.
But can't get success. I have configured $httpprovider
.config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];
$httpProvider.defaults.headers.common['Accept']= "application/json, text/plain, */*";
$httpProvider.defaults.headers.put["Content-Type"] = "application/x-www-form-urlencoded;charset=utf-8";
$httpProvider.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded;charset=utf-8";
$httpProvider.interceptors.push('authenticationFailedInterceptor');
}])
But still con't get success. How to enable CORS support with Vert.x 2.x http server.
CORS is supported in Vert.x 3.x but Right now I can't upgrade Vert.x.
have you tried with something like this on your responses?
vertx.createHttpServer()
.requestHandler(function (req) {
req.response()
.putHeader("content-type", "text/plain")
.putHeader("Access-Control-Allow-Origin", "*")
.putHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
.putHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
.end("Hello from Vert.x!");
}).listen(8080);
for sure, you have to modify this to your needs... but the changes has to be done in the server and at least, you need these three headers.
Complete example to enable cors:
We need to create two route matcher.
One helps to enable cors and other handle the requests.
Below is to enable cors. It Accept all request and add all the required headers needs to enable cors. After that we need to hand over request to the actual route matcher to handle request. We have that by the name secureRoutes.
RouteMatcher routeMatcher = new RouteMatcher();
routeMatcher.options(".*",new Handler<HttpServerRequest>() {
#Override
public void handle(final HttpServerRequest request) {
request.response().putHeader("Access-Control-Allow-Origin", "*");
request.response().putHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
request.response().putHeader("Access-Control-Allow-Headers", "accept, authorization, content-type, email");
request.response().end();
}
})
.all(".*",new Handler<HttpServerRequest>() {
#Override
public void handle(final HttpServerRequest request) {
request.response().putHeader("Access-Control-Allow-Origin", "*");
secureRoutes.getRouteMatcher().handle(request);
}
});
Another route matcher:
public class SecureRoutes {
private static final RouteMatcher routeMatcher = new RouteMatcher();
#Inject
protected Container container;
#Inject
private SigninController signinController;
#Inject
private SignupController signupController;
#Inject
private OauthController oauthController;
#Inject
ClientNetworkSignalController clientNetworkSignalController;
public void initRoutes() {
// APP routes. they use User token for authentication
routeMatcher.get("/", new Handler<HttpServerRequest>() {
#Override
public void handle(final HttpServerRequest request) {
request.response().putHeader("Cache-Control",
"public, max-age=86400");
request.response().sendFile("web/public/index.html");
}
});
routeMatcher.post("/signin", signinController.signin());
routeMatcher.post("/signup", signupController.signup());
routeMatcher.post("/oauth2/token", oauthController.token());
routeMatcher.post("/oauth2/invalidate_token", oauthController.invalidateToken());
}
public RouteMatcher getRouteMatcher() {
return routeMatcher;
}
}
Now finally add requestHandler to server:
server.requestHandler(routeMatcher).listen(port,
host, new Handler<AsyncResult<HttpServer>>() {
public void handle(AsyncResult<HttpServer> asyncResult) {
if (asyncResult.succeeded()) {
logger.info(s + ": Started on " + host + ":"
+ port);
} else {
logger.info(s + ": Unable to start server.\n "
+ asyncResult.cause());
}
}
});
You may have a question What is the use of http options type request handler. The answer is for that is very interesting. Javascript is a secured language that do not allow Cross origin Http request. So, to allow cross origin request javascript send a options type request for each http request request and check weather CORS is supported or not. In such Javascript hits server two times one to check cors is supported or not and one to fatch data.