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

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())
}

Related

Spring Security Maximum session timeout not working

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);

Unable to upload xsl file with multiple sheets..[FileUploadException: Stream closed]

I tried various permutation an combination and googled as well,when I am uploading xsl file with multiplesheet i am getting following Exception-
org.springframework.web.multipart.MultipartException: Could not
parse multipart servlet request; nested exception is
org.apache.commons.fileupload.FileUploadException: Stream closed] with
root cause java.io.IOException: Stream closed
Below is my code-
<form name="upload" th:action="#{/util/uploadExcel}" action="/util/uploadExcel" enctype="multipart/form-data" method="post">
<input type="file" name="fileData" path="fileData" />
<input type="submit"/>
</form>
#Controller
#RequestMapping("/util")
public class UtilController {
#RequestMapping(value=("/uploadExcel"),headers=("content-type=multipart/*"),method= RequestMethod.POST)
public ModelAndView uploadExcel(#RequestParam("fileData") CommonsMultipartFile file,BindingResult result) {
utilService.uploadData(file);
ModelAndView modelAndView = new ModelAndView("paidAnalysis/index");
return modelAndView;
}
#Bean
public CommonsMultipartResolver multipartResolver() {
return new CommonsMultipartResolver();
}
}
MoreOver I have added dependency for 'commons-fileupload' in my project.
When I upload any csv file other than xsl I am getting-
There was an unexpected error (type=Bad Request, status=400).
Required CommonsMultipartFile parameter 'fileData' is not present

Error upload file with Spring Security and Thymeleaf

In my situation I have a controller class with Spring Data and validator of Spring MVC which handle form data with a template made with Thymeleaf 2.1.1
I use Spring Framework 3.2.8 and Spring Security 3.2.3.
Controller
#RequestMapping(value = "/save", method = {RequestMethod.POST, RequestMethod.GET })
public String save(#RequestParam("curriculumVitae") MultipartFile curriculumVitae, #Valid #ModelAttribute("user") User user, BindingResult result, Model model, Pageable pageable) {
try {
user.setCurriculumVitae(curriculumVitae.getBytes());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
userRepository.save(user);
return "/crudUser";
View
<form id="myform"
action="#"
th:action="#{${url}+'/save'+'?' + ${_csrf.parameterName} + '=' + ${_csrf.token}}"
th:object="${user}"
th:method="post"
enctype="multipart/form-data">
<div class="col-sm-2 col-lg-2">
<div th:class="form-group" th:classappend="${#fields.hasErrors('curriculumVitae')}? has-error">
<label class="control-label" scope="col"
th:text="#{crudUser.curriculumVitae}">Curriculum Vitae</label>
<input class="form-control input-sm" type="file" th:field="*{curriculumVitae}" name="curriculumVitae" />
</div>
</div>
</form>
The error displayed in my log is:
Failed to convert property value of type org.springframework.web.multipart.commons.CommonsMultipartFile to required type byte[] for property curriculumVitae; nested exception is java.lang.IllegalArgumentException: Cannot convert value of type [org.springframework.web.multipart.commons.CommonsMultipartFile] to required type [byte] for property curriculumVitae[0]: PropertyEditor [org.springframework.beans.propertyeditors.CustomNumberEditor] returned inappropriate value of type [org.springframework.web.multipart.commons.CommonsMultipartFile]
Thanks in advance for who will help me.
SOLVED!
In the stacktrace I have forget of set the maxUploadSize in the MultiPartResolver in my java configuration file with a value more high then default.
#Bean
public MultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(500000000);
return multipartResolver;
}

With Spring Security 3.2.0.RELEASE, how can I get the CSRF token in a page that is purely HTML with no tag libs

Today I upgraded from Spring Security 3.1.4 with the separate java config dependency, to the new 3.2.0 release which includes java config. CSRF is on by default and I know I can disable it in my overridden configure method with "http.csrf().disable()". But suppose I don't want to disable it, but I need the CSRF token on my login page where no JSP tag libs or Spring tag libs are being used.
My login page is purely HTML that I use in a Backbone app that I've generated using Yeoman. How would I go about including the CSRF token that's contained in the HttpSession in either the form or as a header so that I don't get the "Expected CSRF token not found. Has your session expired?" exception?
You can obtain the CSRF using the request attribute named _csrf as outlined in the reference. To add the CSRF to an HTML page, you will need to use JavaScript to obtain the token that needs to be included in the requests.
It is safer to return the token as a header than in the body as JSON since JSON in the body could be obtained by external domains. For example your JavaScript could request a URL processed by the following:
CsrfToken token = (CsrfToken) request.getAttribute("_csrf");
// Spring Security will allow the Token to be included in this header name
response.setHeader("X-CSRF-HEADER", token.getHeaderName());
// Spring Security will allow the token to be included in this parameter name
response.setHeader("X-CSRF-PARAM", token.getParameterName());
// this is the value of the token to be included as either a header or an HTTP parameter
response.setHeader("X-CSRF-TOKEN", token.getToken());
Your JavaScript would then obtain the header name or the parameter name and the token from the response header and add it to the login request.
Although #rob-winch is right I would suggest to take token from session. If Spring-Security generates new token in SessionManagementFilter using CsrfAuthenticationStrategy it will set it to Session but not on Request. So it is possible you will end up with wrong csrf token.
public static final String DEFAULT_CSRF_TOKEN_ATTR_NAME = HttpSessionCsrfTokenRepository.class.getName().concat(".CSRF_TOKEN");
CsrfToken sessionToken = (CsrfToken) request.getSession().getAttribute(DEFAULT_CSRF_TOKEN_ATTR_NAME);
Note: I'm using CORS and AngularJS.
NoteĀ²: I found Stateless Spring Security Part 1: Stateless CSRF protection which would be interesting to keep the AngularJS' way to handle CSRF.
Instead of using Spring Security CSRF Filter which is based on answers (especially #Rob Winch's one), I used the method described in The Login Page: Angular JS and Spring Security Part II.
In addition to this, I had to add Access-Control-Allow-Headers: ..., X-CSRF-TOKEN (due to CORS).
Actually, I find this method cleaner than adding headers to the response.
Here is the code :
HttpHeaderFilter.java
#Component("httpHeaderFilter")
public class HttpHeaderFilter extends OncePerRequestFilter {
#Autowired
private List<HttpHeaderProvider> providerList;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
providerList.forEach(e -> e.filter(request, response));
if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) {
response.setStatus(HttpStatus.OK.value());
}
else {
filterChain.doFilter(request, response);
}
}
}
HttpHeaderProvider.java
public interface HttpHeaderProvider {
void filter(HttpServletRequest request, HttpServletResponse response);
}
CsrfHttpHeaderProvider.java
#Component
public class CsrfHttpHeaderProvider implements HttpHeaderProvider {
#Override
public void filter(HttpServletRequest request, HttpServletResponse response) {
response.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "X-CSRF-TOKEN");
}
}
CsrfTokenFilter.java
#Component("csrfTokenFilter")
public class CsrfTokenFilter 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);
}
}
web.xml
...
<filter>
<filter-name>httpHeaderFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>httpHeaderFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
...
security-context.xml
...
<custom-filter ref="csrfTokenFilter" after="CSRF_FILTER"/>
...
app.js
...
.run(['$http', '$cookies', function ($http, $cookies) {
$http.defaults.transformResponse.unshift(function (data, headers) {
var csrfToken = $cookies['XSRF-TOKEN'];
if (!!csrfToken) {
$http.defaults.headers.common['X-CSRF-TOKEN'] = csrfToken;
}
return data;
});
}]);
I use thymeleaf with Spring boot. I had the same problem. I diagnosed problem viewing source of returned html via browser. It should be look like this:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example </title>
</head>
<body>
<form method="post" action="/login">
<div><label> User Name : <input type="text" name="username" /> </label></div>
<div><label> Password: <input type="password" name="password" /> </label></div>
<input type="hidden" name="_csrf" value=<!--"aaef0ba0-1c75-4434-b6cf-62c975dcc8ba"--> />
<div><input type="submit" value="Sign In" /></div>
</form>
</body>
</html>
If you can't see this html code. You may be forgot to put th: tag before name and value. <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
login.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example </title>
</head>
<body>
<div th:if="${param.error}"> Invalid username and password. </div>
<div th:if="${param.logout}"> You have been logged out. </div>
<form th:action="#{/login}" method="post">
<div><label> User Name : <input type="text" name="username"/> </label></div>
<div><label> Password: <input type="password" name="password"/> </label></div>
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
<div><input type="submit" value="Sign In"/></div>
</form>
</body>
</html>

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