I have an AJAX call to my endpoint in my Spring controller. After verifying the info from the POST, my controller makes a redirect decision, whether to forward the request to the next location, or send them back to a login page. The response to the post is correct, it's a 302 with the Location header set correctly. However, when the page makes the redirect call, it makes an OPTIONS call to the URL, then a GET call, which just returns the HTML. Great, I have the HTML, but the page stays on my JSP page and never goes to the external URL. How do I manage this?
Sample Java code:
#RequestMapping(value = "/token/{token_code}", method = {RequestMethod.GET})
public void validateToken(HttpServletRequest servletRequest, HttpServletResponse servletResponse, #PathVariable String token_code) {
//set some servlet request attributes from incoming packet info
if(isTokenValid(token_code)) {
servletRequest.getRequestDispatcher(MyConstants.JSP_DEVICE_INFO).forward(servletRequest, servletResponse);
}
else {
servletRequest.getRequestDispatcher(MyConstants.FAILURE_URL).forward(servletRequest, servletResponse);
}
}
#RequestMapping(value = "/token/tokenRedirect", method = {RequestMethod.POST},headers = "content-type=application/json",consumes = {MediaType.APPLICATION_JSON_VALUE})
public ModelAndView getSession(HttpServletRequest servletRequest,
HttpServletResponse servletResponse,
#RequestBody TokenValidateRequest request)
{
boolean isValid = verifyCollectedInfo(request);
if(isValid) {
servletResponse.setHeader("Location", request.url());
servletResponse.setStatus(302);
}
else {
servletResponse.setHeader("Location", MyConstants.FAILURE_URL);
servletResponse.setStatus(302);
}
}
JSP Ajax call:
$.ajax({
headers: {
'accept': 'application/json',
'content-type': 'application/json'
},
type: "POST",
url: "tokenRedirect",
context:document.body,
contentType:"application/json",
data:JSON.stringify(TokenValidateObject)
});
So when I inspect my network traffic, I see the 302 status is set for the response and the Location header has the URL I want, but it just fetches the HTML for the redirect URL, it doesn't actually switch views
You should try using the front end to redirect instead of the back-end. Ajax is meant to make a asynchronous request which then can be consumed on the front end.
So, instead of redirecting in the back end, just tell it print the redirect location and redirect using the returned data on the front end.
$.ajax({
headers: {
'accept': 'application/json',
'content-type': 'application/json'
},
type: "POST",
url: "tokenRedirect",
context:document.body,
contentType:"application/json",
data:JSON.stringify(TokenValidateObject)
success: function(locationToRedirect){
window.location = locationToRedirect
}
});
Because you're using XHR, the client-side code needs to read the HTTP response and handle the redirect using JavaScript. The browser will only execute the redirect for a PAGE, not an XHR call.
See: Redirecting after Ajax post
How to manage a redirect request after a jQuery Ajax call
Redirect on Ajax Jquery Call
Related
There are two pages in my front-end demo, login.html and home.html.
When I click the "login" button in the login.html, a Ajax request which carries the username and password will be sent to the back-end. If I log in successfully, the page will jump to home.html.
<body>
<button id="login-btn">login</button>
<script>
$('#login-btn').click(function() {
$.ajax({
method: 'POST',
url: 'http://localhost:8080/api/login',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify({ 'username': 'hover', 'password': 'hover' }),
success: function(data, status, xhr) {
alert(data.success);
// jump to home
window.location.href = 'home.html';
},
error: function(xhr, error_type, exception) {
console.log(error_type);
}
});
});
</script>
</body>
There is a "request" button in home.html. When I click the "request" button, a Ajax request which carries a "greeting" will be sent to the back-end. If I have logged in, I will receive the echo response, otherwise I will receive a "not yet logged in" message.
To tell the back-end "I have logged in!", I set withCredentials to true to make the request carry the cookie which contains JSESSIONID.
<body>
<button id="request-btn">request</button>
<script>
$('#request-btn').click(function () {
$.ajax({
method: 'POST',
url: 'http://localhost:8080/api/echo',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify({ 'greeting': 'hello' }),
crossDomain: true,
xhrFields: {
withCredentials: true
},
success: function (data, status, xhr) {
console.log(data);
},
error: function (xhr, error_type, exception) {
console.log(error_type);
}
});
});
</script>
</body>
I use SpringMVC to bulid my back-end and I have configured CORS and LoginInterceptor.
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "org.example")
public class AppConfig implements WebMvcConfigurer {
//CORS
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://127.0.0.1:5500")
.allowedMethods("GET", "POST", "OPTIONS")
.allowedHeaders("content-type", "origin")
.allowCredentials(true).maxAge(1800);
}
// Register the LoginInterceptor
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").excludePathPatterns("/login");
}
}
LoginInterceptor will intercept any requests other than Preflight OPTIONS and login requests.
public class LoginInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws IOException {
// Let OPTIONS pass directly
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
return true;
}
// Check whether the client has already logged in.
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("username") != null) {
return true;
}
else {
response.setContentType("application/json; charset=utf-8");
// {"message": "not yet logged in"}
response.getWriter().write("{\"message\": \"not yet logged in\"}");
}
return false;
}
}
In ApiController, the login method prints the username and password, creates a session and stores the username, and returns a "success" message.
The echo method returns the greeting message from home.html.
#RestController
public class ApiController {
#PostMapping(value = "/login", produces = "application/json; charset=utf-8")
public String login(#RequestBody Map<String, String> param, HttpServletRequest request) {
String username = param.get("username");
String password = param.get("password");
System.out.println("username: " + username);
System.out.println("password: " + password);
// create a session and store the username
request.getSession().setAttribute("username", username);
// {"success": true}
return "{\"success\": true}";
}
#PostMapping(value = "/echo", produces = "application/json; charset=utf-8")
public String echo(#RequestBody Map<String, String> param) {
String greeting = param.get("greeting");
System.out.println(greeting);
// {"greeting": "hello"}
return "{\"greeting\": " + "\"" + greeting + "\"}";
}
}
Because a session is created in the login method, the header of the response to the login request will have Set-Cookie field set to JSESSIONID, which is what I want to carry in the home.html's request.
login request images:
preflight OPTIONS
POST
But in reality, when I later jump to home.html and send greeting requests, JSESSIONID is not carried. Thus I receive the "not yet logged in" message. I wonder why this happens?
images:
preflight OPTIONS
POST which does not carry a cookie
If I set that in "login" Ajax request as well, the problem will be solved.
Just like the codes:
<body>
<button id="login-btn">login</button>
<script>
$('#login-btn').click(function() {
$.ajax({
method: 'POST',
url: 'http://localhost:8080/api/login',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify({ 'username': 'hover', 'password': 'hover' }),
xhrFields: {
withCredentials: true
},
success: function(data, status, xhr) {
alert(data.success);
// jump to home
window.location.href = 'home.html';
},
error: function(xhr, error_type, exception) {
console.log(error_type);
}
});
});
</script>
</body>
On the face of it, the problem is solved.
Actually, I only want to make the "greeting" Ajax request carry the cookie, but now I have to set withCredentials to true in "login" Ajax request as well.
So what's going on behind the scenes?
The XMLHttpRequest.withCredentials property is a Boolean that indicates whether or not cross-site Access-Control requests should be made using credentials such as cookies, authorization headers or TLS client certificates. Setting withCredentials has no effect on same-site requests.
In addition, this flag is also used to indicate when cookies are to be ignored in the response. The default is false. XMLHttpRequest from a different domain cannot set cookie values for their own domain unless withCredentials is set to true before making the request. The third-party cookies obtained by setting withCredentials to true will still honor same-origin policy and hence can not be accessed by the requesting script through document.cookie or from response headers.
In addition, this flag is also used to indicate when cookies are to be ignored in the response.
I want to redirect to another .jsp in spring mvc method. I don't want to use javascripts methods like: window.location.replace(url).
My method:
#RequestMapping(value= "loginUser", method=RequestMethod.POST)
public #ResponseBody String loginUser (#RequestParam("name") String name, #RequestParam("password") String password){
return "";
}
You can't do the redirect from Spring when your request expects json response. You can set a parameter for redirect so that when you enter the success block you can check the parameter to redirect using window.location.reload();. For example, from a similar post, check this answer,
Redirect on Ajax Jquery Call
One idea is to let the the browser know that it should redirect by
adding a redirect variable to to the resulting object and checking for
it in JQuery
$(document).ready(function(){
jQuery.ajax({
type: "GET",
url: "populateData.htm",
dataType:"json",
data:"userId=SampleUser",
success:function(response){
if (response.redirect) {
window.location.href = response.redirect;
}
else {
// Process the expected results...
}
},
error: function(xhr, textStatus, errorThrown) {
alert('Error! Status = ' + xhr.status);
}
});
});
You can alternatively add a response header for redirect like response.setHeader("REQUIRES_AUTH", "1") and in jQuery success,
success:function(response){
if (response.getResponseHeader('REQUIRES_AUTH') === '1'){
window.location.href = 'login.htm';
}
else {
// Process the expected results...
}
}
Refer the similar question: Spring Controller redirect to another page
Include an HttpServletResponse parameter and call sendRedirect(String).
Or, don't use #ResponseBody at all, and return the model/view you want to use.
I am using ajax post action. In this action i have used HttpContext.User.Identity.Name to get the user id. Based on the user id i get some records related to that user id in database and return that values through json type.
Sometimes session got expired, in this case the HttpContext.User.Identity.Name value became empty. If it is empty or null it will throw the exception.
So I need to null or empty check the HttpContext.User.Identity.Name value, if it is null or empty i need to redirect it to login page.
But redirecting action not works inside the ajax post action. how to solve this problem?
i need to authorize the ajax post action. Can any one give solution for that?
Regards,
Karthik.
But redirecting action not works inside the ajax post action. how to solve this problem?
You could start by decorating your controller action with the [Authorize] attribute. This ensures that only authenticated users can access it and you are guaranteed that inside User.Identity.Name will never be null:
[Authorize]
public ActionResult SomeAction()
{
string username = User.Identity.Name; // this will never throw
...
return Json(...);
}
then take a look at the following blog post by Phil Haack. Inside its post Phil presents a nice plugin that allows you to configure ASP.NET to send 401 HTTP status code when an AJAX request to a protected action is made. So in your jQuery code you could very easily detect this condition and redirect:
$.ajax({
url: '/SomeAction',
type: 'POST',
statusCode: {
200: function (data) {
alert('200: Authenticated');
// Bind the JSON data to the UI
},
401: function (data) {
// the user is not authenticated => redirect him to the login page
window.location.href = '/login';
}
}
});
And of course to avoid writing this 401 condition in all your AJAX requests you could very easily use the global .ajaxError() handler to centralize this redirection logic for all your AJAX requests in case of 401 status code returned by the server:
$(document).ajaxError(function(e, jqxhr, settings, exception) {
if (jqxhr.status == 401) { // unauthorized
window.location.href = '/login';
}
});
and now your AJAX requests become pretty standard:
$.ajax({
url: '/SomeAction',
type: 'POST',
success: function(data) {
// do something with the data returned by the action
}
});
I create a simple MVC Controller action, that takes some json data - then return true or false.
[AllowCrossSiteJson]
public JsonResult AddPerson(Person person)
{
//do stuff with person object
return Json(true);
}
I call it from javascript:
function saveData(person) {
var json = $.toJSON(person); //converts person object to json
$.ajax({
url: "http://somedomain.com/Ajax/AddPerson",
type: 'POST',
dataType: 'json',
data: json,
contentType: 'application/json; charset=utf-8',
success: function (data) {
alert("ok");
}
});
}
Everything works as long as I am on the same domain, but as soon as I call it from another domain, I run into problems.
On the controller is an action filter "AllowCrossSiteJson" that sets the header "Access-Control-Allow-Origin" to "*", allowing any origin to access the controller action.
public class AllowCrossSiteJsonAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.RequestContext.HttpContext.Response.AddHeader("Access-Control-Allow-Origin", "*");
base.OnActionExecuting(filterContext);
}
}
However - I then get this error in firebug, when calling across domains:
OPTIONS http://somedomain.com/Ajax/AddPerson?packageId=3 500 (Internal Server Error)
XMLHttpRequest cannot load http://somedomain.com/Ajax/AddPerson. Request header field Content-Type is not allowed by Access-Control-Allow-Headers.
What is wrong here?
I have been looking through possible solutions for hours, and it seems to be something to do with jquery using OPTIONS (not POST as I would expect).
If that is indeed the problem, how can I fix that?
To fix the Access-Control-Allow-Origin error, you need to include the following header in your response:
Access-Control-Allow-Headers: Content-Type
Basically, any "non-simple" header needs to be included as a comma-delimited list in the header above. Check out the CORS spec for more details:
http://www.w3.org/TR/cors/
"Content-Type" needs to be included because "application/json" does not match the values defined here:
http://www.w3.org/TR/cors/#terminology
I'd recommend you JSONP, it's the only really cross browser and reliable solution for cross domain AJAX. So you could start by writing a custom action result that will wrap the JSON response with a callback:
public class JsonpResult : ActionResult
{
private readonly object _obj;
public JsonpResult(object obj)
{
_obj = obj;
}
public override void ExecuteResult(ControllerContext context)
{
var serializer = new JavaScriptSerializer();
var callbackname = context.HttpContext.Request["callback"];
var jsonp = string.Format("{0}({1})", callbackname, serializer.Serialize(_obj));
var response = context.HttpContext.Response;
response.ContentType = "application/json";
response.Write(jsonp);
}
}
and then:
public ActionResult AddPerson(Person person)
{
return new JsonpResult(true);
}
and finally perform the cross domain AJAX call:
$.ajax({
url: 'http://somedomain.com/Ajax/AddPerson',
jsonp: 'callback',
dataType: 'jsonp',
data: { firstName: 'john', lastName: 'smith' },
success: function (result) {
alert(result);
}
});
I'm trying to do a simple ajax GET that returns the html from google.com but with the following I keep hitting my onFailure. And when I hit status i get a 0, yet when I attempt to output the responseText I get nothing.
Anyone done a simple request like this in mootools 1.2.1?
function addSomeAction() {
el.onclick = function() {
var uri = "http://www.google.com";
var myRequest = new Request({
url: uri,
method: 'get',
onRequest: function(){
alert("loading...");
},
onSuccess: function(responseText){
alert("hi");
},
onFailure: function(responseFail){
alert("fail: " + responseFail.responseText);
}
});
myRequest.send();
}
}
Regardless of the framework used, you cannot do cross-domain AJAX requests. This is to prevent Cross-Site Request Forgery (CSRF) attacks and is a limitation imposed by the web browser.
Therefore, you can only fetch the HTML source of a page which on the same domain as the originating request.
There are specifications allowing for the asynchronous transfer of JSON data residing on another domain (JSONP), but they will not help you in this case.