Author: Xiao Xian

Source code repository: github.com/zhshuixian/…

For the Web system, it is necessary to control the access permissions of pages and API interfaces. For example, you need to prevent non-system users from accessing and control the access permissions of different pages or interfaces. Common Security frameworks in Java development are Spring Security and Apache Shiro.

Spring Security is a Security framework for the Spring ecosystem. It is based on Spring AOP and Servlet filter implementation, and is the recommended Security framework for Spring Boot. It provides a comprehensive security solution that handles authentication and authorization at both the Web request level and method invocation level.

Spring Security mainly includes the following two parts:

  • Login Authentication
  • Authorization

In this section, Spring Boot integrates Spring Security to control user registration, login, and access to role permissions.

The database uses MySQL and the data persistence layer framework uses MyBatis.

Spring Security uses sessions by default, so instead of RESTful apis, it uses the MVC pattern. Thymeleaf acts as a template engine for Web pages

1) Dependency import and project configuration

Create a new project 05-Spring-Security and ensure that the Spring Boot version is 2.1.X

1.1) Dependency introduction

Spring Security provides a Starter for Spring Boot, enabling Spring Boot to integrate Security with almost zero configuration development.

Thymeleaf – Extras-SpringSecurity 5 is an extension of Thymeleaf that controls the presentation of Web elements on Web pages.

Gradle project dependencies

implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org. Mybatis. Spring. The boot: mybatis - spring - the boot - starter: 2.1.1' implementation 'org.springframework.boot:spring-boot-starter-web' runtimeOnly 'mysql:mysql-connector-java' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' // https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity5 compile group: 'org.thymeleaf.extras', name: 'thymeleaf-extras-springSecurity5 ', version: '3.0.4.RELEASE'Copy the code

The Maven project dependencies are at the bottom of this article

1.2) Project configuration

Configure the MySQL database and MyBatis camel name conversion, application.properties

You only need to change the database URL, username, password, and JDBC Driver
MySQL 8 needs to specify serverTimezone to connect to MySQLspring.datasource.url=jdbc:mysql://localhost:3306/spring? useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
spring.datasource.password=xiaoxian
spring.datasource.username=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# MyBatis Hump naming conversion
mybatis.configuration.map-underscore-to-camel-case=true
Copy the code

Add @ MapperScan

@MapperScan("org.xian.security.mapper")
public class SecurityApplication {}
Copy the code

2) Start using Spring Security

The project uses a three-tier model similar to MVC

View presentation layer: Thymeleaf renders Web pages. Controller Controller: The main logical part of an application. Model Model layer: write the corresponding MyBatis Mapper interface to realize the interaction with MySQL database.

2.1) Data table structures and Mapper entity classes

Create the following user table sys_user

field type note
user_id bigint Since the primary key
username varchar(18) User name, not null unique
password varchar(60) Password, not empty
user_role varchar(8) USER Role (USER/ADMIN)

The USER role is USER/ADMIN. The case that a USER may have multiple roles is not considered.

SQL

use spring;
create table sys_user
(
    user_id   bigint auto_increment,
    username  varchar(18)  not null unique,
    password  varchar(60) not null,
    user_role varchar(8)   not null,
    constraint sys_user_pk
        primary key (user_id)
);
Copy the code

Mapper Entity class: Create a package named Entity. Create a new SysUser class under Entity:

public class SysUser implements Serializable {
    private static final long serialVersionUID = 4522943071576672084L;
    private Long userId;
    private String username;
    private String password;
    private String userRole;
    // omit getter setter constructor
}
Copy the code

2.2) Mapper interface

// Use annotations here
public interface SysUserMapper {
    /** Insert a record * to sys_user@paramSysUser User information */
    @Insert("Insert Into sys_user(username, password,user_role) Values(#{username}, #{password},#{userRole})")
    @Options(useGeneratedKeys = true, keyProperty = "userId")
    void insert(SysUser sysUser);

    /** Query user information based on Username *@paramUsername username *@returnUser information */
    @Select("Select user_id,username, password,user_role From sys_user Where username=#{username}")
    SysUser selectByUsername(String username);
}
Copy the code

2.3) Spring Security configuration

Using Spring Security, only need to implement UserDetailsService interfaces and inheritance WebSecurityConfigurerAdapter.

Create the security package and create the MyUserDetailsServiceImpl and SpringSecurityConfig classes

Implement UserDetailsService

@Service
public class MyUserDetailsServiceImpl implements UserDetailsService {
    @Resource
    private SysUserMapper sysUserMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser sysUser = sysUserMapper.selectByUsername(username);
        if (null == sysUser) {
            throw new UsernameNotFoundException(username);
        }
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("ROLE_" + sysUser.getUserRole()));
        return newUser(sysUser.getUsername(), sysUser.getPassword(), authorities); }}Copy the code

Code parsing:

LoadUserByUsername: Passes the user name, user password, and user role to Spring Security by overwriting the loadUserByUsername method of the UserDetailsService interface.

List

: authorities. Add Can add multiple user roles. For a system with multiple user roles, you can add a user role table and a user-role mapping table to store information about multiple user roles.

“ROLE_” + sysuser.getUserRole () : The Spring Security role name starts with “ROLE_” by default.

The user’s password is not verified when the user information is queried here, because the password verification part is completed by Spring Security.

SpringSecurityConfig class

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    @Resource
    private MyUserDetailsServiceImpl userDetailsService;

    @Override
    public void configure(WebSecurity web) {
        // Ignore front-end static resources such as CSS and js
        web.ignoring().antMatchers("/css/**");
        web.ignoring().antMatchers("/js/**");
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // Set the password encryption mode, verify the password here
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    public PasswordEncoder passwordEncoder(a) {
        / / use BCryptPasswordEncoder
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // Allow unauthorized access to "/login", "/register" "/register-save"
        // All other addresses must be authenticated
        http.authorizeRequests()
                .antMatchers("/login"."/register"."/register-save"."/error").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                // Username and password parameter names
                .passwordParameter("password") 
                .usernameParameter("username")
                // Specify the login page
                .loginPage("/login")
                // Login error jumps to /login-error
                .failureUrl("/login-error")
                .permitAll()
                .and()
                // Set the URL for logging out and redirect to the page after logging out successfully
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login"); }}Copy the code

Code parsing:

BCryptPasswordEncoder: Encrypts stored passwords using the BCrypt strong hash method. For Web systems, passwords are almost never stored in clear text. The BCryptPasswordEncoder class provided by Spring Security implements password encryption. BCrypt strong hashing is different each time it encrypts.

2.3) Realize user registration function

In Resources/Templates /, create register.html


      
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>registered</title>
</head>
<body>
<p>The sign-up page</p>
<p th:if="${error}" class="error">Registration error</p>
<form th:action="@{/register-save}" method="post">
    <label for="username">User name:</label>:
    <input type="text" id="username" name="username" autofocus="autofocus"/> <br/>
    <label for="password">Password:</label>:
    <input type="password" id="password" name="password"/> <br/>
    <label for="userRole">User roles</label>:
    <select name="userRole" id="userRole">
        <option value="ADMIN">The administrator</option>
        <option value="USER">The average user</option>
    </select>
    <br/>
    <input type="submit" value="Registered"/><br/>
    <a href="index.html" th:href="@ {} /">Return to the home page</a> <br/>
    <a href="login.html" th:href="@{/login}">The login</a>
</form>
</body>
</html>
Copy the code

Code parsing:

Th: The label at the beginning indicates that it is rendered by Thymeleaf. Th :if indicates a decision. Th: Action indicates the URL submission path. For more information about Thymeleaf, visit www.thymeleaf.org/

Create a new Controller package and add the RegisterController class to it:

@Controller
public class RegisterController {
    @Resource
    private SysUserMapper sysUserMapper;

    @RequestMapping("/register")
    public String register(a) {
        return "register";
    }

    @RequestMapping("/register-error")
    public String registerError(Model model) {
        // The Model is used to thread data to the Web page
        // model adds an error argument that displays the following line of HTML code if this argument is true
        If ="${error}" class="error"> 

model.addAttribute("error".true); return "register"; } @RequestMapping("/register-save") public String registerSave(@ModelAttribute SysUser sysUser, Model model) { // Check that the username password cannot be empty if (sysUser.getUsername() == null || sysUser.getPassword() == null || sysUser.getUserRole() == null) { model.addAttribute("error".true); return "register"; } try { // Password encrypted storage BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); String password = bCryptPasswordEncoder.encode(sysUser.getPassword()); sysUser.setPassword(password); // Write to the database sysUserMapper.insert(sysUser); // Redirect to the login page return "redirect:/login"; } catch (Exception e) { // Registration error model.addAttribute("error".true); return "register"; }}}Copy the code

Code parsing:

The @Controller annotation returns an HTML page. Return “register” : returns register.html. Return “redirect:/ XXX “Redirects to a page.

@modelAttribute SysUser SysUser: Similar to @requestBody, reads data from the form and assigns values to SysUser.

Run the project, through the browser to http://localhost:8080/register, enter the user name, password, select the user role, click on the register.

Egd =file:/dev/./urandom

2.4) Realize the user login function

Create login. HTML, and index.html (in section 2.6).


      
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>The login</title>
</head>
<body>
<p>The login</p>
<p th:if="${error}" class="error">The user name or password is incorrect</p>
<form th:action="@{/login}" method="post">
    <label for="username">The user name</label>:
    <input type="text" id="username" name="username" autofocus="autofocus"/> <br/>
    <label for="password">The user password</label>:
    <input type="password" id="password" name="password"/> <br/>
    <input type="submit" value="Login"/>
</form>
</body>
</html>
Copy the code

Create a new LoginController class under the Controller package:

@Controller
public class LoginController {

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

    @RequestMapping("/login-error")
    public String loginError(Model model) {
        // Login error
        model.addAttribute("error".true);
        return "login"; }}Copy the code

Because we have specified the role of the /login and /login-error paths, the specific implementation of login and login failure is implemented by Spring Security.

Run the project, through the browser to http://localhost:8080/login, enter the user name, password, select the user role, click log in.

2.5) User role permission control

User role permissions can be controlled through @preauthorize annotation in @requestMapping to indicate that the URL requires certain role permissions to access. There is also a Thymeleaf implementation that allows certain elements of the page to be accessed only by specifying role permissions.

Here is the first way to introduce @preauthorize annotations:

Create AdminController under controller package. Admin.html for AdminController:

@Controller
public class AdminController {
    // ROLE_ADMIN is required to access /admin
    // This is why MyUserDetailsServiceImpl requires "ROLE_" + sysuser.getUserRole ()
    @PreAuthorize("hasRole('ADMIN')")
    @RequestMapping("/admin")
    public String admin(a) {
        return "admin"; }}Copy the code

The USER to run the project, the use of different roles, the ADMIN login, visit http://localhost:8080/admin. View the running results of different role permissions.

2.6) Thymeleaf role control

For the same page, some elements may be visible to ADMIN and some may be visible to USER. This is achieved through the Thymeleaf extension Thymeleaf-Extras-SpringSecurity.

Modify the index.html page:


      
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
    <meta charset="UTF-8">
    <title>The home page</title>
</head>
<body>
<h1>Login successful</h1>
<div sec:authorize="isAuthenticated()">All logged in users can be seen</div>
<br/>
<div sec:authorize="hasRole('ROLE_ADMIN')">The administrator can see it<br/>
    <a href="admin.html" th:href="@{/admin}">Management page</a>
</div>
<br/>
<div sec:authorize="hasRole('ROLE_USER')">Only the user can see it</div>
<br/>
<br/>
<form th:action="@{/logout}" method="post">
    <input type="submit" value="Log out"/>
</form>
</body>
</html>
Copy the code

The appendix

Maven project dependencies

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.2</version>
  </dependency>
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <scope>test</scope>
  </dependency>
  <! -- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity5 -->
  <dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
    <version>3.0.4. RELEASE</version>
  </dependency>
</dependencies>
Copy the code

This section focuses on the implementation of Spring Boot integration with Spring Security to achieve user registration, login and role control. In the next section, the actual Spring Boot is integrated with JJWT RESTful API interface for Token authentication and authorization.