I am trying the following ajax call to login to spring
$.ajax({
url: "http://localhost:8080/context/j_spring_security_check",
data: { j_username: 'user_name' , j_password: 'user_pass' },
type: "POST",
beforeSend: function (xhr) {
xhr.setRequestHeader("X-Ajax-call", "true");
},
success: function(result) {
// ..
},
error: function(XMLHttpRequest, textStatus, errorThrown){
$("#ajax_login_error_" ).html("Bad user/password") ;
return false;
}
});
This is doesn't work, because the username and password are not passed to the UsernamePasswordAuthenticationFilter. When I debug the request they are both null. In other cases when I login, I can see my user name and password in this filter.
give exception for path (j_spring_security_check) in security filter as
final String contextName = ((HttpServletRequest) request).getContextPath();
final String requestURI = ((HttpServletRequest) request).getRequestURI();
if (requestURI.equals(contextName+"/j_spring_security_check"){
chain.doFilter(request, response);
}
That means you should give exception for login url
You need to override SimpleUrlAuthenticationSuccessHandler. See similar questions here
Related
i am trying to send json from ajax to springboot controller but getting error [org.springframework.web.bind.MissingServletRequestParameterException: Required Item[] parameter 'myJsonString[]' is not present]
below is my ajax code
$.ajax({
type: "POST",
url: "/DBA_TOOLS/admin/tracker",
data: {myJsonString:myJsonString},
dataType: "json",
success: function (data){
if(data.status == "SUCCESS"){
alert("AJAX request successfully completed:"+JSON.stringify(data));
}else{
console.log('Error Msg'+JSON.stringify(data));
}
console.log('Error Msg'+data.message+''+JSON.stringify(data));
},
error: function(jqXHR, textStatus, errorThrown) {
console.log(jqXHR.status);
console.log(textStatus);
console.log(errorThrown);
},
complete: function() {
}
});
below is my controller
#PostMapping(value="/tracker")
public int traker(#RequestParam(value="myJsonString[]") Item[] myJsonString){
System.out.println("getting somethig from tracker");
int count=0;
try{
for(Item item : myJsonString){
System.out.println("User is updating item id "+item.getName());
//count+= ggProcessRepository.changeAlert(row,isActive);
}
}catch(Exception e){
System.out.println("Exception Occured"+e);
}
return count;
}
i tried with requestparam required false and its let the block working but how to get the ajax data to my requestparam is the challenge for me.
in browser debug i see the form data getting send is --> myJsonString: [{"name":"product 2","price":20,"quantity":1},{"name":"product 3","price":30,"quantity":1}]
What happens if you change it to:
traker(#RequestParam(value="myJsonString")
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.
Background
My Angular application communicates with a database through a set of API endpoints.
"user/authenticate" endpoints accepts username and password and returns a success (200) or a bad request (400) http response.
Controller
authService.login(email, password)
.success(function (response) {
/* go to application */
})
.error(function (response) {
/* display proper error message */
});
auth Service
factory.login = function(email, password) {
return $http({
method: 'POST',
url: "http://myserver/user/authenticate",
data: $.param({email: email, password: password}),
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
});
}
Problem
When username and password fails and API returns 400 response, even though I'm catching the error and I'm displaying a proper message to user, the error appears in browser console.
POST http://myserver/user/authenticate 400 (Bad Request)
Can I handle errors in a better way?
Try this
factory.login = function(email, password) {
return $http({
method: 'POST',
url: "http://myserver/user/authenticate",
data: $.param({email: email, password: password}),
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
}).then(function (response) {
/**Check here your status code 400 if you found that status code is 400 the reject the promise**/
if (statuscode !==400) {
return response.data;
} else {
// invalid response
return $q.reject(response.data);
}
}, function (response) {
// something went wrong
return $q.reject(response);
});
}
and then use following code
authService.login(email, password)
.then(function (response) {
/* go to application */
},function(error) {
console.log(error); /* catch 400 Error here */
});
I'm writing a simple web app that allows users to login via facebook. When using the javascript sdk, I'm able to retrieve the proper credentials, but my ajax post is unable to find the mvc action to process the information.
Here's the js code:
FB.getLoginStatus(function (response) {
if (response.status === 'connected') {
var credentials = { uid: response.authResponse.userID, accessToken: response.authResponse.accessToken };
SubmitLogin(credentials);
}
});
function SubmitLogin(credentials) {
alert("Creds: " + credentials.uid + ":::" + credentials.accessToken);
$.ajax({
type: "POST",
ContentType: 'application/json',
url: '#Url.Action("FacebookLogin","Home")',
data:JSON.stringify(credentials),
success: function () {
window.location("~/Views/Account/Home.cshtml");
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert(XMLHttpRequest.responseText);
}
});
}
and the corresponding controller:
[HttpPost]
public JsonResult FacebookLogin(string uid, string accessToken)
{
Session["uid"] = uid;
Session["accessToken"] = accessToken;
return null;
}
The model used in the controller:
public class FBLoginModel
{
public string uid { get; set; }
public string accessToken { get; set; }
}
The alert in the js function shows the correct token, but my ajax post is unable to the action. When I remove the "[HttpPost]" above the controller, I can access the action, but all data I attempt to pass is null.
Any help would be greatly appreciated. Thanks.
use
$.ajax({
type: "POST",
ContentType: 'application/json',
url: '#Url.Action("FacebookLogin","Home")',
data:JSON.stringify(credentials),
success: function () {
window.location("~/Views/Account/Home.cshtml");
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert(XMLHttpRequest.responseText);
}
});
Ther should be single quote in 'Url.Action()'
Yout Controller action should be like below.Because you are not passing model items
public JsonResult FacebookLogin(long uid,string accessToken)
{
Session["uid"] = uid;
Session["accessToken"] = accessToken;
return null; //Because you are not posting anything back
}
I somehow devised a solution... By writing out the url and adding a forward slash to my ajax call, somehow my ajax call can find my action.
$.ajax({
type: 'POST',
url: 'Home/FacebookLogin/',
data: {
'uid': response.authResponse.userID,
'accessToken': response.authResponse.accessToken
},
cache: false,
success: function (result) { },
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert(XMLHttpRequest.responseText);
}
});
I've been working through a simple API example, a modified version of the ServiceStack Hello World example with authentication. The goal of the proof of concept is to create an a RESTful API that contains services requiring authentication accessible entirely through Ajax from several different web projects.
I've read the wiki for, and implemented, Authentication and authorization and implementing CORS (many, results [sorry, not enough cred to point to the relevant link]). At this point, my Hello service can authenticate using a custom authentication mechanism which is over-riding CredentialsAuthProvider and a custom user session object. I've created, or borrowed, rather, a simple test application (an entirely separate project to simulate our needs) and can authenticate and then call into the Hello service, passing a name, and receive a 'Hello Fred' response through a single browser session. That is, I can call the /auth/credentials path in the url, passing the username and id, and receive a proper response. I can then update the url to /hello/fred and receive a valid response.
My breakdown in understanding is how to implement the authentication for all ajax calls. My initial login, below, works fine. No matter what I do, my attempt to call the authenticated service via ajax, I either receive a OPTIONS 404 error or Not Found error, or Origin http // localhost:12345 (pseudo-link) is not allowed by Access-Control-Allow-Origin, etc.
Do I need to go this route?
Sorry if this is confusing. I can provide greater details if required, but think this might be sufficient help the knowledgeable to help my lack of understanding.
function InvokeLogin() {
var Basic = new Object();
Basic.UserName = "MyUser";
Basic.password = "MyPass";
$.ajax({
type: "POST",
contentType: "application/json; charset=utf-8",
dataType: "json",
data: JSON.stringify(Basic),
url: "http://localhost:58795/auth/credentials",
success: function (data, textStatus, jqXHR) {
alert('Authenticated! Now you can run Hello Service.');
},
error: function(xhr, textStatus, errorThrown) {
var data = $.parseJSON(xhr.responseText);
if (data === null)
alert(textStatus + " HttpCode:" + xhr.status);
else
alert("ERROR: " + data.ResponseStatus.Message + (data.ResponseStatus.StackTrace ? " \r\n Stack:" + data.ResponseStatus.StackTrace : ""));
}
});
}
EDIT:
Based on the responses and the link provided by Stefan, I've made a couple of changes:
My Config (Note: I'm using custom authentication and session object and that is all working correctly.)
public override void Configure(Funq.Container container)
{
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new CustomCredentialsAuthProvider(),
}));
base.SetConfig(new EndpointHostConfig
{
GlobalResponseHeaders = {
{ "Access-Control-Allow-Origin", "*" },
{ "Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS" },
{ "Access-Control-Allow-Headers", "Content-Type, Authorization" },
},
DefaultContentType = "application/json"
});
Plugins.Add(new CorsFeature());
this.RequestFilters.Add((httpReq, httpRes, requestDto) =>
{
//Handles Request and closes Responses after emitting global HTTP Headers
if (httpReq.HttpMethod == "OPTIONS")
httpRes.EndRequest(); // extension method
});
Routes
.Add<Hello>("/Hello", "GET, OPTIONS");
container.Register<ICacheClient>(new MemoryCacheClient());
var userRep = new InMemoryAuthRepository();
container.Register<IUserAuthRepository>(userRep);
}
My Simple Hello Service
[EnableCors]
public class HelloService : IService
{
[Authenticate]
public object GET(Hello request)
{
Looks strange when the name is null so we replace with a generic name.
var name = request.Name ?? "John Doe";
return new HelloResponse { Result = "Hello, " + name };
}
}
After making the login call, above, my subsequent call the Hello service is now yielding a 401 error, which is progress, though not where I need to be. (The Jquery.support.cors= true is set in my script file.)
function helloService() {
$.ajax({
type: "GET",
contentType: "application/json",
dataType: "json",
url: "http://localhost:58795/hello",
success: function (data, textStatus, jqXHR) {
alert(data.Result);
},
error: function (xhr, textStatus, errorThrown) {
var data = $.parseJSON(xhr.responseText);
if (data === null)
alert(textStatus + " HttpCode:" + xhr.status);
else
alert("ERROR: " + data.ResponseStatus.Message +
(data.ResponseStatus.StackTrace ? " \r\n Stack:" + data.ResponseStatus.StackTrace : ""));
}
});
}
Again, this works in the RESTConsole if I first make the call to /auth/credentials properly and then follow that up with a call to /hello.
FINAL EDIT
Following Stefan's advise, below, including many other links, I was finally able to get this working. In addition to Stefan's code, I had to make one additional modification:
Plugins.Add(new CorsFeature(allowedHeaders: "Content-Type, Authorization"));
On to the next challenge: Updating Jonas Eriksson's CustomAuthenticateAttibute code (which appears to be using an older version of ServiceStack as a couple of functions are no longer available.
THANKS AGAIN STEFAN!!
this code works for me, based on the Wiki documentation Custom authentication and authorization
Code is based also in the blog post from Community Resources
CORS BasicAuth on ServiceStack with custom authentication
For Basic Authentication, a custom provider
public class myAuthProvider : BasicAuthProvider
{
public myAuthProvider() : base() { }
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
//Add here your custom auth logic (database calls etc)
//Return true if credentials are valid, otherwise false
if (userName == "admin" && password == "test")
return true;
else
return false;
}
public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary<string, string> authInfo)
{
//Fill the IAuthSession with data which you want to retrieve in the app
// the base AuthUserSession properties e.g
session.FirstName = "It's me";
//...
// derived CustomUserSession properties e.g
if(session is CustomUserSession)
((CustomUserSession) session).MyData = "It's me";
//...
//Important: You need to save the session!
authService.SaveSession(session, SessionExpiry);
}
}
public class CustomUserSession : AuthUserSession
{
public string MyData { get; set; }
}
In AppHost
using System.Web;
using ServiceStack; // v.3.9.60 httpExtensions methods, before in ServiceStack.WebHost.Endpoints.Extensions;
using ....
AppHost.Configure
public override void Configure(Container container)
{
SetConfig(new ServiceStack.WebHost.Endpoints.EndpointHostConfig
{
DefaultContentType = ContentType.Json
..
// remove GlobalResponseHeaders because CordFeature adds the CORS headers to Config.GlobalResponseHeaders
});
Plugins.Add(new CorsFeature(allowedHeaders: "Content-Type, Authorization")); //Registers global CORS Headers
this.RequestFilters.Add((httpReq, httpRes, requestDto) =>
{
if (httpReq.HttpMethod == "OPTIONS")
httpRes.EndRequestWithNoContent(); // v 3.9.60 httpExtensions method before httpRes.EndServiceStackRequest();
});
//Register all Authentication methods you want to enable for this web app.
Plugins.Add(new AuthFeature(() => new CustomUserSession(), // OR the AuthUserSession
new IAuthProvider[] {
new myAuthProvider(),
}) { HtmlRedirect = null }); // Redirect on fail
HtmlRedirect answer
Routes.Add<TestRequest>("/TestAPI/{Id}", "POST,GET, OPTIONS");
....
}
In Service
[Authenticate]
public class TestAPI : Service
{
...
}
in javascript
jQuery.support.cors = true;
function make_base_auth(user, password) {
var tok = user + ':' + password;
var hash = btoa(tok);
return "Basic " + hash;
}
Login first
function Authenticate() {
$.ajax({
type: 'Post',
contentType: 'application/json',
url: serverIP + 'Auth',
cache: false,
async: false,
data: {},
dataType: "json",
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization", make_base_auth(username, password));
},
success: function (response, status, xhr) {
localStorage.sessionId = data.SessionId;
var UserName = response.userName;
},
error: function (xhr, err) {
alert(err);
}
});
}
and request
function DoTest() {
var TestRequest = new Object();
TestRequest.name = "Harry Potter";
TestRequest.Id = 33;
var username = "admin";
var password = "test";
$.ajax({
type: 'Post',
contentType: 'application/json',
cache: false,
async: false,
url: serverIP + '/TestAPI/'+ TestRequest.Id,
data: JSON.stringify(TestRequest),
dataType: "json",
beforeSend: function (xhr) {
xhr.setRequestHeader("Session-Id", localStorage.sessionId);
},
success: function (response, status, xhr) {
var s= response.message;
},
error: function (xhr, err) {
alert(xhr.statusText);
}
});
}
these questions here and here are helpful.
Also this answer for CredentialsAuthProvider, in case we can use cookies and sessions.