Spring Rest Service - Invalid CSRF token when I attempt to login - spring

I have a Spring MVC REST service, with Spring Security (3.2.5.RELEASE) enabled. When I turn on #EnableWebMvcSecurity, a login form is automatically generated for me at http://localhost:8080/login. If I use this form to login, everything works just fine.
The problem occurs when I attempt to login by sending a POST request directly. In my post request, I provide the username and password. I also include the http header 'X-CSRF-TOKEN' and for the header value, I use the JSESSIONID that I see has been generated in a cookie. But when I send this POST request, I get back the following result:
HTTP Status 403 - Invalid CSRF Token '29F5E49EFE8D758D4903C0491D56433E'
was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'.
What am I doing wrong? Am I providing the wrong token value? What is this JSESSIONID? If I don't enter a value for this header, or omit the header all together, it tells me "Null CSRF token found".
Below is my Spring Security configuration:
#Configuration
#EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/secure/**").authenticated()
.and()
.formLogin()
.usernameParameter("username")
.passwordParameter("password")
.and()
.logout()
.and()
.httpBasic()
.and()
.csrf();
}
}
I'd really appreciate any help! Thanks in advance!

(1) Include the CSRF token within all your AJAX requests.
$(function () {
var token = $('#logoutform>input').val();
var header = $('#logoutform>input').attr('name');
$(document).ajaxSend(function(e, xhr, options) {
xhr.setRequestHeader('X-CSRF-TOKEN', token);
});
});
(2) Simple request .
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>

You need to send the csrf token when you submit the login form. Please add the below line in the HTML form:
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>

you need <meta name="_csrf" content="${_csrf.token}"/> , https://spring.io/blog/2013/08/21/spring-security-3-2-0-rc1-highlights-csrf-protection/#ajax-requests
or if you are using thymeleaf <meta name="_csrf" th:content="${_csrf.token}" />

Related

How to get CSRF token using Postman?

I'm using Spring Security to generate the CSRF token for me.
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
...etc
}
But I'm unable to find the cookies in Postman, no cookie found for X-XSRF-TOKEN to use.
Thanks to #SteveRiesenberg.
in Spring Security 6 , CSRF tokens are defered

Spring Security: How to get checkbox value from login page in my controller?

How to I get the value of the check box in my loginpage?
In my Jsp I have a remember me check box.
<form:form action="${pageContext.request.contextPath}/amPostLogin" method="POST" modelAttribute="userLogin">
<form:input type="text" path="username" id="username" placeholder="username"required="required" autofocus="autofocus"/>
<form:input type="password" path="password" id="password" placeholder="password" required="required"/>
<br><form:checkbox label="Remember Me" path="isRemember" />
<br><form:checkbox label="Auto Login" path="isAutoLogin"/>
<form:button class="login-button" >Login</form:button>
</form:form>
In my Spring Security I have:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/am*")
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login_AM")
.loginProcessingUrl("/amPostLogin")
.failureUrl("/login_AM?error=true")
.defaultSuccessUrl("/amChatPage")
.and()
.logout()
.logoutUrl("/amLogout")
.logoutSuccessUrl("/logoutSuccessful_AM")
.and()
.exceptionHandling()
.accessDeniedPage("/am403")
.and().httpBasic()
.and().csrf().disable();
}
Now I want to get the values of AutoLogin and RememberMe in my controller.
How do I do that?
I tried creating a controller for "/amPostLogin" but for some reason it doesn't get inside the controller. How can I get the value of those checkbox in my controller? Please someone help me. Thank you.
ANSWER
Instead of getting the value of checkbox in the controller, I created an AuthenticationSuccessHandler and get the values there.
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
boolean isRemember = Boolean.parseBoolean(request.getParameter("isRemember"));
boolean isAutoLogin = Boolean.parseBoolean(request.getParameter("isAutoLogin"));
response.setStatus(HttpServletResponse.SC_OK);
response.sendRedirect("amChatPage");
}
If you are using spring mvc your login jsp needs some changes .
1)You login page action should be mapped to j_spring_security_check.htm, spring's UsernamePasswordAuthenticationFilter.java intercepts this url for authentication.
2)Your username and password should be mapped to is j_username and j_password , this above filter expects these fields to be set, you can check the source code.
3)You also need to provide an authentication provider in context.xml
4)You need to define lgtAuthenticationFailureHandler and lgtAuthenticationsuccessHandler. From lgtAuthenticationsuccessHandler you can direct to your home page of application.
This is the login part.You can refer many sites for login authentication in spring.
For cookie part,
what you can do is on click of remember me checkbox , you can write a javascript that will create a remember me cookie with username in it and all other parameters like expiry n all.
when next time the page loads you can write a onload javascript funtion where you can check if remember me cookie exists and based in that you can populate username and tick your remember me checkbox.
This is how i have implemented so you can try this.

Spring Security unexpected behavior for REST endpoints authentication?

The scenario we are looking for is as follows:
client connects with REST to a REST login url
Spring microservice (using Spring Security) should return 200 OK and a login token
the client keeps the token
the client calls other REST endpoints using the same token.
However, I see that the client is getting 302 and a Location header, together with the token. So it does authenticate, but with un-desired HTTP response status code and header.
The Spring Security configuration looks like this:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable() // Refactor login form
// See https://jira.springsource.org/browse/SPR-11496
.headers()
.addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN))
.and()
.formLogin()
.loginPage("/signin")
.permitAll()
.and()
.logout()
.logoutUrl("/signout")
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated();
...
}
I tried adding interceptors and filters but can't see where 302 and Location being set and added in Spring side.
However, the Location header does show in the response headers received at the client side (together with the rest of the Spring Security headers LINK):
Server=Apache-Coyote/1.1
X-Content-Type-Options=nosniff
X-XSS-Protection=1; mode=block
Cache-Control=no-cache, no-store, max-age=0, must-revalidate
Pragma=no-cache
Expires=0
X-Frame-Options=DENY, SAMEORIGIN
Set-Cookie=JSESSIONID=D1C1F1CE1FF4E1B3DDF6FA302D48A905; Path=/; HttpOnly
Location=http://ec2-35-166-130-246.us-west-2.compute.amazonaws.com:8108/ <---- ouch
Content-Length=0
Date=Thu, 22 Dec 2016 20:15:20 GMT
Any suggestion how to make it work as expected ("200 OK", no Location header and the token)?
NOTE: using Spring Boot, Spring Security, no UI, just client code calling REST endpoints.
If you need a rest api, you must not use http.formLogin(). It generates form based login as described here.
Instead you can have this configuration
httpSecurity
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.logout()
.disable()
.addFilterBefore(authTokenFilter, UsernamePasswordAuthenticationFilter.class);
Create a class, AuthTokenFilter which extends Spring UsernamePasswordAuthenticationFilter and override doFilter method, which checks for an authentication token in every request and sets SecurityContextHolder accordingly.
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse resp = (HttpServletResponse) response;
resp.setHeader("Access-Control-Allow-Origin", "*");
resp.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
resp.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, " + tokenHeader);
HttpServletRequest httpRequest = (HttpServletRequest) request;
String authToken = httpRequest.getHeader(tokenHeader);
String username = this.tokenUtils.getUsernameFromToken(authToken); // Create some token utility class to manage tokens
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(-------------);
// Create an authnetication as above and set SecurityContextHolder
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
Then create an AuthenticationController, mapped with /login url, which checks credentials, and returns token.
/*
* Perform the authentication. This will call Spring UserDetailsService's loadUserByUsername implicitly
* BadCredentialsException is thrown if username and password mismatch
*/
Authentication authentication = this.authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
authenticationRequest.getUsername(),
authenticationRequest.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetailsImp userDetails = (UserDetailsImp) authentication.getPrincipal();
// Generate token using some Token Utils class methods, using this principal
To understand loadUserByUsername , UserDetailsService and UserDetails, please refer Spring security docs
}
For better understanding, please thoroughly read above link and subsequent chapters.
It's a 302 response telling the browser to redirect to your login page. What do you expect to happen? 302 response must have a Location header.
http.formLogin()
is designed for form-based login. So the 302 status and Location header in the response is expected if you attempt to access a protected resource without being authenticated.
Based on your requirement/scenario,
client connects with REST to a REST login url
have you considered using HTTP Basic for authentication?
http.httpBasic()
Using HTTP Basic, you can populate the Authorization header with the username/password and the BasicAuthenticationFilter will take care of authenticating the credentials and populating the SecurityContext accordingly.
I have a working example of this using Angular on the client-side and Spring Boot-Spring Security on back-end.
If you look at security-service.js, you will see a factory named securityService which provides a login() function. This function calls the /principal endpoint with the Authorization header populated with the username/password as per HTTP Basic format, for example:
Authorization : Basic base64Encoded(username:passsword)
The BasicAuthenticationFilter will process this request by extracting the credentials and ultimately authenticating the user and populating the SecurityContext with the authenticated principal. After authentication is successful, the request will proceed to the destined endpoint /principal which is mapped to SecurityController.currentPrincipal which simply returns a json representation of the authenticated principal.
For your remaining requirements:
Spring microservice (using Spring Security) should return 200 OK and a login token
the client keeps the token
the client calls other REST endpoints using the same token.
You can generate a security/login token and return that instead of the user info. However, I would highly recommend looking at Spring Security OAuth if you have a number of REST endpoints deployed across different Microservices that need to be protected via a security token. Building out your own STS (Security Token Service) can become very involved and complicated so not recommended.
You can implement your custom AuthenticationSuccessHandler and override method "onAuthenticationSuccess" to change the response status as per your need.
Example:
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
ObjectMapper mapper = new ObjectMapper();
Map<String, String> tokenMap = new HashMap<String, String>();
tokenMap.put("token", accessToken.getToken());
tokenMap.put("refreshToken", refreshToken.getToken());
response.setStatus(HttpStatus.OK.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
mapper.writeValue(response.getWriter(), tokenMap);
}
You need to override the default logout success handler to avoid redirect. In spring boot2 you can do as below:
....logout().logoutSuccessHandler((httpServletRequest,httpServletResponse,authentication)->{
//do nothing not to redirect
})
For more details: Please check this.
You can use headers().defaultsDisabled() and then chain that method to add the specific headers you want.

Spring Security blocking Rest Controller

My app has Spring boot 1.3.2 and I'm trying use Spring MVC with Spring Security.
I have administration panel under http://localhost:8080/admin and my page content for common users under http://localhost:8080/
If You are trying to open an admin panel (http://localhost:8080/admin) You have to log in, if You are common just enter http://localhost:8080/ and have fun no log in required.
My Security config class:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password("password")
.roles("ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/**").permitAll()
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/login");
}
}
Config above let me to require login from /admin
But I have some problem with Admin panel features.
This is Controller I'm trying to request with POST from admin panel:
#RestController
#RequestMapping("/admin/v1")
public class AdminController {
#RequestMapping(value = "/logout", method = RequestMethod.POST)
public String logout(HttpServletRequest request, HttpServletResponse response) {
String hello = "hi!";
return hello;
}
}
So I can log in, browser render Admin panel for me but when I'm clicking logout button which request POST logout method from Controller above. App tells me 403 Forbidden
Can anybody tell me what I'm doing wrong?
Most probably the 403 Forbidden error is because the spring by default enable csrf protection.
You can disable csrf in configuration or Include the CSRF Token in the POST method.
Disable csrf in config:
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/**").permitAll()
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/login")
.and()
.logout()
.logoutSuccessUrl("/admin/v1/logout");
Include the CSRF Token in Form Submissions:
<c:url var="logoutUrl" value="/admin/v1/logout"/>
<form action="${logoutUrl}" method="post">
<input type="submit" value="Log out" />
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
</form>

Spring Security Disable Login Page / Redirect

Is there a way to disable the redirect for Spring Security and the login page. My requirements specify the login should be part of the navigation menu.
Example:
Therefore there is no dedicated login page. The login information needs to be submitted via Ajax. If an error occurs it should return JSON specifying the error and use the proper HTTP Status code. If authentication checks out it should return a 200 and then javascript can handle it from there.
I hope that makes sense unless there is any easier way to accomplish this with Spring Security. I don't have much experience with Spring Security. I assume this has to be a common practice, but I didn't find much.
Current spring security configuration
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/public/**").permitAll()
.antMatchers("/about").permitAll()
.anyRequest().fullyAuthenticated()
.and()
.formLogin()
.loginPage("/login")
.failureUrl("/login?error")
.usernameParameter("email")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.deleteCookies("remember-me")
.logoutSuccessUrl("/")
.permitAll()
.and()
.rememberMe();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(new BCryptPasswordEncoder());
}
Update:
I tried using HttpBasic() but then it asks for login creds not matter what and its the ugly browser popup which is not acceptable to the end user. It looks like I may have to extend AuthenticationEntryPoint.
At the end of the day I need Spring security to send back JSON saying the authentication succeeded or failed.
The redirect behavior comes from SavedRequestAwareAuthenticationSuccessHandler which is the default success handler. Thus an easy solution to remove the redirect is to write your own success handler. E.g.
http.formLogin().successHandler(new AuthenticationSuccessHandler() {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//do nothing
}
});
You need to disable redirection in a couple of different places. Here's a sample based on https://github.com/Apress/beg-spring-boot-2/blob/master/chapter-13/springboot-rest-api-security-demo/src/main/java/com/apress/demo/config/WebSecurityConfig.java
In my case, I don't return json body but only HTTP status to indicate success/failure. But you can further customize the handlers to build the body. I also kept CSRF protection on.
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
public void initialize(AuthenticationManagerBuilder auth, DataSource dataSource) throws Exception {
// here you can customize queries when you already have credentials stored somewhere
var usersQuery = "select username, password, 'true' from users where username = ?";
var rolesQuery = "select username, role from users where username = ?";
auth.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery(usersQuery)
.authoritiesByUsernameQuery(rolesQuery)
;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// all URLs are protected, except 'POST /login' so anonymous user can authenticate
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.anyRequest().authenticated()
// 401-UNAUTHORIZED when anonymous user tries to access protected URLs
.and()
.exceptionHandling()
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
// standard login form that sends 204-NO_CONTENT when login is OK and 401-UNAUTHORIZED when login fails
.and()
.formLogin()
.successHandler((req, res, auth) -> res.setStatus(HttpStatus.NO_CONTENT.value()))
.failureHandler(new SimpleUrlAuthenticationFailureHandler())
// standard logout that sends 204-NO_CONTENT when logout is OK
.and()
.logout()
.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler(HttpStatus.NO_CONTENT))
// add CSRF protection to all URLs
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
;
}
}
Here's a deep explanation of the whole process, including CSRF and why you need a session: https://spring.io/guides/tutorials/spring-security-and-angular-js/
Scenarios that I tested:
happy path
GET /users/current (or any of your protected URLs)
request --> no cookie
<- response 401 + cookie XSRF-TOKEN
POST /login
-> header X-XSRF-TOKEN + cookie XSRF-TOKEN + body form with valid username/password
<- 204 + cookie JSESSIONID
GET /users/current
-> cookie JSESSIONID
<- 200 + body with user details
POST /logout
-> header X-XSRF-TOKEN + cookie XSRF-TOKEN + cookie JSESSIONID
<- 204
=== exceptional #1: bad credentials
POST /login
-> header X-XSRF-TOKEN + cookie XSRF-TOKEN + body form with bad username/password
<- 401
=== exceptional #2: no CSRF at /login (like a malicious request)
POST /login
-> cookie XSRF-TOKEN + body form with valid username/password
<- 401 (I would expect 403, but this should be fine)
=== exceptional #3: no CSRF at /logout (like a malicious request)
(user is authenticated)
POST /logout
-> cookie XSRF-TOKEN + cookie JSESSIONID + empty body
<- 403
(user is still authenticated)
On my project I implemented it for the requirements:
1) For rest-request 401 status if user is not authorized
2) For simple page 302 redirect to login page if user is not authorized
public class AccessDeniedFilter extends GenericFilterBean {
#Override
public void doFilter(
ServletRequest request,
ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
try {
filterChain.doFilter(request, response);
} catch (Exception e) {
if (e instanceof NestedServletException &&
((NestedServletException) e).getRootCause() instanceof AccessDeniedException) {
HttpServletRequest rq = (HttpServletRequest) request;
HttpServletResponse rs = (HttpServletResponse) response;
if (isAjax(rq)) {
rs.sendError(HttpStatus.FORBIDDEN.value());
} else {
rs.sendRedirect("/#sign-in");
}
}
}
}
private Boolean isAjax(HttpServletRequest request) {
return request.getContentType() != null &&
request.getContentType().contains("application/json") &&
request.getRequestURI() != null &&
(request.getRequestURI().contains("api") || request.getRequestURI().contains("rest"));
}
}
And enable the filter:
#Override
protected void configure(HttpSecurity http) throws Exception {
...
http
.addFilterBefore(new AccessDeniedFilter(),
FilterSecurityInterceptor.class);
...
}
You can change handle AccessDeniedException for you requirements in the condition:
if (isAjax(rq)) {
rs.sendError(HttpStatus.FORBIDDEN.value());
} else {
rs.sendRedirect("/#sign-in");
}
When a browser gets a 401 with "WWW-Authetication: Basic ... ", it pops up a Dialog. Spring Security sends that header unless it sees "X-Requested-With" in the request.
You should send "X-Requested-With: XMLHttpRequest" header for all requests, this is an old fashioned way of saying - I am an AJAX request.

Resources