在本教程中,我将指导您如何编写代码,以使用具有基于表单的身份验证的Spring安全API来保护Spring Boot应用程序中的网页。用户详细信息存储在MySQL数据库中,并使用春季JDBC连接到数据库。我们将从本教程中的 ProductManager 项目开始,向现有的弹簧启动项目添加登录和注销功能。

1. 创建用户表和虚拟凭据

凭据应存储在数据库中,因此让我们创建新表,表间关系ER图如下:

-- --------------------------------------------------------
-- 主机:                           127.0.0.1
-- 服务器版本:                        8.0.22 - MySQL Community Server - GPL
-- 服务器操作系统:                      Win64
-- HeidiSQL 版本:                  12.1.0.6537
-- --------------------------------------------------------/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET NAMES utf8 */;
/*!50503 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;-- 导出 product3 的数据库结构
CREATE DATABASE IF NOT EXISTS `product3` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;
USE `product3`;-- 导出  表 product3.permission 结构
CREATE TABLE IF NOT EXISTS `permission` (`id` int NOT NULL AUTO_INCREMENT,`name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,`uri` varchar(8192) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,`method` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,PRIMARY KEY (`id`),UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;-- 正在导出表  product3.permission 的数据:~5 rows (大约)
INSERT INTO `permission` (`id`, `name`, `description`, `uri`, `method`) VALUES(3, 'product_create', '增加产品', '/new', 'GET'),(4, 'product_delete', '删除产品', '/delete/*', 'GET'),(5, 'product_save', '保存产品', '/save', 'POST'),(9, 'product_read', '读取产品', '/', 'GET'),(10, 'product_edit', '编辑产品', '/edit/*', 'GET');-- 导出  表 product3.product 结构
CREATE TABLE IF NOT EXISTS `product` (`id` bigint NOT NULL AUTO_INCREMENT,`brand` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,`madein` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,`name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,`price` float NOT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;-- 正在导出表  product3.product 的数据:~2 rows (大约)
INSERT INTO `product` (`id`, `brand`, `madein`, `name`, `price`) VALUES(6, '6', '6', '6', 6),(7, '7', '7', '7', 7);-- 导出  表 product3.role 结构
CREATE TABLE IF NOT EXISTS `role` (`id` int NOT NULL AUTO_INCREMENT,`name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,PRIMARY KEY (`id`),UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;-- 正在导出表  product3.role 的数据:~3 rows (大约)
INSERT INTO `role` (`id`, `name`, `description`) VALUES(1, 'ADMIN', 'Administrator role'),(2, 'USER_P1', 'Perfil 1'),(3, 'USER_P2', 'Perfil 2');-- 导出  表 product3.role_permission 结构
CREATE TABLE IF NOT EXISTS `role_permission` (`id` int NOT NULL AUTO_INCREMENT,`role_id` int NOT NULL DEFAULT '0',`permission_id` int NOT NULL DEFAULT '0',PRIMARY KEY (`id`),UNIQUE KEY `role_id_permission_id` (`role_id`,`permission_id`),KEY `FK_role_permission_permission` (`permission_id`),CONSTRAINT `FK_role_permission_permission` FOREIGN KEY (`permission_id`) REFERENCES `permission` (`id`),CONSTRAINT `FK_role_permission_role` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;-- 正在导出表  product3.role_permission 的数据:~10 rows (大约)
INSERT INTO `role_permission` (`id`, `role_id`, `permission_id`) VALUES(1, 1, 3),(2, 1, 4),(3, 1, 5),(4, 1, 9),(5, 1, 10),(8, 2, 5),(6, 2, 9),(7, 2, 10),(10, 3, 4),(9, 3, 9);-- 导出  表 product3.urls 结构
CREATE TABLE IF NOT EXISTS `urls` (`name` varchar(500) COLLATE utf8mb4_unicode_ci NOT NULL,`description` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL,PRIMARY KEY (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;-- 正在导出表  product3.urls 的数据:~0 rows (大约)-- 导出  表 product3.user 结构
CREATE TABLE IF NOT EXISTS `user` (`id` int NOT NULL AUTO_INCREMENT,`username` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,`email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,`name` varchar(65) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,`enabled` int DEFAULT NULL,PRIMARY KEY (`id`),UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;-- 正在导出表  product3.user 的数据:~3 rows (大约)
INSERT INTO `user` (`id`, `username`, `email`, `name`, `password`, `enabled`) VALUES(1, 'admin', 'admin@example.com', 'Administrator', '$2a$10$2/LSmp3YoEOT97KzgrYODen7I88ErBovM2Qehw9DL1dW9DZ7DZSAm', 1),(2, 'u1', 'u1@example.com', 'User P1', '$2a$10$2/LSmp3YoEOT97KzgrYODen7I88ErBovM2Qehw9DL1dW9DZ7DZSAm', 1),(3, 'u2', 'u2@example.com', 'User P2', '$2a$10$2/LSmp3YoEOT97KzgrYODen7I88ErBovM2Qehw9DL1dW9DZ7DZSAm', 1);-- 导出  表 product3.user_role 结构
CREATE TABLE IF NOT EXISTS `user_role` (`id` int NOT NULL AUTO_INCREMENT,`user_id` int NOT NULL DEFAULT '0',`role_id` int NOT NULL DEFAULT '0',PRIMARY KEY (`id`),UNIQUE KEY `user_id_role_id` (`user_id`,`role_id`),KEY `FK_user_role_role` (`role_id`),CONSTRAINT `FK_user_role_role` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`),CONSTRAINT `FK_user_role_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;-- 正在导出表  product3.user_role 的数据:~3 rows (大约)
INSERT INTO `user_role` (`id`, `user_id`, `role_id`) VALUES(1, 1, 1),(2, 2, 2),(3, 3, 3);/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */;
/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */;

2. 配置数据源属性

接下来,在应用程序属性文件中指定数据库连接信息,如下所示:根据您的MySQL数据库更新URL,用户名和密码。

spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:3306/product3?autoReconnect=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
#logging.level.root=WARN

3. 声明弹簧安全性和 MySQL JDBC 驱动程序的依赖关系

要将Spring安全API用于项目,请在pom.xml文件中声明以下依赖项:并且要将JDBC与弹簧启动和MySQL一起使用:请注意,依赖项版本已由弹簧启动初学者父项目定义。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.4</version><relativePath /> <!-- lookup parent from repository --></parent><groupId>net.codejava</groupId><artifactId>ProductManagerJDBCAuthenticationManuallyAuthenticateCaptchaAccess</artifactId><version>2.0</version><name>ProductManagerJDBCAuthenticationManuallyAuthenticateCaptchaAccess</name><description>ProductManagerJDBCAuthentication</description><packaging>jar</packaging><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</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.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.thymeleaf.extras</groupId><artifactId>thymeleaf-extras-springsecurity5</artifactId></dependency><dependency><groupId>com.github.penggle</groupId><artifactId>kaptcha</artifactId><version>2.3.2</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.24</version><scope>provided</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

4. 配置 JDBC 身份验证详细信息

要将 Spring 安全性与基于表单的身份验证和 JDBC 结合使用,请按如下方式创建 WebSecurityConfig 类:

package com.example;import javax.sql.DataSource;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
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;@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate DataSource dataSource;@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Autowiredpublic void configAuthentication(AuthenticationManagerBuilder authBuilder) throws Exception {authBuilder.jdbcAuthentication().dataSource(dataSource).passwordEncoder(new BCryptPasswordEncoder()).usersByUsernameQuery("select username, password, enabled from user where username=?").authoritiesByUsernameQuery("SELECT user.username,permission.name FROM user,role,user_role,permission,role_permission WHERE user.id=user_role.user_id AND role.id=user_role.role_id AND role.id=role_permission.role_id AND permission.id=role_permission.permission_id  AND user.username=?");}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/common/**").permitAll().antMatchers("/login").permitAll().antMatchers("/logout").permitAll().antMatchers("/verify").permitAll().anyRequest().access("@rbacService.hasPermission(request , authentication)").and().formLogin().loginPage("/login").permitAll().and().logout().permitAll().and().exceptionHandling().accessDeniedPage("/403");}}

不适用已废弃的WebSecurityConfigurerAdapter

package com.example;import javax.sql.DataSource;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;@Configuration
@EnableWebSecurity
public class WebSecurityConfig {@Autowiredprivate LoginSuccessHandler loginSuccessHandler;@Autowiredprivate CustomLoginFailureHandler loginFailureHandler;@Autowiredprivate DataSource dataSource;//    @Bean
//    @Override
//    public AuthenticationManager authenticationManagerBean() throws Exception {
//        return super.authenticationManagerBean();
//    }@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {return authenticationConfiguration.getAuthenticationManager();}@BeanBCryptPasswordEncoder bCryptPasswordEncoder() {return new BCryptPasswordEncoder();}@Autowiredpublic void configAuthentication(AuthenticationManagerBuilder authBuilder) throws Exception {authBuilder.jdbcAuthentication().dataSource(dataSource).passwordEncoder(new BCryptPasswordEncoder()).usersByUsernameQuery("select username, password, enabled from user where username=?").authoritiesByUsernameQuery("SELECT user.username,permission.name FROM user,role,user_role,permission,role_permission WHERE user.id=user_role.user_id AND role.id=user_role.role_id AND role.id=role_permission.role_id AND permission.id=role_permission.permission_id  AND user.username=?");}//    protected void configure(HttpSecurity http) throws Exception {
//        http.authorizeRequests()
//                .antMatchers("/webjars/**").permitAll()
//                .antMatchers("/common/**").permitAll()
//                .antMatchers("/login").permitAll()
//                .antMatchers("/logout").permitAll()
//                .antMatchers("/verify").permitAll()
//                .antMatchers("/home").authenticated()
//                .antMatchers("/user/info").authenticated()
//                .antMatchers("/change/password").authenticated()
//                .antMatchers("/new/password").authenticated()
//                .anyRequest()
//                .access("@rbacService.hasPermission(request , authentication)")
//                .and()
//                .formLogin().loginPage("/login")
//                .successHandler(loginSuccessHandler)
//                .failureHandler(loginFailureHandler)
//                .permitAll()
//                .and()
//                .logout().permitAll()
//                .and()
//                .exceptionHandling().accessDeniedPage("/403");
//    }@Beanprotected SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/webjars/**").permitAll().antMatchers("/common/**").permitAll().antMatchers("/login").permitAll().antMatchers("/logout").permitAll().antMatchers("/verify").permitAll().antMatchers("/home").authenticated().antMatchers("/user/info").authenticated().antMatchers("/change/password").authenticated().antMatchers("/new/password").authenticated().anyRequest().access("@rbacService.hasPermission(request , authentication)").and().formLogin().loginPage("/login").successHandler(loginSuccessHandler).failureHandler(loginFailureHandler).permitAll().and().logout().permitAll().and().exceptionHandling().accessDeniedPage("/403");return http.build();}}

此安全配置类必须使用@EnableWebSecurity注释进行批注,并且是 Web 安全配置器适配器的子类。数据源对象的实例将由Spring框架创建并注入:

    @Autowiredprivate DataSource dataSource;

它将从应用程序属性文件中读取数据库连接信息。要使用JDBC配置身份验证,请编写以下方法:

    @Autowiredpublic void configAuthentication(AuthenticationManagerBuilder authBuilder) throws Exception {authBuilder.jdbcAuthentication().dataSource(dataSource).passwordEncoder(new BCryptPasswordEncoder()).usersByUsernameQuery("select username, password, enabled from users where username=?").authoritiesByUsernameQuery("SELECT users.username,permissions.name FROM users,roles,users_roles,permissions,roles_permissions WHERE users.username=users_roles.username AND roles.name=users_roles.role_name AND roles.name=roles_permissions.role_name AND permissions.name=roles_permissions.permission AND users.username=?");}

如您所见,我们需要指定密码编码器(建议使用BCrypt),数据源和两个SQL语句:第一个根据用户名选择用户,第二个选择用户的角色。请注意,Spring安全性要求列名必须是用户名,密码,启用和角色。为了配置基于表单的身份验证,我们重写了 configure(HttpSecurity) 方法,如下所示:

    @Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/edit/*", "/delete/*").hasAnyAuthority("ADMIN").anyRequest().authenticated().and().formLogin().permitAll().and().logout().permitAll().and().exceptionHandling().accessDeniedPage("/403");}

在这里,我们指定所有请求都必须进行身份验证,这意味着用户必须登录才能使用该应用程序。使用Spring安全性提供的默认登录表单。要显示已登录用户的用户名,请在Thymeleaf模板文件中编写以下代码:

<div sec:authorize="isAuthenticated()">Welcome <b><span sec:authentication="name">Username</span></b>&nbsp;<i><span sec:authentication="principal.authorities">Roles</span></i></div>

并添加注销按钮:

            <form th:action="@{/logout}" method="post"><input type="submit" value="Logout" /></form>

如您所见,Spring Security将处理应用程序的登录和注销。我们不必编写重复的代码,只需指定一些配置即可。

5.自定义登录验证过程

package com.example;import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpSession;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import static org.springframework.security.web.context.HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY;
import org.springframework.ui.Model;
import org.springframework.web.servlet.ModelAndView;@Controllerpublic class LoginController {private static final long PASSWORD_EXPIRATION_TIME = 30L * 24L * 60L * 60L * 1000L;    // 30 days@Autowiredprivate UserService userService;@Autowiredprivate UserRepository userRepository;/*** 注入身份认证管理器*/@Autowiredprivate AuthenticationManager authenticationManager;@GetMapping("/login")public String login() {return "login";}@PostMapping(value = "/verify")public String login(@RequestParam("username") String username,@RequestParam("password") String password,@RequestParam("verifyCode") String verifyCode,HttpSession session) {System.out.println("username is:" + username);System.out.println("password is:" + password);System.out.println("verifyCode is:" + verifyCode);if (StringUtils.isEmpty(verifyCode)) {session.setAttribute("errorMsg", "The verification code cannot be empty");return "login";}if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {session.setAttribute("errorMsg", "User name or password cannot be empty");return "login";}String kaptchaCode = session.getAttribute("verifyCode") + "";System.out.println("kaptchaCode is:" + kaptchaCode);if (StringUtils.isEmpty(kaptchaCode) || !verifyCode.equals(kaptchaCode)) {session.setAttribute("errorMsg", "Verification code error");return "login";}
//        User user = userService.login(userName, password);System.out.println(username + "==" + password + "==" + verifyCode);User userCheck = userRepository.getByUsername(username);if (userCheck != null) {if (!userCheck.isAccountNonLocked()) {if (userService.unlockWhenTimeExpired(userCheck)) {System.out.println("Your account has been unlocked. Please try to login again.");}} else {// 创建用户名与密码认证对象UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);try {// 调用认证方法,返回认证对象Authentication authenticate = authenticationManager.authenticate(token);// 判断是否认证成功if (authenticate.isAuthenticated()) {// 设置用户认证成功,往Session中添加认证通过信息SecurityContextHolder.getContext().setAuthentication(authenticate);SecurityContext sc = SecurityContextHolder.getContext();sc.setAuthentication(authenticate);session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, sc);// 重定向到登录成功页面System.out.println(authenticate.getAuthorities());Object principal = authenticate.getPrincipal();System.out.println(principal.getClass());//判断数据是否为空 以及类型是否正确if (null != principal && principal instanceof org.springframework.security.core.userdetails.User) {
//                    String username = ((org.springframework.security.core.userdetails.User) principal).getUsername();
//                    System.out.println(username);User user = userRepository.getByUsername(username);if (user.getFailedAttempt() > 0) {userService.resetFailedAttempts(user.getUsername());}if (user.getPasswordChangedTime() == null) {return "redirect:/change/password";}long currentTime = System.currentTimeMillis();long lastChangedTime = user.getPasswordChangedTime().getTime();if (currentTime > lastChangedTime + PASSWORD_EXPIRATION_TIME) {return "redirect:/change/password";}String homepage = "redirect:/" + user.getHomepage();return homepage;}return "redirect:/";} else {User user = userService.findUserByUsername(username);if (user != null) {if (user.getEnabled() == 1 && user.isAccountNonLocked()) {if (user.getFailedAttempt() < UserService.MAX_FAILED_ATTEMPTS - 1) {userService.increaseFailedAttempts(user);} else {userService.lock(user);System.out.println("Your account has been locked due to 3 failed attempts."+ " It will be unlocked after 24 hours.");}} else if (!user.isAccountNonLocked()) {if (userService.unlockWhenTimeExpired(user)) {System.out.println("Your account has been unlocked. Please try to login again.");}}}session.setAttribute("errorMsg", "Login failed");return "login";}} catch (BadCredentialsException ex) {System.out.println("BadCredentialsException");User user = userService.findUserByUsername(username);if (user != null) {if (user.getEnabled() == 1 && user.isAccountNonLocked()) {System.out.println("user.getEnabled() == 1");if (user.getFailedAttempt() < UserService.MAX_FAILED_ATTEMPTS - 1) {userService.increaseFailedAttempts(user);} else {userService.lock(user);System.out.println("Your account has been locked due to 3 failed attempts."+ " It will be unlocked after 24 hours.");}} else if (!user.isAccountNonLocked()) {if (userService.unlockWhenTimeExpired(user)) {System.out.println("Your account has been unlocked. Please try to login again.");}}}
//            ex.printStackTrace();} catch (Exception ex) {ex.printStackTrace();}}}return "login";}@GetMapping("/register")public String showRegistrationForm(Model model) {model.addAttribute("user", new User());return "signup_form";}@PostMapping("/process_register")public String processRegister(User user) {BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();String encodedPassword = passwordEncoder.encode(user.getPassword());user.setPassword(encodedPassword);userRepository.save(user);return "register_success";}@GetMapping("/users")public String listUsers(Model model) {List<User> listUsers = userRepository.findAll();model.addAttribute("listUsers", listUsers);return "users";}@GetMapping("/users/edit/{id}")public String editUser(@PathVariable("id") Integer id, Model model) {User user = userService.get(id);List<Role> listRoles = userService.listRoles();model.addAttribute("user", user);model.addAttribute("listRoles", listRoles);return "user_form";}@PostMapping("/users/save")public String saveUser(User user) {userService.save(user);return "redirect:/users";}@RequestMapping(value = "/home", method = RequestMethod.GET)public ModelAndView home() {ModelAndView modelAndView = new ModelAndView();Authentication auth = SecurityContextHolder.getContext().getAuthentication();User user = userService.findUserByUsername(auth.getName());String name = SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString();System.out.println(name);modelAndView.addObject("userName","Welcome " + user.getUsername());modelAndView.addObject("adminMessage", "Content Available Only for Users with Admin Role");modelAndView.setViewName("home");return modelAndView;}
}

kaptcha验证码

package net.codejava;import com.google.code.kaptcha.impl.DefaultKaptcha;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;@Controller
public class KaptchaController {@Autowiredprivate DefaultKaptcha captchaProducer;@GetMapping("/common/kaptcha")public void defaultKaptcha(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {byte[] captchaOutputStream = null;ByteArrayOutputStream imgOutputStream = new ByteArrayOutputStream();try {//Produce the verification code string and save it in the sessionString verifyCode = captchaProducer.createText();httpServletRequest.getSession().setAttribute("verifyCode", verifyCode);BufferedImage challenge = captchaProducer.createImage(verifyCode);ImageIO.write(challenge, "jpg", imgOutputStream);} catch (IllegalArgumentException e) {httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND);return;}captchaOutputStream = imgOutputStream.toByteArray();httpServletResponse.setHeader("Cache-Control", "no-store");httpServletResponse.setHeader("Pragma", "no-cache");httpServletResponse.setDateHeader("Expires", 0);httpServletResponse.setContentType("image/jpeg");ServletOutputStream responseOutputStream = httpServletResponse.getOutputStream();responseOutputStream.write(captchaOutputStream);responseOutputStream.flush();responseOutputStream.close();}}
package net.codejava;import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;import java.util.Properties;@Component
public class KaptchaConfig {@Beanpublic DefaultKaptcha getDefaultKaptcha() {DefaultKaptcha defaultKaptcha = new DefaultKaptcha();Properties properties = new Properties();properties.put("kaptcha.border", "no");properties.put("kaptcha.textproducer.font.color", "black");properties.put("kaptcha.image.width", "150");properties.put("kaptcha.image.height", "40");properties.put("kaptcha.textproducer.font.size", "30");properties.put("kaptcha.session.key", "verifyCode");properties.put("kaptcha.textproducer.char.space", "5");Config config = new Config(properties);defaultKaptcha.setConfig(config);return defaultKaptcha;}}

6.登录页面

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Bootstrap 5 Sign In Form with Image Example</title><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous"><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"crossorigin="anonymous"></script><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css"></head><body><form th:action="@{/verify}" method="post"><div class="container-fluid vh-100" style="margin-top:50px"><div class="" style="margin-top:50px"><div class="rounded d-flex justify-content-center"><div class=" col-md-4 col-sm-12 shadow-lg p-5 bg-light"><div class="text-center"><h3 class="text-primary">请登录</h3></div><div class="p-4"><div class="input-group mb-3"><span class="input-group-text bg-secondary"><iclass="bi bi-person-fill text-white"></i></span><input id="username" type="text" name="username" required class="form-control" placeholder="用户名"></div><div class="input-group mb-3"><span class="input-group-text bg-secondary"><iclass="bi bi-key-fill text-white"></i></span><input  id="password" type="password" name="password" required class="form-control" placeholder="密码"></div><div class="input-group mb-3"><span class="input-group-text bg-secondary"><iclass="bi bi-lock-fill text-white"></i></span><input type="text" name="verifyCode"  class="form-control" placeholder="输入下图中的校验码"></div><div class="input-group mb-3"><span class="input-group-text bg-secondary"><iclass="bi bi-image-fill text-white"></i></span><img alt="Click the picture to refresh!" class="pointer" th:src="@{/common/kaptcha}"onclick="this.src = '/common/kaptcha?d=' + new Date() * 1"></div><div class="col-12"><button type="submit" class="btn btn-primary px-4 float-end mt-4">登录</button></div></div></div></div></div></div></body></html>

5. 测试登录和注销

启动Spring Boot应用程序并访问 http://localhost:8080 在Web浏览器中,您将看到自定义的登录页面出现:

现在输入正确的用户名admin和密码admin,您将看到主页如下:

并注意欢迎消息后跟用户名。用户现在已通过身份验证以使用该应用程序。单击“注销”按钮,您将看到自定义的登录页面出现,这意味着我们已成功实现登录并注销到我们的Spring Boot应用程序。

自定义从数据库中获取动态权限验证

package com.example;import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.Data;@Data
@Entity
public class Permission {@Idprivate Long id;private String name;private String description;private String uri;private String method;}
package com.example;import org.springframework.data.jpa.repository.JpaRepository;public interface PermissionRepository extends JpaRepository<Permission, Long> {public Permission findByName(String name);}
package com.example;import javax.servlet.http.HttpServletRequest;
import org.springframework.security.core.Authentication;/*** RBAC模型实现Security,即通过角色对用户进行分组,在对每个角色进行权限授权就, 进而简化用户权限分配以及管理。 需要的表: user:* 用户信息表 保存有的用户id,用户名、密码、账号、状态、salt加盐 role:角色表 user_role:用户角色关联表 permission: 权限表* 保存的有权限相关信息 role_permission: 角色与权限管理表*/
public interface RbacService {//用户判断当前请求是否有操作权限boolean hasPermission(HttpServletRequest request, Authentication authentication);
}
package com.example;import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;@Component("rbacService")
public class RbacServiceImpl implements RbacService {@Autowiredprivate PermissionRepository permissionRepository;private AntPathMatcher antPathMatcher = new AntPathMatcher();@Overridepublic boolean hasPermission(HttpServletRequest request, Authentication authentication) {//获取用户认证信息System.out.println(authentication.getAuthorities());Object principal = authentication.getPrincipal();System.out.println(principal.getClass());//判断数据是否为空 以及类型是否正确if (null != principal && principal instanceof User) {String username = ((User) principal).getUsername();System.out.println(username);}String requestURI = request.getRequestURI();System.out.println(requestURI);String method = request.getMethod();System.out.println(method);Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();boolean hasPermission = false;for (GrantedAuthority authority : authorities) {String authorityname = authority.getAuthority();System.out.println(authority.getAuthority());Permission permission = permissionRepository.findByName(authorityname);System.out.println(permissionRepository.findByName(authorityname));if (null != permission && permission.getMethod().equals(request.getMethod()) && antPathMatcher.match(permission.getUri(), request.getRequestURI())) {hasPermission = true;break;}}System.out.println(hasPermission);return hasPermission;}
}

thymeleaf视图文件中,根据权限显示连接菜单

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity5"><head><meta charset="ISO-8859-1"><title>Product Manager</title></head><body><div align="center"><div sec:authorize="isAuthenticated()">Welcome <b><span sec:authentication="name">Username</span></b>&nbsp;<i><span sec:authentication="principal.authorities">Roles</span></i></div><form th:action="@{/logout}" method="post"><input type="submit" value="Logout" /></form><h1>Product Manager</h1><div sec:authorize="hasAnyAuthority('product_create')"><a href="/new">Create New Product</a></div><br/><br/><table border="1" cellpadding="10"><thead><tr><th>Product ID</th><th>Name</th><th>Brand</th><th>Made In</th><th>Price</th><th sec:authorize="hasAnyAuthority('product_edit', 'product_delete')">Actions</th></tr></thead><tbody><tr th:each="product : ${listProducts}"><td th:text="${product.id}">Product ID</td><td th:text="${product.name}">Name</td><td th:text="${product.brand}">Brand</td><td th:text="${product.madein}">Made in</td><td th:text="${product.price}">Price</td><td><a sec:authorize="hasAuthority('product_edit')" th:href="@{'/edit/' + ${product.id}}">Edit</a>&nbsp;&nbsp;&nbsp;&nbsp;<a sec:authorize="hasAuthority('product_delete')" th:href="@{'/delete/' + ${product.id}}">Delete</a></td></tr></tbody></table></div></body>
</html>

Thymeleaf集成Bootstrap 5 Free Admin Dashboard Template 1 - freeetemplates

header.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"xmlns:th="http://www.thymeleaf.org"  xmlns:sec="http://www.thymeleaf.org"><head><meta charset="utf-8"><meta content="width=device-width, initial-scale=1.0" name="viewport"><title>Dashboard - Admin Bootstrap Template</title><meta name="robots" content="noindex, nofollow"><meta content="" name="description"><meta content="" name="keywords"><link href="assets/img/favicon.png" rel="icon"><link href="assets/img/apple-touch-icon.png" rel="apple-touch-icon"><link href="https://fonts.gstatic.com" rel="preconnect"><link href="https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i|Nunito:300,300i,400,400i,600,600i,700,700i|Poppins:300,300i,400,400i,500,500i,600,600i,700,700i" rel="stylesheet"><link href="assets/css/bootstrap.min.css" rel="stylesheet"><link href="assets/css/bootstrap-icons.css" rel="stylesheet"><link href="assets/css/boxicons.min.css" rel="stylesheet"><link href="assets/css/quill.snow.css" rel="stylesheet"><link href="assets/css/quill.bubble.css" rel="stylesheet"><link href="assets/css/remixicon.css" rel="stylesheet"><link href="assets/css/simple-datatables.css" rel="stylesheet"><link href="assets/css/style.css" rel="stylesheet"></head><body><header id="header" class="header fixed-top d-flex align-items-center" th:fragment="header"><form th:action="@{/logout}" method="post" th:hidden="true" name="logoutForm"><input type="submit" value="Logout" /></form><div class="d-flex align-items-center justify-content-between"> <a href="index.html" class="logo d-flex align-items-center"> <img src="/assets/img/logo.png" alt=""> <span class="d-none d-lg-block">CRUD和RBAC系统</span> </a> <i class="bi bi-list toggle-sidebar-btn"></i></div><nav class="header-nav ms-auto"><ul class="d-flex align-items-center"><li class="nav-item d-block d-lg-none"> <a class="nav-link nav-icon search-bar-toggle " href="#"> <i class="bi bi-search"></i> </a></li><li class="nav-item dropdown pe-3"><a class="nav-link nav-profile d-flex align-items-center pe-0" href="#" data-bs-toggle="dropdown"> <img src="/assets/img/profile.png" alt="Profile" class="rounded-circle"> <span class="d-none d-md-block dropdown-toggle ps-2"><span sec:authentication="name">Username</span></span> </a><ul class="dropdown-menu dropdown-menu-end dropdown-menu-arrow profile"><li class="dropdown-header"><h6><span sec:authentication="name">Username</span></h6></li><li><hr class="dropdown-divider"></li><li> <a class="dropdown-item d-flex align-items-center" href="/user/info"> <i class="bi bi-person"></i> <span>账号信息</span> </a></li><li><hr class="dropdown-divider"></li><li> <a href="javascript: document.logoutForm.submit()" class="dropdown-item d-flex align-items-center" > <i class="bi bi-box-arrow-right"></i> <span>注销</span> </a></li></ul></li></ul></nav></header></body></html>

footer.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"xmlns:th="http://www.thymeleaf.org"  xmlns:sec="http://www.thymeleaf.org"><head><meta charset="utf-8"><meta content="width=device-width, initial-scale=1.0" name="viewport"><title>Dashboard - Admin Bootstrap Template</title><meta name="robots" content="noindex, nofollow"><meta content="" name="description"><meta content="" name="keywords"><link href="assets/img/favicon.png" rel="icon"><link href="assets/img/apple-touch-icon.png" rel="apple-touch-icon"><link href="https://fonts.gstatic.com" rel="preconnect"><link href="https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i|Nunito:300,300i,400,400i,600,600i,700,700i|Poppins:300,300i,400,400i,500,500i,600,600i,700,700i" rel="stylesheet"><link href="assets/css/bootstrap.min.css" rel="stylesheet"><link href="assets/css/bootstrap-icons.css" rel="stylesheet"><link href="assets/css/boxicons.min.css" rel="stylesheet"><link href="assets/css/quill.snow.css" rel="stylesheet"><link href="assets/css/quill.bubble.css" rel="stylesheet"><link href="assets/css/remixicon.css" rel="stylesheet"><link href="assets/css/simple-datatables.css" rel="stylesheet"><link href="assets/css/style.css" rel="stylesheet"></head><body><footer id="footer" class="footer"  th:fragment="footer"><div class="copyright"> &copy; Copyright <strong><span>CRUD和RBAC系统</span></strong>. All Rights Reserved</div><div class="credits"> with love <a href="https://freeetemplates.com/">FreeeTemplates</a></div></footer></body></html>

sidebar.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"xmlns:th="http://www.thymeleaf.org"  xmlns:sec="http://www.thymeleaf.org"><head><meta charset="utf-8"><meta content="width=device-width, initial-scale=1.0" name="viewport"><title>Dashboard - Admin Bootstrap Template</title><meta name="robots" content="noindex, nofollow"><meta content="" name="description"><meta content="" name="keywords"><link href="assets/img/favicon.png" rel="icon"><link href="assets/img/apple-touch-icon.png" rel="apple-touch-icon"><link href="https://fonts.gstatic.com" rel="preconnect"><link href="https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i|Nunito:300,300i,400,400i,600,600i,700,700i|Poppins:300,300i,400,400i,500,500i,600,600i,700,700i" rel="stylesheet"><link href="assets/css/bootstrap.min.css" rel="stylesheet"><link href="assets/css/bootstrap-icons.css" rel="stylesheet"><link href="assets/css/boxicons.min.css" rel="stylesheet"><link href="assets/css/quill.snow.css" rel="stylesheet"><link href="assets/css/quill.bubble.css" rel="stylesheet"><link href="assets/css/remixicon.css" rel="stylesheet"><link href="assets/css/simple-datatables.css" rel="stylesheet"><link href="assets/css/style.css" rel="stylesheet"></head><body><aside id="sidebar" class="sidebar" th:fragment="sidebar"><ul class="sidebar-nav" id="sidebar-nav"><li class="nav-item"> <a class="nav-link " href="index.html"> <i class="bi bi-grid"></i> <span>Dashboard</span> </a></li><li class="nav-item"><a class="nav-link collapsed" data-bs-target="#components-nav" data-bs-toggle="collapse" href="#"> <i class="bi bi-menu-button-wide"></i><span>系统管理</span><i class="bi bi-chevron-down ms-auto"></i> </a><ul id="components-nav" class="nav-content collapse " data-bs-parent="#sidebar-nav"><li sec:authorize="hasAuthority('user')"><a th:href="@{/user}"><i class="bi bi-circle"></i><span>用户管理</span> </a></li><li sec:authorize="hasAuthority('role')"><a th:href="@{/role}"><i class="bi bi-circle"></i><span>角色管理</span> </a></li><li sec:authorize="hasAuthority('permission')"><a th:href="@{/permission}"><i class="bi bi-circle"></i><span>权限管理</span> </a></li><li> <a href="components-breadcrumbs.html"> <i class="bi bi-circle"></i><span>Breadcrumbs</span> </a></li><li> <a href="components-buttons.html"> <i class="bi bi-circle"></i><span>Buttons</span> </a></li><li> <a href="components-cards.html"> <i class="bi bi-circle"></i><span>Cards</span> </a></li><li> <a href="components-carousel.html"> <i class="bi bi-circle"></i><span>Carousel</span> </a></li><li> <a href="components-list-group.html"> <i class="bi bi-circle"></i><span>List group</span> </a></li><li> <a href="components-modal.html"> <i class="bi bi-circle"></i><span>Modal</span> </a></li><li> <a href="components-tabs.html"> <i class="bi bi-circle"></i><span>Tabs</span> </a></li><li> <a href="components-pagination.html"> <i class="bi bi-circle"></i><span>Pagination</span> </a></li><li> <a href="components-progress.html"> <i class="bi bi-circle"></i><span>Progress</span> </a></li><li> <a href="components-spinners.html"> <i class="bi bi-circle"></i><span>Spinners</span> </a></li><li> <a href="components-tooltips.html"> <i class="bi bi-circle"></i><span>Tooltips</span> </a></li></ul></li><li class="nav-item"><a class="nav-link collapsed" data-bs-target="#forms-nav" data-bs-toggle="collapse" href="#"> <i class="bi bi-journal-text"></i><span>Forms</span><i class="bi bi-chevron-down ms-auto"></i> </a><ul id="forms-nav" class="nav-content collapse " data-bs-parent="#sidebar-nav"><li> <a href="forms-elements.html"> <i class="bi bi-circle"></i><span>Form Elements</span> </a></li><li> <a href="forms-layouts.html"> <i class="bi bi-circle"></i><span>Form Layouts</span> </a></li><li> <a href="forms-editors.html"> <i class="bi bi-circle"></i><span>Form Editors</span> </a></li><li> <a href="forms-validation.html"> <i class="bi bi-circle"></i><span>Form Validation</span> </a></li></ul></li><li class="nav-item"><a class="nav-link collapsed" data-bs-target="#tables-nav" data-bs-toggle="collapse" href="#"> <i class="bi bi-layout-text-window-reverse"></i><span>Tables</span><i class="bi bi-chevron-down ms-auto"></i> </a><ul id="tables-nav" class="nav-content collapse " data-bs-parent="#sidebar-nav"><li> <a href="tables-general.html"> <i class="bi bi-circle"></i><span>General Tables</span> </a></li><li> <a href="tables-data.html"> <i class="bi bi-circle"></i><span>Data Tables</span> </a></li></ul></li><li class="nav-item"><a class="nav-link collapsed" data-bs-target="#charts-nav" data-bs-toggle="collapse" href="#"> <i class="bi bi-bar-chart"></i><span>Charts</span><i class="bi bi-chevron-down ms-auto"></i> </a><ul id="charts-nav" class="nav-content collapse " data-bs-parent="#sidebar-nav"><li> <a href="charts-chartjs.html"> <i class="bi bi-circle"></i><span>Chart.js</span> </a></li><li> <a href="charts-apexcharts.html"> <i class="bi bi-circle"></i><span>ApexCharts</span> </a></li><li> <a href="charts-echarts.html"> <i class="bi bi-circle"></i><span>ECharts</span> </a></li></ul></li><li class="nav-item"><a class="nav-link collapsed" data-bs-target="#icons-nav" data-bs-toggle="collapse" href="#"> <i class="bi bi-gem"></i><span>Icons</span><i class="bi bi-chevron-down ms-auto"></i> </a><ul id="icons-nav" class="nav-content collapse " data-bs-parent="#sidebar-nav"><li> <a href="icons-bootstrap.html"> <i class="bi bi-circle"></i><span>Bootstrap Icons</span> </a></li><li> <a href="icons-remix.html"> <i class="bi bi-circle"></i><span>Remix Icons</span> </a></li><li> <a href="icons-boxicons.html"> <i class="bi bi-circle"></i><span>Boxicons</span> </a></li></ul></li><li class="nav-heading">Pages</li><li class="nav-item"> <a class="nav-link collapsed" href="users-profile.html"> <i class="bi bi-person"></i> <span>Profile</span> </a></li><li class="nav-item"> <a class="nav-link collapsed" href="pages-faq.html"> <i class="bi bi-question-circle"></i> <span>F.A.Q</span> </a></li><li class="nav-item"> <a class="nav-link collapsed" href="pages-contact.html"> <i class="bi bi-envelope"></i> <span>Contact</span> </a></li><li class="nav-item"> <a class="nav-link collapsed" href="pages-register.html"> <i class="bi bi-card-list"></i> <span>Register</span> </a></li><li class="nav-item"> <a class="nav-link collapsed" href="pages-login.html"> <i class="bi bi-box-arrow-in-right"></i> <span>Login</span> </a></li><li class="nav-item"> <a class="nav-link collapsed" href="pages-error-404.html"> <i class="bi bi-dash-circle"></i> <span>Error 404</span> </a></li><li class="nav-item"> <a class="nav-link collapsed" href="pages-blank.html"> <i class="bi bi-file-earmark"></i> <span>Blank</span> </a></li></ul></aside></body></html>

list_user.html

<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8"><meta content="width=device-width, initial-scale=1.0" name="viewport"><title>Dashboard - Admin Bootstrap Template</title><meta name="robots" content="noindex, nofollow"><meta content="" name="description"><meta content="" name="keywords"><link href="/assets/img/favicon.png" rel="icon"><link href="/assets/img/apple-touch-icon.png" rel="apple-touch-icon"><link href="/assets/css/bootstrap.min.css" rel="stylesheet"><link href="/assets/css/bootstrap-icons.css" rel="stylesheet"><link href="/assets/css/boxicons.min.css" rel="stylesheet"><link href="/assets/css/quill.snow.css" rel="stylesheet"><link href="/assets/css/quill.bubble.css" rel="stylesheet"><link href="/assets/css/remixicon.css" rel="stylesheet"><link href="/assets/css/simple-datatables.css" rel="stylesheet"><link href="/assets/css/style.css" rel="stylesheet"></head><body><header id="header" class="header fixed-top d-flex align-items-center" th:replace="fragments/header :: header"></header><aside id="sidebar" class="sidebar" th:replace="fragments/sidebar :: sidebar"></aside><main id="main" class="main"><div class="pagetitle"><nav><ol class="breadcrumb"><li class="breadcrumb-item"><a href="index.html">主页</a></li><li class="breadcrumb-item active">用户管理</li></ol></nav></div><section class="section dashboard"><div class="row"><div class="col-lg-12"><div class="card"><div class="card-body"><br/><br/><div class="table-title"><div class="row"><div class="col-sm-6"><h2> <b>用户管理</b></h2></div><div class="col-sm-6"><div sec:authorize="hasAnyAuthority('permission_create')"><a href="/user/new" class="btn btn-success" ><i class="bi bi-person-plus" data-toggle="tooltip" title="添加用户"></i></a><!-- <a href="#deleteEmployeeModal" class="btn btn-danger" data-toggle="modal"><i class="material-icons"></i> <span>Delete</span></a>                       --></div></div></div></div><table class="table table-striped table-hover"><thead ><tr><th>ID</th><th>User Name</th><th>E-mail</th><th>Name</th><th>Home Page</th><th>Roles</th><th>Actions</th></tr></thead><tbody><tr th:each="user: ${listUsers}"><td th:text="${user.id}">User ID</td><td th:text="${user.username}">User Name</td><td th:text="${user.email}">E-mail</td><td th:text="${user.name}">Name</td><td th:text="${user.homepage}">Home Page</td><td th:text="${user.roles}">Roles</td><td><a sec:authorize="hasAuthority('user_edit')" th:href="@{'/user/edit/' + ${user.id}}" class="edit" ><i class="bi bi-pencil" data-toggle="tooltip" title="编辑"></i></a><a sec:authorize="hasAuthority('user_edit')" th:href="@{'/user/resetpassword/' + ${user.id}}" class="reset" ><i class="bi bi-key" data-toggle="tooltip" title="重置密码"></i></a><a sec:authorize="hasAuthority('user_delete')" th:href="@{'/user/delete/' + ${user.id}}" class="delete" ><i class="bi bi-trash" data-toggle="tooltip" title="删除"></i></a></td></tr></tbody></table></div></div></div></section></main><footer id="footer" class="footer" th:replace="fragments/footer :: footer"><div class="copyright"> &copy; Copyright <strong><span>Compnay Name</span></strong>. All Rights Reserved</div><div class="credits"> with love <a href="https://freeetemplates.com/">FreeeTemplates</a></div></footer><a href="#" class="back-to-top d-flex align-items-center justify-content-center"><i class="bi bi-arrow-up-short"></i></a><script src="/assets/js/apexcharts.min.js"></script><script src="/assets/js/bootstrap.bundle.min.js"></script><script src="/assets/js/chart.min.js"></script><script src="/assets/js/echarts.min.js"></script><script src="/assets/js/quill.min.js"></script><script src="/assets/js/simple-datatables.js"></script><script src="/assets/js/tinymce.min.js"></script><script src="/assets/js/validate.js"></script><script src="/assets/js/main.js"></script></body>
</html>

历史密码保存和重用检测

package com.example;import java.util.Date;
import java.util.List;
import java.util.Set;
import javax.validation.Valid;
import org.apache.commons.text.similarity.LevenshteinDistance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.servlet.ModelAndView;@Controllerpublic class UserController {@Autowiredprivate UserService userService;@Autowiredprivate UserRepository userRepository;@Autowiredprivate BCryptPasswordEncoder bCryptPasswordEncoder;/*** 注入身份认证管理器*/@Autowiredprivate AuthenticationManager authenticationManager;@GetMapping("/user/new")public String showRegistrationForm(Model model) {model.addAttribute("user", new UserDTO());return "user/new_user";}@GetMapping("/user")public String listUsers(Model model) {List<User> listUsers = userRepository.findAll();model.addAttribute("listUsers", listUsers);return "user/list_user";}@GetMapping("/user/edit/{id}")public String editUser(@PathVariable("id") Integer id, Model model) {User user = userService.get(id);List<Role> listRoles = userService.listRoles();model.addAttribute("user", user);model.addAttribute("listRoles", listRoles);return "user/edit_user";}@PostMapping("/user/save")public String saveUser(@Valid @ModelAttribute("user") UserDTO user, BindingResult bindingResult) {if (bindingResult.hasErrors()) {return "user/new_user";}userService.saveUser(user);return "redirect:/user";}@PostMapping("/user/update")public String updateUser(User user) {User repoUser = userRepository.findById(user.getId()).orElse(null);repoUser.setUsername(user.getUsername());repoUser.setEmail(user.getEmail());repoUser.setName(user.getName());repoUser.setEnabled(user.isEnabled());repoUser.setAccountNonLocked(user.isAccountNonLocked());repoUser.setHomepage(user.getHomepage());repoUser.setRoles(user.getRoles());userRepository.save(repoUser);return "redirect:/user";}@GetMapping("/user/resetpassword/{id}")public String showResetPasswordForm(@PathVariable("id") Integer id, Model model) {User user = userRepository.findById(id).orElse(null);UserResetPasswordDTO userResetPasswordDTO = new UserResetPasswordDTO();userResetPasswordDTO.setId(user.getId());userResetPasswordDTO.setUsername(user.getUsername());model.addAttribute("user", userResetPasswordDTO);return "user/reset_password";}@PostMapping("/user/savepassword")public String savePassword(@Valid @ModelAttribute("user") UserResetPasswordDTO user, BindingResult bindingResult) {if (bindingResult.hasErrors()) {return "user/reset_password";}User repoUser = userRepository.findById(user.getId()).orElse(null);repoUser.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));Date passwordChangedTime = new Date();repoUser.setPasswordChangedTime(passwordChangedTime);userRepository.save(repoUser);return "redirect:/user";}@RequestMapping("/user/delete/{id}")public String deleteUser(@PathVariable(name = "id") Integer id) {userRepository.deleteById(id);return "redirect:/user";}//    @RequestMapping(value = "user/info", method = RequestMethod.GET)
//    public ModelAndView userProfile() {
//        ModelAndView modelAndView = new ModelAndView();
//        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
//        System.out.println(auth.getName());
//        User user = userRepository.getByUsername(auth.getName());
//        modelAndView.addObject("user", user);
//        modelAndView.addObject("userName", user.getUsername());
//        modelAndView.setViewName("user-profile");
//        return modelAndView;
//    }@GetMapping("user/info")public String userProfile(Model model) {Authentication auth = SecurityContextHolder.getContext().getAuthentication();System.out.println(auth.getName());User user = userRepository.getByUsername(auth.getName());model.addAttribute("user", user);return "user/user_profile";}//    @RequestMapping(value = "/change/password", method = RequestMethod.GET)
//    public ModelAndView changePassword() {
//        ModelAndView modelAndView = new ModelAndView();
//        modelAndView.addObject("userDTO", new UserChangePasswordDTO());
//        modelAndView.setViewName("password-update");
//        return modelAndView;
//    }@GetMapping("/change/password")public String changePassword(Model model) {model.addAttribute("userDTO", new UserChangePasswordDTO());return "user/password_update";}//    @RequestMapping(value = "/new/password", method = RequestMethod.POST)
//    public ModelAndView newPassword(@Valid @ModelAttribute("userDTO") UserChangePasswordDTO userChangePasswordDTO, BindingResult bindingResult) {
//        if (bindingResult.hasErrors()) {
//            ModelAndView modelAndView = new ModelAndView();
//            modelAndView.addObject("userDTO", new UserChangePasswordDTO());
//            modelAndView.setViewName("password-update");
//            return modelAndView;
//        }
//        ModelAndView modelAndView = new ModelAndView();
//        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
//
//        if (userChangePasswordDTO.getNewPass().equals(userChangePasswordDTO.getConfirmPass())) {
//            User user = userService.findUserByUsername(auth.getName());
//            Boolean status = userService.isPasswordValid(userChangePasswordDTO.getPassword(), user.getPassword());
//            if (status == true) {
//
//                userService.changePassword(user, userChangePasswordDTO);
//                modelAndView.setViewName("login");
//            } else {
//                modelAndView.addObject("wrongPass", "Current possword was wrong..!");
//                modelAndView.setViewName("password-update");
//            }
//
//        } else {
//            modelAndView.addObject("passMatched", "Password doesn't matched..!");
//            modelAndView.setViewName("password-update");
//        }
//        return modelAndView;
//    }@PostMapping("/new/password")public StringnewPassword(@Valid @ModelAttribute("userDTO") UserChangePasswordDTO userChangePasswordDTO, BindingResult bindingResult, Model model) {if (bindingResult.hasErrors()) {return "user/password_update";}Authentication auth = SecurityContextHolder.getContext().getAuthentication();if (userChangePasswordDTO.getNewPass().equals(userChangePasswordDTO.getConfirmPass())) {User user = userService.findUserByUsername(auth.getName());Boolean status = userService.isPasswordValid(userChangePasswordDTO.getPassword(), user.getPassword());if (status == true) {LevenshteinDistance levenshteinWithThreshold = new LevenshteinDistance(3);// Returns -1 since the actual distance, 4, is higher than the thresholdSystem.out.println("Levenshtein distance: " + levenshteinWithThreshold.apply(userChangePasswordDTO.getNewPass(), userChangePasswordDTO.getPassword()));if (levenshteinWithThreshold.apply(userChangePasswordDTO.getNewPass(), userChangePasswordDTO.getPassword()) == -1) {Set<History> setHistorysCheck = user.getHistorys();Boolean check = true;for (History hist : setHistorysCheck) {System.out.print(hist.getPassword());if (bCryptPasswordEncoder.matches(userChangePasswordDTO.getNewPass(), hist.getPassword())) {check = false;break;}}if (check == true) {System.out.println(user.getHistorys());History history = new History();System.out.println("userChangePasswordDTO.getNewPass()=" + userChangePasswordDTO.getNewPass());history.setPassword(bCryptPasswordEncoder.encode(userChangePasswordDTO.getNewPass()));System.out.println(history);Set<History> setHistorys = user.getHistorys();setHistorys.add(history);user.setHistorys(setHistorys);System.out.println(user.getHistorys());userService.changePassword(user, userChangePasswordDTO);return "login";} else {model.addAttribute("passMatched", "New password was same as history..!");return "user/password_update";}} else {model.addAttribute("passMatched", "New password need 4 diff wtih Current password..!");return "user/password_update";}} else {model.addAttribute("wrongPass", "Current password was wrong..!");return "user/password_update";}} else {model.addAttribute("passMatched", "Password doesn't matched..!");return "user/password_update";}}}

结论:

到目前为止,您已经学会了使用基于表单的身份验证和数据库内凭据来保护Spring Boot应用程序。您会看到 Spring 安全性使实现登录和注销功能变得非常容易,并且非常方便。为方便起见,您可以下载下面的示例项目。

下载源码:GitHub - allwaysoft/ProductManagerJDBCAuthenticationManuallyAuthenticateCaptchaAccess

https://github.com/allwaysoft/ProductManagerMaximumSessionsCaptchaNew

Spring Security自定义登录验证,验证码,动态管理uri访问权限,Thymeleaf,限制密码强度、过期、错误密码锁定超时自动解锁、禁用历史密码、新密码和现密码差异要求编辑距离相关推荐

  1. Spring Security登录验证,验证码,动态管理uri访问权限,Thymeleaf,限制密码强度、过期、错误密码锁定超时自动解锁、禁用历史密码、新密码和现密码差异要求编辑距离

    在本教程中,我将指导您如何编写代码,以使用具有基于表单的身份验证的Spring安全API来保护Spring Boot应用程序中的网页.用户详细信息存储在MySQL数据库中,并使用春季JDBC连接到数据 ...

  2. Spring Security自定义登录验证及登录返回结果

    Spring Security自定义登录验证及登录返回结果 一.功能描述 二.处理逻辑 简单流程 自定义UserDetails 自定义UserDetailsDAO 自定义UserDetailsServ ...

  3. 关闭Spring security的登录验证

    目的关闭Spring security 默认登录页 Springboot 2.x关闭需要在启动类上排除SecurityAutoConfiguration和ManagementWebSecurityAu ...

  4. (二)Spring Security自定义登录成功或失败处理器

    目录 一:创建登录成功处理器 二:创建登录失败处理器 三:添加处理器 三. 项目地址 我们接着上一章 Spring Security最简单的搭建,进行开发 LoginSuccessHandler 和L ...

  5. Spring Security:自定义登录页面

    本文来说下Spring Security中如何自定义登录页面 文章目录 准备工作 自定义登录界面 本文小结 准备工作 添加模板引擎 这里使用了thymeleaf模板引擎,在pom.xml进行添加: & ...

  6. spring security实现登录验证以及根据用户身份跳转不同页面

    想关依赖,采用session加redis存储用户信息 <dependency><groupId>org.springframework.security</groupId ...

  7. Spring Security 定制UserDetailsService,动态uri权限,Thymeleaf,限制密码强度、过期、错误密码锁定超时自动解锁、禁用历史密码、新密码和现密码差异要求编辑距离

    在本教程中,我将指导您如何编写代码,以使用具有基于表单的身份验证的Spring安全API来保护Spring Boot应用程序中的网页.用户详细信息存储在MySQL数据库中,并使用春季JDBC连接到数据 ...

  8. 用spring security设置用户jwt令牌和设置接口访问权限案例

    文章目录 1.配置Swagger 2.spring security配置 3.用户校验逻辑 注册和登录接口 dao层 service层 pojo层 4.加密验证逻辑 5.生成令牌逻辑 身份验证提供者: ...

  9. SpringBoot Security 自定义登录验证逻辑+密码加盐

    密码加盐思路 JAVA 加盐加密方法_Teln_小凯的博客-CSDN博客 盐加密方法 @ApiOperation(value = "002-加密")@PreAuthorize(&q ...

最新文章

  1. 「彩票假说」要修正?王言治团队:神经网络要「中奖」,秘密在于学习率!|ICML 2021...
  2. 前端总线,外频及单位GT/s,MHz区别
  3. yum源无法安装mysql_Centos7上使用官方YUM源安装Mysql
  4. linux如何查看系统架构?(查看系统架构命令)(armv7l)
  5. SAP Hybris Commerce里的数据库表
  6. CI Weekly #11 | 微服务场景下的自动化测试与持续部署
  7. ajax post 没有返回_Ajax异步技术之三:jQuery中的ajax学习
  8. Linux用户管理详解大结局(下)
  9. 深入分析String类型(一)
  10. 在线教育雪崩:藏在家长群里的“水军”消失了
  11. 分离圆环图显示百分比_excel这个百分比图,你不一定会制作
  12. 整理一些css在使用中的小技巧(进行中)
  13. 每天一道面试题(2):实现strncpy
  14. JavaScript高级程序设计(第四版) 第二章 HTML中的javascript
  15. 图文详解互联网根基之HTTP
  16. 倾斜摄影测量三维实景建模
  17. [Python]一个简单的QQ截图
  18. 一只青蛙跳向三个台阶_青蛙跳台阶-递归思想解算
  19. ShaderJoy —— 心形爆炸烟花效果【GLSL】
  20. Unity3d培训中Rotation和EularAngles的正确使用方法

热门文章

  1. 不知道照片一键换天空的软件有什么?分享这几款制作软件
  2. 跟着团子学SAP PS—项目中的物料需求逻辑及导入程序设计要点(采购标识符/BAPI_NETWORK_COMP_ADD)
  3. 计算机专业Java毕业设计题目参考【附项目+论文+源码】
  4. 红外光谱技术在气体检测分析中的应用
  5. 【Fluent】Failed Share Topology in DM/SCDM,Attempting to repair model
  6. 利用python3爬虫下载图片、pdf文档
  7. 【Proteus仿真】8位数码管动态扫描显示变化数据
  8. QT Designer使用入门
  9. 永磁同步电机基本控制方法
  10. 数据获取 | 如何获取各地高质量的航拍图并生成等高线地形?