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.