I am working with Spring Security version 4.2.3.RELEASE
Use csrf is mandatory.
I used how reference the following for the login.jsp file, 5.3 Java Configuration and Form Login, thus I have:
<body>
<spring:url var="loginUrl" value="/perform_/login"/>
<form name="login" action="${loginUrl}" method="POST">
<fieldset class="fieldset">
<legend><spring:message code="login.legend"/></legend>
<c:if test="${param.error != null}">
<p class="error"><spring:message code="login.invalid"/></p>
</c:if>
<c:if test="${param.logout != null}">
<p><spring:message code="logout.valid"/></p>
</c:if>
<table>
.... fields for username and password and submit button ...
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
</form>
in other .jsp page to logout purposes exists:
<spring:url var="logoutUrl" value="/perform/logout" />
<form id="logoutForm" action="${logoutUrl}" method="post">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
<spring:message code="logout.title"/>
</form>
<script>
function formSubmit() {
document.getElementById("logoutForm").submit();
}
</script>
Finally through Javaconfig for Spring Security the following exists:
.formLogin()
.loginPage("/perfom/login")
.loginProcessingUrl("/perform_/login")
.usernameParameter("username")//default
.passwordParameter("password")//default
.defaultSuccessUrl("/welcome")
.failureUrl("/perfom/login?error") //default is /login?error
.permitAll()
.and()
.logout()
.logoutUrl("/perform/logout")//default is /logout
.logoutSuccessUrl("/perfom/login?logout")//default is /login?logout
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID");
Note: /perfom/login is used in:
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/message").setViewName("general/message");
registry.addViewController("/welcome").setViewName("general/welcome");
registry.addViewController("/perfom/login").setViewName("login/login");
}
When the login page is loaded and either username or password values are incorrects the same login page is loaded again and the error message appears. Therefore <c:if test="${param.error != null}"> works fine.
The problem is when the logout event happens it goes to the login.jsp page how is expected but the logout message does not appear. But if in that moment I try to do a login with valid values the same login.jsp page is loaded again and just then the logout message appears.
I did a research and 'seems' correct my configuration, even according with this:
How to get the logout message in custom login page
What missing configuration is need it?
TL; DR You need to add another permitAll() call after deleteCookies("JSESSIONID").
As per documentation permitAll() for FormLoginConfigurer
Ensures the urls for failureUrl(String) as well as for the HttpSecurityBuilder, the getLoginPage and getLoginProcessingUrl are granted access to any user.
But you need to grant access to logoutSuccessUrl as well (to be fair access to logoutUrl is also needed but prerequisite for logout is that user is authenticated).
So what happens in your case is this:
You perform logout and get redirect to logoutSuccessUrl in response;
You dont have access to logoutSuccessUrl so normal process kicks in: url is saved to session and you get another redirect to login page (without parameter);
When you perform login you get redirect to saved logoutSuccessUrl and finally see your message.
Related
RESOLVED:
Spring Security 4 no longer seems to provide a default login-processing-url. We must now explicitly provide it in form-login as follows.
login-processing-url="/j_spring_security_check"
I have a strange combination of behaviors that has me stumped. Found lots of tutorials and similar questions but none quite exactly like this. I hope someone can help.
Short version: The login page redisplays - regardless of good or bad username/password and the AuthenticationProvider is never called (breakpoints not tripped).
Long version:
Step 1 - successful
I was able to use spring security with the default AuthenticationProvider and the default auto-generated login screen. Good.
I was able to write a custom AuthenticationProvider and UserDetailsService. Things worked fine, using the default auto-generated login screen. Good.
... so the above rules out a lot of possible problems.
Step 2 - the problem - Setting up to use a custom login form
1) I added the form-login:
<sec:form-login
login-processing-url="/j_spring_security_check"
login-page="/login.htm"
authentication-failure-url="/login.htm?error"
username-parameter="username"
password-parameter="password"
/>
<sec:logout logout-success-url="/login.htm?logout"/>
<sec:csrf/>
2) I made sure that login URLs will be accessible:
<sec:intercept-url pattern="/login.htm" access="permitAll" />
3) I created a trivial LoginController:
#Controller
public class LoginController {
#RequestMapping(value="/login.htm")
public ModelAndView login(HttpServletRequest request,
#RequestParam(value="error", required=false) String error,
#RequestParam(value="logout",required=false) String logout) {
ModelAndView model = new ModelAndView();
if(error!=null) {
model.addObject("error", "Invalid username or password.");
}
if(logout!= null) {
model.addObject("msg", "Successfully logged out.");
}
model.setViewName("/login");
return model;
}
4) I created a trivial login.jsp:
<%# taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<title>Login</title>
</head>
<body onload="document.loginForm.username.focus()">
<h1>Login form</h1>
<div>
<h2>Username and Password</h2>
<c:if test="${not empty error}">
<div style="color: red">${error}</div>
</c:if>
<c:if test="${not empty msg}">
<div style="color: green">${msg}</div>
</c:if>
<form name="loginForm" action="<c:url value='j_spring_security_check' />" method='POST'>
<table>
<tr>
<td>User:</td>
<td><input type='text' name='username' value = '' /></td>
</tr>
<tr>
<td>Password:</td>
<td><input type='password' name='password' /></td>
</tr>
<tr>
<td colspan='2'><input name="submit" type="submit" value="submit" /></td>
</tr>
<tr><td colspan='2'>[${not empty webUser}]</td></tr>
</table>
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
</form>
</div>
</body>
</html>
5) Then I setup some breakpoints in Eclipse to see how it behaves.
The login page displays fine.
I enter username and password and hit submit.
Good or bad username and password all cause the same behavior, the redisplay of the login page.
The breakpoints in the AuthenticationProvider are never tripped.
My interpretation is that the action specified by < c : url value='j_spring_security_check' /> is not triggering the AuthenticationProvider.
Note that the very same custom AuthenticationProvider worked flawlessly when I used the built-in default spring security login form. That tells me that the problem has nothing to do with the provider itself, but rather with "reaching it" when I hit submit in login.jsp, especially since I have a breakpoint on its very first line and it is not being tripped.
I bet my error is trivial and obvious to someone familiar with Spring Security. My thanks in advance to anyone that catches it.
This question is resolved.
Spring Security 4 no longer seems to provide a default login-processing-url. We must now explicitly provide it in form-login as follows.
login-processing-url="/j_spring_security_check"
After banging my head against the wall for quite a few hours I stumbled upon the solution. I hope this is of help to others in the same situation.
Let's say that I have a page rendered by Spring Framework. It is a page with Angular 2 code. How could I receive data on the page if Spring sends it? I do not want to render a page, then do an HTTP request, then get a response. No, in Spring controller I want to send data and somehow receive it with Angular 2 and show (without additional requests). With JSPs it is obvious how to do, but how to set Angular 2 model from JSON sent by Spring upon rendering the page?
What I have right now is Spring Security managing login. Then, in controller data gets prepared depending on the user, and then I want to send it to Angular 2 page. So, I am stuck with not knowing how to render a page AND attach data for it to be rendered by Angular 2 at the same time.
I'm not sure if I totally understood the question... anyway if I did, I'd do in this way
In spring controller, before rendering the JSP page, i'd all the business logic you need to create the JSON; then I'd create a JSON string and I'd attach it to the model
It means I'd do something like that:
#RequestMapping("/render")
public String renderView(ModelAndView model)
{
//Your JSON String
String json = "{\"foo\":\"bar\"}";
model.addAttribute("json", json);
return "yourJspName";
}
Then in your JSP you can use Angular to access to the json contained in the model (that is in the pageContext); for example, by using JSTL:
<script type="text/javascript">
var theJsonString = '${json}';
</script>
or
<script type="text/javascript">
var theJsonString = '<c:out value="${json}" />';
</script>
At this point you have the JS variable named theJsonString with your JSON string and you can use it as you want... for example by passing it to angular
If you need JSON object you should to something like this:
<script type="text/javascript">
var theJsonString = '${json}';
var theJsonObj = JSON.parse(theJsonString);
</script>
or
<script type="text/javascript">
var theJsonString = '<c:out value="${json}" />';
var theJsonObj = JSON.parse(theJsonString);
</script>
I hope this can help
Angelo
I was able to solve the problem but by redesigning the app and not by finding a real fix. What I was doing is starting Angular 2 app, have some navigation, functionality on the page, then one could click login, and Spring Security came into play. However, after verification I could not figure out how to launch Angular 2 app again: start a completely new one? Or somehow use the already launched to proceed with the development? Not sure.
What I did is just start the app by showing login page always at the very beginning and only after user logs in, Angular 2 comes into play. I guess, I could set up a second website to develop another Angular app, and make a link there that would launch this one, if we need some functionality before user logs in. The problem was that I was trying to split one Angular app into two, I guess.
Just in case, here is my security.xml part (Spring Security version 4.0.4.RELEASE):
<http use-expressions="true">
<intercept-url pattern="/views/**" access="hasAnyRole('ROLE_CUSTOMER', 'ROLE_ADMIN')" />
<form-login
login-page="/login.jsp"
login-processing-url="/views/home"
authentication-failure-url="/login.jsp?error=failed_login"
/>
<logout logout-url="/logout" logout-success-url="/login.jsp" delete-cookies="JSESSIONID" />
<remember-me />
<headers>
<cache-control/>
<xss-protection/>
</headers>
</http>
Login form:
<form class="form-signin" method="POST"
action="${pageContext.servletContext.contextPath}/views/home">
<h2 class="form-signin-heading">Please sign in</h2>
<label for="inputEmail" class="sr-only">Email address</label>
<input type="text" name="username" class="form-control" placeholder="Login" required autofocus>
<label for="inputPassword" class="sr-only">Password</label>
<input type="password" name="password" class="form-control" placeholder="Password" required>
<div class="checkbox">
<label>
<input type="checkbox" value="remember-me"> Remember me
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
</form>
And a controller that launches Angular application upon successful log in:
#RequestMapping("/views/home")
public String login() {
return "index";
}
Then, in launched Angular 2 app I am planning to fetch the data by making HTTP requests, and using Springs' controllers to return me JSON. So, after Angular is launched it works by itself just interacting with the server via HTTP. After logging out a user will not get access to Angular app because it is under a protected folder and index.jsp is not in the app root folder, so it can not be launched by accessing / path.
I am trying to work on Ajax based login system using Spring Security and it was working fine, till I got with another requirement where I need to configure authentication-success-handler, so as I can do some post processing once user is authenticated by Spring security.
Earlier I was not using <form-login/> for showing Login page.My Login section is a drop down and part of header section which is common for entire application.
This is how I am trying to configure Spring security
<http pattern="/customer/**" auto-config="true" use-expressions="true" authentication-manager-ref="customerAuthenticationManager">
<!--<http pattern="/customer/**" auto-config="true" use-expressions="true">-->
<intercept-url pattern="/customer/logon.html*" access="permitAll" />
<intercept-url pattern="/customer/denied.html" access="permitAll"/>
<intercept-url pattern="/customer" access="hasRole('AUTH_CUSTOMER')" />
<intercept-url pattern="/customer/" access="hasRole('AUTH_CUSTOMER')" />
<intercept-url pattern="/customer/*.html" access="hasRole('AUTH_CUSTOMER')" />
<intercept-url pattern="/customer/*/*.html" access="hasRole('AUTH_CUSTOMER')" />
<form-login login-processing-url="/customer/logon.html" login-page="/shop/home.html"
authentication-success-handler-ref="webshopAuthenticationSuccessHandler" />
<logout invalidate-session="true"
logout-success-url="/customer/home.html"
logout-url="/customer/j_spring_security_logout" />
<access-denied-handler error-page="/customer/denied.html"/>
</http>
With these configuration, when ever I am clicking on login button with correct credentials, i am getting HTTP 302 error
http://localhost:8080/shop/customer/logon.html 302 found
Not sure what exactly I am doing wrong?
<form id="login" method="post" accept-charset="UTF-8">
<input id="userName" type="text" name="userName" size="30" />
<button type="submit" class="btn">Login</button>
</form>
Ajax Code
var data = $(this).serializeObject();
$.ajax({
'type': 'POST',
'url': "<c:url value="/customer/logon.html"/>",
'contentType': 'application/json',
'data': JSON.stringify(data),
'dataType': 'json',
....
}}:
Login Section
Authentication authenticationToken = new UsernamePasswordAuthenticationToken(customer.getUserName(), customer.getPassword());
try {
Authentication authentication = customerAuthenticationManager.authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
resp.setStatus(AjaxResponse.RESPONSE_STATUS_SUCCESS);
} catch (AuthenticationException ex) {
resp.setStatus(AjaxResponse.RESPONSE_STATUS_FAIURE);
}
Any inputs? With all my efforts, I am still getting 302 and with above configuration, even my logon.html controller is not getting called.
My main issue is when I am enabling Spring security using
<form-login login-processing-url="/customer/logon.html" login-page="/shop/home.html"
authentication-success-handler-ref="webshopAuthenticationSuccessHandler" />
I am getting 302 from logon.html and even logon.html Controller is not being triggered (placed debugger on it)
First you don't need a controller, spring security handles all this for you. So drop the controller.
The current problem is due to your JSON based post. This is handled by the normal filter which handles login, but it doesn't see a j_username and j_password field and as such will redirect (the 302) to the login page (in your case /shop/home.html) asking for those fields.
Basically you are posting the data as JSON whereas it simply should be just an ajaxified form post. This allows you to write a simple AuthenticationSuccessHandler which simply returns a 401 or a 200 status-code (and maybe a body) when things are ok.
Changing your ajax submit to something like this should make things work.
var data = $(this).serializeObject();
$.ajax({
'type': $(this).action,
'url': $(this).target,
'data': data
}):
This should create a ajax form post. Might be that you need a call to preventDefault() in the surrounding method to stop the form from actual posting.
With the login form that is currently posted it cannot work, as a form like this is needed:
<body>
<form action="/j_spring_security_check" method="POST">
<label for="username">User Name:</label>
<input id="username" name="j_username" type="text"/>
<label for="password">Password:</label>
<input id="password" name="j_password" type="password"/>
<input type="submit" value="Log In"/>
</form>
</body>
The post needs to be done j_spring_security_check, and username and password need to be suplied in fields j_username and j_password, otherwise it won't work.
I think you are getting a redirect to a login page.
You can see where the redirect is going with the chrome debugger or firebug if using firefox.
To approach this first get the rest of the config working with the default login page generated by spring security, by removing login-page="/customer/logon.html".
Once this works you can add back the custom login page, but it needs to have the elements above.
Also I believe you are trying to post a JSTL tag via ajax to the server? If it's true it won't work, otherwise can you edit the question.
Try it step by step by first using the default login page, then with an ajax request with hardcoded values just for testing and then when this works with a custom login page.
response code 302 is not an error indication. It's a way of performing a redirection.
as you have not shared your webshopAuthenticationSuccessHandler code.
But i am pretty much sure you are redirecting the request to some specified resource after processing some certain condition. so just check your header and get the URL of redirection.
and then redirect with javascript code.
An HTTP response with this status code will additionally provide a URL in the Location header field.
For an example -
Client request:
GET /index.html HTTP/1.1
Host: www.example.com
Server response:
HTTP/1.1 302 Found
Location: http://www.iana.org/domains/example/
so along with this error code you are getting http://www.iana.org/domains/example/ url where your authenticationSuccessHandler want you to redirect so redirect it through javascript code.
I'm trying to pre-populate my local registration with Linkedin Callback. I am getting a NullPointer Exception on the "Email" field when I attempt to register.
It is possible that Email Address Field is not accessible with Linkedin but the Class UserProfile (used to populate fields) Docs say otherwise - "model provides access to those common fields in an uniform way".
It works with Facebook. When the Authentication process is triggered with a click on "Sign in with Facebook" Button, I am redirected back to the local site (after authentication) with the local signup form pre-populated with name and email.
This does not work for LinkedIn or Twitter, both throw NullPointer Exception. I would like settings for both LinkedIn and Twitter.
Here is my form
Linkedin
<form name="linkedin_signin" id="linkedin_signin" action="${linkedin_uri}" method="POST">
<input type="image" src="${linkedin_img}" />
</form>
Twitter
<form:form id="twitter_signin" action="${twitter_uri}" method="POST">
<input type="image" src="${twitter_img}" />
</form:form>
You must request r_emailaddress permission when authorizing your application for LinkedIn.
<form name="linkedin_signin" id="linkedin_signin" action="${linkedin_uri}" method="POST">
<input type="image" src="${linkedin_img}" />
<input type="hidden" name="scope" value="r_emailaddress" />
</form>
Permissions for LinkedIn: https://developer.linkedin.com/documents/authentication#granting
Permissions for Facebook: http://developers.facebook.com/docs/reference/login/#permissions
It looks like you can't do it with twitter https://dev.twitter.com/discussions/4019
I want to place the Login Form of the generated Login View from the Grails Spring Security Plugin at the main layout in layouts/main.gsp. I just copied it over, but it doesn't work, because of cause, now no controller is available to pass the form data to. The form-code looks like this:
<form action='${postUrl}' method='POST' id='loginForm' class='cssform' autocomplete='off'>
<p>
<label for='username'><g:message code="springSecurity.login.username.label"/>:</label>
<input type='text' class='text_' name='j_username' id='username'/>
</p>
<p>
<label for='password'><g:message code="springSecurity.login.password.label"/>:</label>
<input type='password' class='text_' name='j_password' id='password'/>
</p>
<p id="remember_me_holder">
<input type='checkbox' class='chk' name='${rememberMeParameter}' id='remember_me' <g:if test='${hasCookie}'>checked='checked'</g:if>/>
<label for='remember_me'><g:message code="springSecurity.login.remember.me.label"/></label>
</p>
<p><input type='submit' id="submit" value='${message(code: "springSecurity.login.button")}'/></p>
</form>
How should I change this code, so that I send the form code to the Login auth action?
Thanks.
As I udenrstand, your question is what to use for form action? postUrl should be same as grails.plugins.springsecurity.apf.filterProcessesUrl property in config, and by default it's /j_spring_security_check.
If you didn't change this value, you can use /j_spring_security_check instead of ${postUrl}.
Spring Securty Plugin have special filter that handles this URL, and authorizes user. Next steps are depends on configuration, but if I remember correctly, by default it redirects user to /.
See configuration options at http://grails-plugins.github.io/grails-spring-security-core/guide/urlProperties.html