The login form

Basic authentication is good for our RESTful API, but we would rather have a login page carefully designed by our team to improve the web experience.

Spring Security allows us to define as many WebSecurityConfigurerAdapter classes as we need. We will split our SecurityConfiguration class into two parts:

  • ApiSecurityConfiguration: This will be configured first. This will secure the RESTful endpoints with basic authentication.
  • WebSecurityConfiguration: This will then configure login form for the rest of our application.

You can remove or rename SecurityConfiguration and create ApiSecurityConfiguration instead:

@Configuration
@Order(1)
public class ApiSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    public void configureAuth(AuthenticationManagerBuilder auth)
        throws Exception {
        auth.inMemoryAuthentication()
            .withUser("user").password("user").roles("USER").and()
            .withUser("admin").password("admin").roles("USER", "ADMIN");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .antMatcher("/api/**")
            .httpBasic().and()
            .csrf().disable()
            .authorizeRequests()
            .antMatchers(HttpMethod.GET).hasRole("USER")
            .antMatchers(HttpMethod.POST).hasRole("ADMIN")
            .antMatchers(HttpMethod.PUT).hasRole("ADMIN")
            .antMatchers(HttpMethod.DELETE).hasRole("ADMIN")
            .anyRequest().authenticated();
    }
}

Note the @Order(1) annotation, which will ensure that this configuration is executed before the other one. Then, create a second configuration for the web, called WebSecurityConfiguration:

package masterSpringMvc.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .formLogin()
                .defaultSuccessUrl("/profile")
                .and()
                .logout().logoutSuccessUrl("/login")
                .and()
                .authorizeRequests()
                .antMatchers("/webjars/**", "/login").permitAll()
                .anyRequest().authenticated();
    }
}

The result of this code is that anything matching /api/** will be secured with basic authentication, without CSRF protection. Then, the second configuration will be loaded. It will secure anything else. Everything in this part of the application requires the client to be authenticated, except requests on WebJars and on the login page (this will avoid the redirection loop).

If an unauthenticated user tries to access a protected resource, they will automatically be redirected to the login page.

By default, the login URL is GET /login. The default login will be posted via a POST /login request that will contain three values: a user name (username), a password (password) and a CSRF token (_csrf). If the login is unsuccessful, the user will be redirected to /login?error. The default logout page is a POST /logout request with a CSRF token.

Now, if you try to navigate on your application, this form will be generated automatically!

If you are already logged in from a previous attempt, close your browser; this will clear up the session.

The login form

We can now log in and out of our application!

This is lovely but we can do a lot better with very little effort. First, we will define a login page on /login in the WebSecurityConfiguration class:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .formLogin()
        .loginPage("/login") // <= custom login page
        .defaultSuccessUrl("/profile")
        // the rest of the configuration stays the same
}

This will let us create our own login page. To do that, we will need a very simple controller to handle the GET login request. You can create one in the authentication package:

package masterSpringMvc.authentication;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class LoginController {

    @RequestMapping("/login")
    public String authenticate() {
        return "login";
    }
}

This will trigger the display of the login.html page located in the template directory. Let's create it:

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorator="layout/default">
<head>
    <title>Login</title>
</head>
<body>
<div class="section no-pad-bot" layout:fragment="content">
    <div class="container">

        <h2 class="header center orange-text">Login</h2>

        <div class="row">
            <div id="errorMessage" class="card-panel red lighten-2" th:if="${param.error}">
                <span class="card-title">Invalid user name or password</span>
            </div>

            <form class="col s12" action="/login" method="post">
                <div class="row">
                    <div class="input-field col s12">
                        <input id="username" name="username" type="text" class="validate"/>
                        <label for="username">Username</label>
                    </div>
                </div>
                <div class="row">
                    <div class="input-field col s12">
                        <input id="password" name="password" type="password" class="validate"/>
                        <label for="password">Password</label>
                    </div>
                </div>
                <div class="row center">
                    <button class="btn waves-effect waves-light" type="submit" name="action">Submit
                        <i class="mdi-content-send right"></i>
                    </button>
                </div>
                <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
            </form>
        </div>
    </div>
</div>
</body>
</html>

Note that we handle the error message and that we post a CSRF token. We also use the default username and password input names, but those are configurable if needed. The result looks much better already!

The login form

You can see right away that Spring Security assigns anonymous credentials to all non-authenticated users by default.

We shouldn't show the sign-out button to an anonymous user so we can wrap the corresponding HTML part in sec:authorize="isAuthenticated()" to display it to authenticated users only, like so:

<div sec:authorize="isAuthenticated()">
    You are logged as <b sec:authentication="name"/> with roles <span sec:authentication="authorities"/>
    -
    <form th:action="@{/logout}" method="post" style="display: inline-block">
        <input type="submit" value="Sign Out"/>
    </form>
    <hr/>
</div>
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.22.41.235