Spring MVC how to return to same view on login failed - spring

I have Spring Security configured as:
protected void configure(HttpSecurity http) throws Exception
{
http
.csrf().disable()
.formLogin()
.successHandler( successHandler() )
.loginPage("/login")
.loginProcessingUrl("/loginProcess")
.failureUrl( LOGIN_ERROR )}
How would be my controller for LOGIN_ERROR path in order to keep the user into same page but showing the expected error on login form?
And could be posible redirect user to Registration page when user failed to login 3 times?

Your first question's answer:
You can create a "CustomAuthenticationFailureHandler" by extending the AuthenticationFailureHandler class.
In CustomAuthenticationFailureHandler's onAuthenticationFailure method, write this code:
#Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException
{
saveException(request, exception);
redirectStrategy.sendRedirect(request, response, request.getHeader("referer"));
}
In your configuration file add this code instead of failureUrl:
#Bean
public CustomAuthenticationFailureHandler customAuthenticationFailureHandler()
{
return new CustomAuthenticationFailureHandler();
}
http.failureHandler(customAuthenticationFailureHandler())
Your second question's answer:
You can use a session or database to store failure attempts. Then after 3 failure attempts, you just need to change the url to redirect.

The common way is to return to the login page(failureUrl( "/login?error" )) and in the login form view:
<c:if test="${not empty error}">
<div class="error">${error}</div>
</c:if>
That way when you navigate to login the first time error parameter will be empty the the error message will not be shown.

Related

Unable to access Spring Boot login error page with custom login failure handler

Whenever we try to input wrong credentials in Spring Boot login page, we got Bad Credentials Error with link /login?error I'm trying to limit a login for which I've created a custom login failure handler and whenever I try to provide wrong credentials I'm not able to get any kind of error by Spring Security at this /login?error page in place of this, I'm getting Status 404 Error.
AppConfig
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.failureHandler(loginFailureHandler)
.permitAll()
.and()
.logout();
}
LoginFailureHandler
#Component
public class LoginFailureHandler extends SimpleUrlAuthenticationFailureHandler {
#Autowired
private UserService service;
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
String username = request.getParameter("username");
Employee emp = service.getByUsername(username);
if(emp!=null) {
if (emp.isAccountNonLocked()) {
if (emp.getFailedAttempt() < UserService.MAX_FAILED_ATTEMPTS - 1) {
service.increaseFailedAttempt(emp);
} else {
service.lock(emp);
exception = new LockedException("Your account has been locked due to three failed attempts"
+"Try again after 24 Hours....");
}
} else {
if(service.unlockWhenTimeExpired(emp)){
exception = new LockedException("Your Account is unLocked now...." +
"try to login again");
}
}
}
super.setDefaultFailureUrl("/login?error"); // I'm not getting this page while a fail login
super.onAuthenticationFailure(request, response, exception);
}
}
Since I'm unable to get this page /login?error I'm not able to display any message regarding failure login.
I assume you are using spring-boot-mvc. You can overwrite the default login page by creating a login.html in the src/main/resources/templates directory.
And in it you can display your error message by utilizing th:if="${param.error}" like so:
<div th:if="${param.error}">Wrong credentials!</div>

Can't use post method of any controller after adding facebook social in my Spring Boot

The controller has #RequestMapping("/api")
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/users" ,"/api/books/{id}","/api/authors", "/api/books","/api/categories").permitAll()
.anyRequest().authenticated()
.and().logout().logoutUrl("/logout")
.logoutSuccessUrl("/");
}
Get methods work perfect, but Post doesn't work on the same url's.
Here is an example from one GET and one POST method.
#PostMapping("/users")
User createUser(#RequestBody User user){
return this.userRepository.save(user);
}
#GetMapping("/users")
Collection<User> getUsers(){
return this.userRepository.findAll();
}
So, GET is working, but POST not.
Post method redirect me to facebook login
I solved it by adding the following line
.csrf().disable() at the beginning of the configure method

Spring Boot Redirect to requested URL after login

I have a Spring Boot UI application. I am trying to redirect users to the originally requested URL after login.
When a user requests http://www.example.com/myapp/user/22, the application aptly redirects to http://www.example.com/myapp/login. Once the user logs in, the application redirects to http://www.example.com/myapp/dashboard. I would like the application to redirect to http://www.example.com/myapp/user/22.
I have gone through several links and feel I have a proper configuration, yet, redirection is not working as expected.
My Security Config is
public class SecurityConfig extends WebSecurityConfigurerAdapter {
.....
....
#Autowired
private MyAuthenticationSuccessHandler authenticationSuccessHandler;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.
authorizeRequests()
.antMatchers("/user/**").authenticated()
.and().csrf().disable().formLogin()
.successHandler(authenticationSuccessHandler)
......
and My Success Handler is
#Component
public class MyAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
...
public MyAuthenticationSuccessHandler() {
super();
this.setDefaultTargetUrl("/myapp/dashboard");
this.setUseReferer(true);
}
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
//Do something ..........
........
.........
super.onAuthenticationSuccess(request, response, authentication);
}
I tried using SavedRequestAwareAuthenticationSuccessHandler too.
I notice that my success handler is invoked, but the target URL is always /user/login and my login controller is invoked..
#RequestMapping("/login")
public ModelAndView login(#ModelAttribute() {
if(!userIdentified) {
//go to login page
} else {
new ModelAndView("redirect:/myapp/dashboard");
}
}
and the user is redirected to "dashboard".
What else am I missing?
Use "Referer" from session attribute to get the latest request URL. On my app, i use this one
public class CustomAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
public static final String REDIRECT_URL_SESSION_ATTRIBUTE_NAME = "REDIRECT_URL";
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
Object redirectURLObject = request.getSession().getAttribute(REDIRECT_URL_SESSION_ATTRIBUTE_NAME);
if(redirectURLObject != null)
setDefaultTargetUrl(redirectURLObject.toString());
else{
setDefaultTargetUrl("/");
}
request.getSession().removeAttribute(REDIRECT_URL_SESSION_ATTRIBUTE_NAME);
super.onAuthenticationSuccess(request, response, authentication);
}
}
Edit :
Sorry i forgot to show the login controller
#RequestMapping(method = RequestMethod.GET, value = {"/login"})
String login(Model model, Principal principal, HttpServletRequest request) throws Exception{
String referer = request.getHeader("Referer"); //Get previous URL before call '/login'
//save referer URL to session, for later use on CustomAuthenticationSuccesshandler
request.getSession().setAttribute(CustomAuthenticationSuccessHandler.REDIRECT_URL_SESSION_ATTRIBUTE_NAME, referer);
return principal == null ? "login" : "redirect:/";
}
Although Singgih S answer works, BUT there is a better way as below :
Ref:
https://www.baeldung.com/spring-security-redirect-login
There is no magic in these easy to use features in Spring Security.
When a secured resource is being requested, the request will be
filtered by a chain of various filters. Authentication principals and
permissions will be checked. If the request session is not
authenticated yet, AuthenticationException will be thrown.
The AuthenticationException will be caught in the
ExceptionTranslationFilter, in which an authentication process will be
commenced, resulting in a redirection to the login page.
Therefore :
1. When redirection to the "/login" page occurs, your secured request url is saved in the session as DefaultSavedRequest object.
2. Also we know when a successful form based login occurs, one of the implementations of AuthenticationSuccessHandler is called.
so we can create a custom class and get DefaultSavedRequest in it as below :
public class CustomAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
DefaultSavedRequest defaultSavedRequest = (DefaultSavedRequest) request.getSession().getAttribute("SPRING_SECURITY_SAVED_REQUEST");
if(defaultSavedRequest != null){
getRedirectStrategy().sendRedirect(request, response, defaultSavedRequest.getRedirectUrl());
}else{
super.onAuthenticationSuccess(request, response, authentication);
}
}
}
3. We have to introduce this class in WebSecurityConfigurerAdapter :
#Override
protected void configure(HttpSecurity http) throws Exception {
http.(...).anyRequest().authenticated().and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/login")
.successHandler(new CustomAuthenticationSuccessHandler());
So you can implement your logic in the above onAuthenticationSuccess method.
Best wishes
The Spring route, ala extending SavedRequestAwareAuthenticationSuccessHandler or SimpleUrlAuthenticationSuccessHandler can be a bit clunky to implement. In the controller (ex. a POST method that processes logins), you can do the header request yourself; ex:
HttpServletRequest request =null;
String priorUrl = request.getHeader("Referer");
You will notice that you will have the URL prior to either a manual (initiated by user) logout or a session timeout (as handled by Spring session): you'll get an https://iAmPriorUrl.com/.... Then you can do whatever you want with it.

Send a http 401 error code instead of default login page, spring security

I am using a basic authorization with the Spring Security. I configure the latter via Java config.
I would like to send to a client the HTTP 401 error code with the message "Invalid login and password" if they are invalid. However, currently Spring Security simply displays me a default pop-up window.
Here is my Spring security configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().anyRequest().authenticated().and()
.httpBasic()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/pages/index.html");
http.exceptionHandling().authenticationEntryPoint(new AjaxAuthorizationPoint());
}
As far as I understood, I have to add the custom authentificationEntryPoint to handle the case of the invalid credentials i.e. I have to send there a 401 error code with the error message
Here is the code for it. For the sake of simplicity, the body of the method is rather simple.
public class AjaxAuthorizationPoint extends BasicAuthenticationEntryPoint{
#Override
public void commence(final HttpServletRequest request,
final HttpServletResponse response,
final AuthenticationException authException) throws IOException {
System.out.println("blah");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
However, the method public void commence() doesn't fire up, when I enter an invalid login and password and Spring simply sends me the default login pop-up window.
How can I redefine the default strategy? How can I configure the Spring security to send a HTTP 401 error code instead of displaying a default login page?
I think, I have found a solution for my problem. Here the code that does exactly what I need.
Spring security configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().authenticationEntryPoint(new AjaxAuthorizationPoint("/ajax_login"));
http.authorizeRequests().anyRequest().authenticated().and().httpBasic()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/pages/index.html");
}
And custom ajax authorization point:
public class AjaxAuthorizationPoint extends LoginUrlAuthenticationEntryPoint {
public AjaxAuthorizationPoint(String loginFormUrl) {
super(loginFormUrl);
}
#Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.setStatus(403);
response.getWriter().print("Invalid login/password");
response.getWriter().flush();
}
}
Would appreciate any code review.

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