2024.5.17 Friday
Following the article【WEEK12】 【DAY4】Building Spring Security Environment Part One【English Version】
Contents
14.5. Logging Out and Access Control
14.5.1. Adding Functionality to Redirect to Main Page upon Logout
Modifying SecurityConfig.java
package com.P34.config;
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;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
// Implement interceptor functionality using AOP
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//alt+insert->override methods
//Method chaining
//Authorization
@Override
protected void configure(HttpSecurity http) throws Exception {
// Everyone can access the home page, functional pages are only open to authorized users
// Request authorization rules
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
// Redirect to login page if not authorized, can check source code of formLogin for more details
http.formLogin();
// Enable logout functionality, redirect to home page after logout
http.logout().logoutSuccessUrl("/");
}
// public WebSecurityCustomizer webSecurityCustomizer() - New method
//Authentication
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// Syntax example visible by opening WebSecurityConfigurerAdapter at line 194:
/**
* protected void configure(AuthenticationManagerBuilder auth) {
* auth
* // enable in memory based authentication with a user named
* // "user" and "admin"
* .inMemoryAuthentication().withUser("user").password("password").roles("USER").and()
* .withUser("admin").password("password").roles("USER", "ADMIN");
* }
*/
// These data should theoretically be retrieved from the database
// Running it shows "There was an unexpected error (type=Internal Server Error, status=500)".
// The password encoding is missing (There is no PasswordEncoder mapped for the id “null”), so we need to encrypt the "password" in the code
// Many encryption methods have been added in Spring Security 5.0+: click BCryptPasswordEncoder->PasswordEncoder->click the green "I" on the left sidebar (see image below)
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("Zzz").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
.and()
.withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip3");
}
}
Restart and login with any user to verify logout functionality.
Upon logout, redirected to:
Regardless of which level option is clicked, the user is prompted to log in (redirected to the login page).
14.5.2. Modifying the Display Effect After Login
14.5.2.1. Adding thymeleaf-security Dependency in pom.xml
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
Version selection:
- Integration Package 4 (springsecurity4) — for Spring Boot version 2.0.9
- Integration Package 5 (springsecurity5) — for Spring Boot version after 2.0.9
14.5.2.2. Modifying index.html
Importing namespaces and adding conditional statements to determine the display of the top navigation bar based on login status. Adding functionality to display the username and its authorities, and displaying corresponding accessible pages based on permissions.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<!-- The URL on the previous line is just for reference -->
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>Home</title>
<!--semantic-ui-->
<link href="https://cdn.bootcss.com/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet">
<link th:href="@{/Zzz/css/qinstyle.css}" rel="stylesheet">
</head>
<body>
<!-- Main Container -->
<div class="ui container">
<div class="ui segment" id="index-header-nav" th:fragment="nav-menu">
<div class="ui secondary menu">
<a class="item" th:href="@{/index}">Home</a>
<!-- Login/logout -->
<div class="right menu">
<!-- If not logged in -->
<div sec:authorize="!isAuthenticated()"><!-- Check if logged in -->
<a class="item" th:href="@{/toLogin}">
<i class="address card icon"></i> Login
</a>
</div>
<!-- If logged in -->
<div sec:authorize="isAuthenticated()">
<a class="item">
<i class="address card icon"></i>
Username: <span sec:authentication="principal.username"></span>
Authorities: <span sec:authentication="principal.authorities"></span>
<!-- The syntax for 'principal' here is different from thymeleaf-extras-springsecurity4 version -->
</a>
</div>
<div sec:authorize="isAuthenticated()">
<a class="item" th:href="@{/logout}">
<i class="sign-out icon"></i> Logout
</a>
</div>
</div>
</div>
</div>
<div class="ui segment" style="text-align: center">
<h3>Spring Security Study</h3>
</div>
<div>
<br>
<div class="ui three column stackable grid">
<!-- Menu implemented dynamically based on user roles -->
<div class="column" sec:authorize="hasRole('vip1')"><!-- If has vip1 permission, display level1 content on the homepage, otherwise, don't display -->
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 1</h5>
<hr>
<div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
<div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
<div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
</div>
</div>
</div>
</div>
<div class="column" sec:authorize="hasRole('vip2')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 2</h5>
<hr>
<div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
<div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
<div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
</div>
</div>
</div>
</div>
<div class="column" sec:authorize="hasRole('vip3')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 3</h5>
<hr>
<div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
<div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
<div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Importing JS libraries -->
<script th:src="@{/Zzz/js/jquery-3.1.1.min.js}"></script>
<script th:src="@{/Zzz/js/semantic.min.js}"></script>
</body>
</html>
14.5.2.3. Restart
Re-login after logout:
14.6. “Remember Me” Functionality
14.6.1. Modifying SecurityConfig.java
package com.P34.config;
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;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
// Using AOP concept to implement interceptor functionality
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// alt+insert->override methods
// Chained programming
// Authorization
@Override
protected void configure(HttpSecurity http) throws Exception {
// Everyone can access the homepage, functional pages are only open to users with permissions
// Request authorization rules
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
// If no permission, default to the login page, can open formLogin to view source code
http.formLogin();
// Enable logout function, redirect to the homepage after logout
http.logout().logoutSuccessUrl("/");
// Enable "Remember Me" function (cookie implementation), default duration: 2 weeks
http.rememberMe();
}
// Authentication
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// Clicking on line 194 of WebSecurityConfigurerAdapter reveals syntax examples:
/**
* protected void configure(AuthenticationManagerBuilder auth) {
* auth
* // enable in memory based authentication with a user named
* // "user" and "admin"
* .inMemoryAuthentication().withUser("user").password("password").roles("USER").and()
* .withUser("admin").password("password").roles("USER", "ADMIN");
* }
*/
// These data should theoretically be read from the database
// Running it will display an error (type=Internal Server Error, status=500).
// It's because the password encoding is not encrypted (There is no PasswordEncoder mapped for the id “null”), so the password in the code needs to be encrypted
// In Spring Security 5.0+, many encryption methods have been added: you can click BCryptPasswordEncoder->PasswordEncoder->click on the green "I" in the left sidebar (as shown below)
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("Zzz").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
.and()
.withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip3");
}
}
14.6.2. Restart
Choose any user to log in, and check “Remember me on this computer.”
Now, open a new web page, access http://localhost:8080/, and the login status is already present.
Inspect using F12:
After clearing cookies and refreshing the page, return to the login page.
14.7. Customizing the Login Page
14.7.1. Modifying SecurityConfig.java
package com.P34.config;
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;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
// Using AOP concept to implement interceptor functionality
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// alt+insert->override methods
// Chained programming
// Authorization
@Override
protected void configure(HttpSecurity http) throws Exception {
// Everyone can access the homepage, functional pages are only open to users with permissions
// Request authorization rules
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
// If no permission, default to the login page, can open formLogin to view source code (line 2202: public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception)
http.formLogin().loginPage("/toLogin").usernameParameter("username1").passwordParameter("password1").loginProcessingUrl("/login");
//.usernameParameter("username1").passwordParameter("password1") These two settings are because the names used in the login.html file are not default, so they need to be reconfigured here to be able to recognize and login normally. Otherwise, the URL will become http://localhost:8080/toLogin?error and cannot login normally.
// Enable logout function, redirect to the homepage after logout
http.logout().logoutSuccessUrl("/");
// If getting a 404 error on logout, it's because it defaults to preventing CSRF cross-site request forgery, which can cause security issues. We can change the request to post form submission, or disable CSRF functionality in Spring Security by adding the next line of code to solve the problem
http.csrf().disable(); // Disable CSRF protection: Cross-Site Request Forgery, by default, only logout requests can be submitted via the post method
// Enable "Remember Me" function (cookie implementation), default duration: 2 weeks
http.rememberMe().rememberMeParameter("remember");
}
// Authentication
......
}
14.7.2. Modifying index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<!-- The above line of URL is just for reference -->
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>Homepage</title>
<!--semantic-ui-->
<link href="https://cdn.bootcss.com/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet">
<link th:href="@{/Zzz/css/qinstyle.css}" rel="stylesheet">
</head>
<body>
<!-- Main container -->
<div class="ui container">
<div class="ui segment" id="index-header-nav" th:fragment="nav-menu">
<div class="ui secondary menu">
<a class="item" th:href="@{/index}">Homepage</a>
<!-- Login and logout -->
<div class="right menu">
<!-- If not logged in -->
<div sec:authorize="!isAuthenticated()"><!-- Check if logged in -->
<a class="item" th:href="@{/toLogin}">
<i class="address card icon"></i> Login
</a>
</div>
<!-- If logged in -->
<div sec:authorize="isAuthenticated()">
<a class="item">
<i class="address card icon"></i>
Username: <span sec:authentication="principal.username"></span>
Authorization: <span sec:authentication="principal.authorities"></span>
<!-- The syntax of principal here is different from thymeleaf-extras-springsecurity4 version -->
</a>
</div>
<div sec:authorize="isAuthenticated()">
<a class="item" th:href="@{/logout}">
<i class="sign-out icon"></i> Logout
</a>
</div>
</div>
</div>
</div>
<div class="ui segment" style="text-align: center">
<h3>Spring Security Study</h3>
</div>
<div>
<br>
<div class="ui three column stackable grid">
<!-- Menu dynamically implemented based on user roles -->
<div class="column" sec:authorize="hasRole('vip1')"><!-- Show level1 content on the homepage if there is vip1 permission, otherwise do not display -->
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 1</h5>
<hr>
<div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
<div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
<div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
</div>
</div>
</div>
</div>
<div class="column" sec:authorize="hasRole('vip2')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 2</h5>
<hr>
<div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
<div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
<div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
</div>
</div>
</div>
</div>
<div class="column" sec:authorize="hasRole('vip3')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 3</h5>
<hr>
<div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
<div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
<div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script th:src="@{/Zzz/js/jquery-3.1.1.min.js}"></script>
<script th:src="@{/Zzz/js/semantic.min.js}"></script>
</body>
</html>
14.7.3. Modifying login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>Login</title>
<!--semantic-ui-->
<link href="https://cdn.bootcss.com/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet">
</head>
<body>
<!-- Main container -->
<div class="ui container">
<div class="ui segment">
<div style="text-align: center">
<h1 class="header">Login</h1>
</div>
<div class="ui placeholder segment">
<div class="ui column very relaxed stackable grid">
<div class="column">
<div class="ui form">
<form th:action="@{/login}" method="post">
<div class="field">
<label>Username</label>
<div class="ui left icon input">
<input type="text" placeholder="Username" name="username1">
<!-- The reason for naming it username and password is because the annotation in HttpSecurity.java is default is username and default is password -->
<!-- If you want to use custom names (changed here to username1 and password1), you need to modify SecurityConfig -->
<i class="user icon"></i>
</div>
</div>
<div class="field">
<label>Password</label>
<div class="ui left icon input">
<input type="password" name="password1">
<i class="lock icon"></i>
</div>
</div>
<div class="field"><!-- Centered -->
<input type="checkbox" name="remember"> Remember me
</div>
<input type="submit" class="ui blue submit button"/>
</form>
</div>
</div>
</div>
</div>
<div style="text-align: center">
<div class="ui label">
</i>Register
</div>
<br><br>
<small>https://blog.csdn.net/2401_83329143</small>
</div>
<div class="ui segment" style="text-align: center">
<h3>Spring Security Study</h3>
</div>
</div>
</div>
<script th:src="@{/Zzz/js/jquery-3.1.1.min.js}"></script>
<script th:src="@{/Zzz/js/semantic.min.js}"></script>
</body>
</html>
14.7.4. Restart
At this point, you cannot access the login page by visiting http://localhost:8080/login
You can only access the login page by clicking “Login” in the top right corner of the homepage
After clicking “Logout” in the top right corner, you will return to the login page.