Spring Security Maximum session timeout not working - spring

We have this
http.sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true)
and
http
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout-gimli-user")).permitAll()
.deleteCookies("JSESSIONID", "sessionid" /*, "sessdetail", "countfr"*/ );
and
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.invalidSessionUrl("/login?invalidSession") //dokunma
.maximumSessions(1) //dokunma
.maxSessionsPreventsLogin(true) //dokunma
.expiredUrl("/login?expired")
.sessionRegistry(sessionRegistry());
in
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
And in application.properties, we have
server.servlet.session.timeout=3m
When a user tries to login, user puts password + username, then one time code is sent to user. After this, user needs to put this code and then can view pages.
But if user does not put that code but only puts username+ password, and closes browser, logout is not working. Because logout is not invoked.
But timeout should work and kill after 3 minutes. Or tomcat should kill the session (because we deploy to external tomcat 9).
I tried this
https://stackoverflow.com/a/41450580/11369236
I added
#Bean
public static ServletListenerRegistrationBean httpSessionEventPublisher() {
return new ServletListenerRegistrationBean(new HttpSessionEventPublisher());
}
but still same.
I put
List<SessionInformation> sessions = sessionRegistry.getAllSessions(authentication.getPrincipal(), false);
to
#Override
public Authentication authenticate(Authentication authentication) {
in public class CustomAuthenticationProviderWithRoles implements AuthenticationProvider {
and tried lots of logins without one time code confirm and saw that, sessions are increasing.
But when i try with putting one time code, it does not allow because of maxSessionsPreventsLogin
like here:
https://github.com/spring-projects/spring-security/issues/3078
Login page code:
<form method="POST" th:action="#{/login}">
<input autocomplete="off" class="form-control" id="mobile" name="username"
type="text">
<input autocomplete="off" class="form-control password" name="password"
type="password">
<button class="btn btn btn-block btn-primary btn-lg"
type="submit"
value="Log In">LOGIN
</button>
this is for
login:
http
.formLogin()
.loginPage("/login").permitAll()
and successhandler does this for successfull login:
response.sendRedirect("/otp");
Then, it sets the seconds which to count from for putting code. And sends another view to put code and which contains another form and submit button.
What can be best practice? For example user can close the page after putting user name password but session still remains. Despite there are timeouts.
I can use this and it solves it but I already session timeout in application.properties:
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response
request.getSession(false).setMaxInactiveInterval(11);

Related

How to hook into Spring Security authentication process?

Currently I have this trivial configuration:
// Kotlin code
override fun configure(http: HttpSecurity) {
http
.formLogin()
.loginPage("/entry")
.loginProcessingUrl("/auth")
.usernameParameter("usr")
.passwordParameter("pwd")
.defaultSuccessUrl("/", true)
.failureHandler { request, response, exception ->
// Can't figure out what to enter here (see below).
}
}
If authentication fails, I have two requirements:
Flash error message into the session (avoiding 'error' param in query string). It seems I can't inject RedirectAttributes into this lambda; is there a workaround?
I want to send back the login (but not the password) that user entered before submitting login form, in order to repopulate the field. How do I do that?
I was able to figure it out.
#Configuration
#EnableWebSecurity
class SecurityConfig: WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http
.formLogin()
.loginPage("/entry")
.loginProcessingUrl("/auth")
.usernameParameter("usr")
.passwordParameter("pwd")
.defaultSuccessUrl("/", true)
.failureHandler { request, response, _ ->
request.session.setAttribute("loginError", "Login Error!")
request.session.setAttribute("failedUsername", request.getParameter("usr"))
response.sendRedirect("/entry")
}
}
}
Then, you have to set up login controller to customize serving of login form:
#Controller
#RequestMapping("/entry")
internal class LoginController {
#GetMapping
fun getLoginForm(session: HttpSession, model: Model): String {
if (session.getAttribute("loginError") != null) {
model.addAttribute("loginError", "Login Error!")
session.removeAttribute("loginError")
model.addAttribute("failedUsername", session.getAttribute("failedUsername"))
session.removeAttribute("failedUsername")
}
return "login"
}
}
Then, you can use loginError and failedUsername model attributes in your templates:
<div th:if="${loginError}">Incorrect login/password</div>
<!-- ... -->
<input type="text" name="usr" th:value="${failedUsername}">
Basically we are emulating "flashing" messages into session. We carry these messages in the session and remove them as soon as they are read and passed on into the model. It’s possible that redirect will go wrong and messages will remain in the session, but they are harmless on their own, plus they will be removed the next time user visits /entry page.
As a result, now there is no ?error in page URL, and the user is not required to retype username.

How to handle session creation and adding hidden input csrf token for any page containing a form for an anonymous user in Spring Boot?

I Introduce the problem:
when I launch the application and I enter the url "/home". The home page is displayed but not correctly (the template is not well organized) and I receive an exception TemplateInputException. After a while, If I refresh the home page and the other pages It comes back to normal but if I go to "/login", and I logout which redirects me to the home view the same issue comes back again.
The Stacktrace Console:
org.thymeleaf.exceptions.TemplateInputException: An error happened
during template parsing (template: "class path resource
[templates/home.html]") ...
Caused by: org.attoparser.ParseException: Error during execution of processor
'org.thymeleaf.spring4.processor.SpringActionTagProcessor' (template:
"home" - line 2494, col 10) at
org.attoparser.MarkupParser.parseDocument(MarkupParser.java:393)
~[attoparser-2.0.4.RELEASE.jar:2.0.4.RELEASE]...
Caused by: org.thymeleaf.exceptions.TemplateProcessingException: Error
during execution of processor
'org.thymeleaf.spring4.processor.SpringActionTagProcessor' (template:
"home" - line 2494, col 10)
Caused by: java.lang.IllegalStateException: Cannot create a session after the response has been committed at
org.apache.catalina.connector.Request.doGetSession(Request.java:2995)
~[tomcat-embed-core-8.5.14.jar:8.5.14]
...
at org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.saveToken(HttpSessionCsrfTokenRepository.java:63)
~[spring-security-web-4.2.0.RELEASE.jar:4.2.0.RELEASE] at
org.springframework.security.web.csrf.LazyCsrfTokenRepository$SaveOnAccessCsrfToken.saveTokenIfNecessary(LazyCsrfTokenRepository.java:176)
~[spring-security-web-4.2.0.RELEASE.jar:4.2.0.RELEASE] at
org.springframework.security.web.csrf.LazyCsrfTokenRepository$SaveOnAccessCsrfToken.getToken(LazyCsrfTokenRepository.java:128)
~[spring-security-web-4.2.0.RELEASE.jar:4.2.0.RELEASE] at
org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor.getExtraHiddenFields(CsrfRequestDataValueProcessor.java:71)
~[spring-security-web-4.2.0.RELEASE.jar:4.2.0.RELEASE] ...
The Code source:
The issue is in the Contact Form of the home.html in this line: th:action="#{/home/contact}" th:object="${mailForm}":
<form id="contact-form" method="post" action="/home/contact}"
th:action="#{/home/contact}" th:object="${mailForm}"
role="form">
<!-- <input type="hidden" name="${_csrf.parameterName}"
value="${_csrf.token}" /> -->
<input type="text" name="senderName" th:field="*{senderName}">
<input type="text" name="senderLastName" th:field="*{senderLastName}">
<input type="email" name="senderEmail" th:field="*{senderEmail}">
<textarea name="message" th:field="*{message}"></textarea>
<button type="submit">Send Message</button>
</form>
I think it's a problem with csrf token. I tried to add this line <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /> in my form and the csrf protection is enabled by default by Spring Security but it did not work.
The Controller that calls the service to send mails:
#Controller
public class HomeController {
#Autowired
private EmailService emailService;
#Autowired
private MailValidator mailValidator;
// some other code like #InitBinder methode ...
// method to post mailForm
#PostMapping("/home/contact")
public String contactUsHome(#Valid #ModelAttribute("mailForm") final MailForm mailForm, BindingResult bindingResult)
throws MessagingException {
if (bindingResult.hasErrors()) {
return HOME_VIEW;
} else {
mailForm.setRecipientEmail(recipientEmail);
Mail mail = DTOUtil.map(mailForm, Mail.class);
emailService.sendSimpleMail(mail);
return REDIRECT_HOME_VIEW;
}
}
}
This is how to fix the issue "Cannot create a session and CSRF token".
In the spring security configuration class, I just added this line .and().csrf().csrfTokenRepository(..) and everything works well.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//some another line of code...
.and().csrf().csrfTokenRepository(new HttpSessionCsrfTokenRepository())
}

Spring security: Custom login controller is never beeing entered

My security configuration is as following:
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("select username, password, activated from Person where username=?")
.authoritiesByUsernameQuery("select person.username, personrole.roleName from person, personrole, personandroles where person.username=? and personandroles.personid=person.personId and personrole.roleid=personandroles.roleid");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/empl").access("hasRole('employee')")
.antMatchers("/", "/index" , "/loginform", "/registerform","/approvelogin") .permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/loginform")
.loginProcessingUrl("/approvelogin")
.usernameParameter("username")
.passwordParameter("password")
.and()
.httpBasic().disable()
.and()
.exceptionHandling().accessDeniedPage("/403");
}
Where /approvelogin is the name of my login-controller. This controller expects a Modelattribute 'Person' and a request parameter 'role' (user may select a role as which to log in)
On the loginform I have this:
<form:form action="approvelogin" modelAttribute="userBean" method="POST">
<table>
<tr>
<td><label><b>Benutzername:</b></label>
<form:input type="text" path="username" required="true"/>
</td>
</tr>
<tr>
<td><label><b>Kennwort</b></label>
<form:input type="password" path="password" required="true">
</td>
<td>
<c:if test="${!empty roles}">
<select name="role">
<c:forEach var="r" items="${roles}">
<option value='${r.roleName}'>${r.roleName}</option>
</c:forEach>
</select>
</c:if>
</td>
<td> <input type="submit" value="Login"/>
</td>
</tr>
</table>
</form:form>
When I call /empl in the browser I am beeing redirected to loginform as expected. But after editing the correct user credencials I arrive on my 403.jsp.
Setting a breakpoint on the method successfullAuthentication(request, response, chain, authResult) of the class AuthenticationProcessingFilter shows that the 'authorities' collection is filled with the correct role 'employee'.
So I assume something else prevent my login controller from beeing entered at all.
The login controller:
#RequestMapping(value="/approvelogin", method=RequestMethod.POST)
public String login(#ModelAttribute("userBean") Person user, #RequestParam(value="role") String role, Model model){
if( user.getUsername().isEmpty() || user.getPassword().isEmpty() ){
model.addAttribute("error", "Please enter your username and password");
return "loginform";
}
else {
Person p = personDao.getPerson(user.getUsername(), user.getPassword());
if ( p == null) {
model.addAttribute("error", "Invalid Details, try again:");
return "loginform";
}
else{
String view="welcome";
model.addAttribute("loggedperson", p);
for( PersonRole r: p.getRoles() ){
if(r.getRoleName().equals(role)) {
view= role +"View";
break;
}
}
return view;
}
}
}
#RequestMapping(value="/empl")
public String employeeView(){
return "employeeView";
}
Your login controller will return String with role+View, are you sure that you have view with this name?
I think you want to redirect user after login to /empl.
Finally I found a solution. Instead of setting the
.loginProcessingUrl("/approvelogin")
I have to set the
.defaultSuccessUrl("/approvelogin")
Clearly after spring has done the authentication and has athorised the user to go on I am able to get to my custom login controller in order to redirect the user to the desired view (according to his role and request).

Same form in multiple locations in Thymeleaf 3 & Spring 4

I'd like to have my login form (for example) on multiple pages, and I want to pre-populate it with the current user's login (assuming that user is recognized via cookie). But I don't want every controller method for every possible page to have to provide a LoginForm bean for the form. I do want all the validation magic when the form is submitted, and then of course I want the result of the form to the same page the user was on when they submitted it.
I can't quite figure out how to accomplish this right now. Is it even possible?
EDIT:
I've got a Thymeleaf form like this:
<form action="#" data-th-action="#{/users/login}" data-th-object="${loginForm}" method="post">
<input type="text" placeholder="Email or Username" data-th-field="${loginForm.login}">
<input type="password" placeholder="Password" data-th-field="${loginForm.password}">
<button type="submit" name="login">Sign in</button>
<button type="submit" name="register">Register</button>
</form>
If I don’t create a LoginForm (my class) bean and stick it in the model under loginForm, then I get an exception on GET, when rendering the page.
You don't need to pass the LoginForm to multiple controllers, rather, you can centralize your code at a single place with a http filter by validating the Login form for the required urls as below:
LoginFormValidationFilter class:
#Component
public class LoginFormValidationFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String url = request.getRequestURL();
if(url.equals(YOUR_LOGIN_URL)) {
//validate the request here for the required urls
//If request is invalid, send an error message back
}
chain.doFilter(req, res);
}
#Override
public void init(FilterConfig filterConfig) {
}
#Override
public void destroy() {
}
}
Web.xml filter mapping:
<filter>
<filter-name>LoginFormValidationFilter </filter-name>
<filter-class>xyz.LoginFormValidationFilter </filter-class>
</filter>
<filter-mapping>
<filter-name>LoginFormValidationFilter </filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

Display error messages in Spring login

I am using Spring security for authenticating users. I created a custom authentication provider and now I am wondering how I can get error messages from the provider into my form. This is the authenticate() method in my custom authentication provider:
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UserProfile profile = userProfileService.findByEmail(authentication.getPrincipal().toString());
if(profile == null){
throw new UsernameNotFoundException(String.format("Invalid credentials", authentication.getPrincipal()));
}
String suppliedPasswordHash = DigestUtils.shaHex(authentication.getCredentials().toString());
if(!profile.getPasswordHash().equals(suppliedPasswordHash)){
throw new BadCredentialsException("Invalid credentials");
}
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(profile, null, profile.getAuthorities());
return token;
}
This is my form:
<form name='f' action="<c:url value='j_spring_security_check' />" method='POST'>
<div id="data-entry-form">
<div class="form-entry">
<label><spring:message code="login.form.label.email"/></label>
<input type='text' name='j_username' value=''>
</div>
<div class="form-entry">
<label><spring:message code="login.form.label.password"/></label>
<input type='password' name='j_password'/>
</div>
<div class="form-entry">
<input type="submit" value="Verzenden"/>
</div>
</div>
How would I get error messages into my form? From the moment I press the login button, Spring takes over, so the only method I could generate error messages in would be the authenticate() method...
3 Steps of the safest way (we don't rely on the LAST_EXCEPTION):
Specify error page (for example "login-error") in configuration for your custom authentication provider
httpSecurity
.authorizeRequests()
.antMatchers("/css/**", "/js/**", "/img/**").permitAll()
.anyRequest().fullyAuthenticated()
.and()
.formLogin().loginPage("/login").permitAll()
.failureUrl("/login-error")
.and()
.logout().permitAll()
Create controller for url /login-error that returns view of your custom login page (for example "login") with the next code:
#Controller
public class LoginController {
#GetMapping("/login-error")
public String login(HttpServletRequest request, Model model) {
HttpSession session = request.getSession(false);
String errorMessage = null;
if (session != null) {
AuthenticationException ex = (AuthenticationException) session
.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
if (ex != null) {
errorMessage = ex.getMessage();
}
}
model.addAttribute("errorMessage", errorMessage);
return "login";
}
}
Get the error message into your page finally (ThymeLeaf tags for example):
<!--/*#thymesVar id="errorMessage" type="java.lang.String"*/-->
<div class="alert" th:if="${errorMessage}" th:text="${errorMessage}"></div>
I was able to solve it like this:
<c:if test="${param.auth eq 'failure'}">
<div class="error">
<c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}" />
</div>
</c:if>
Note that you need to detect whether there was an error via a special parameter which you can set in your spring-security config like this:
<security:form-login [...] authentication-failure-url="/login?auth=failure" />
EDIT:
Actually, passing that parameter is not necessary. Instead, one can simply check whether SPRING_SECURITY_LAST_EXCEPTION.message is defined, like this:
<c:if test="${not empty SPRING_SECURITY_LAST_EXCEPTION.message}">
<div class="error">
<c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}" />
</div>
</c:if>
I think that you should be able to get the messages in the same way than using the "standard" authenticators.
If an exception (or more than one) is thrown in the authentication process, the last exception is stored in a session attribute: SPRING_SECURITY_LAST_EXCEPTION.
So, to get the last exception message from the JSP you can use something like this:
<%=((Exception) request.getSession().getAttribute("SPRING_SECURITY_LAST_EXCEPTION")).getMessage()%>;
Of course, if you have a controller you should probably get the message from the controller and pass only the string to the jsp. This is only an example ;)

Resources