600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > Spring Security自定义登录验证 验证码 动态管理uri访问权限 Thymeleaf 限制密码

Spring Security自定义登录验证 验证码 动态管理uri访问权限 Thymeleaf 限制密码

时间:2023-05-21 23:38:42

相关推荐

Spring Security自定义登录验证 验证码 动态管理uri访问权限 Thymeleaf 限制密码

在本教程中,我将指导您如何编写代码,以使用具有基于表单的身份验证的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@', 'Administrator', '$2a$10$2/LSmp3YoEOT97KzgrYODen7I88ErBovM2Qehw9DL1dW9DZ7DZSAm', 1),(2, 'u1', 'u1@', 'User P1', '$2a$10$2/LSmp3YoEOT97KzgrYODen7I88ErBovM2Qehw9DL1dW9DZ7DZSAm', 1),(3, 'u2', 'u2@', '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=updatespring.datasource.url=jdbc:mysql://localhost:3306/product3?autoReconnect=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghaispring.datasource.username=rootspring.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="/POM/4.0.0"xmlns:xsi="/2001/XMLSchema-instance"xsi:schemaLocation="/POM/4.0.0 /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@EnableWebSecuritypublic 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@EnableWebSecuritypublic 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;@Controllerpublic 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.ponent;import java.util.Properties;@Componentpublic 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=""><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="/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous"><script src="/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"crossorigin="anonymous"></script><link rel="stylesheet" href="/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@Entitypublic 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.ponent;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=""xmlns:sec="/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="/1999/xhtml"xmlns:th="" xmlns:sec=""><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="" rel="preconnect"><link href="/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="/1999/xhtml"xmlns:th="" xmlns:sec=""><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="" rel="preconnect"><link href="/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="/">FreeeTemplates</a></div></footer></body></html>

sidebar.html

<!DOCTYPE html><html xmlns="/1999/xhtml"xmlns:th="" xmlns:sec=""><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="" rel="preconnect"><link href="/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">&#xE15C;</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="/">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 mons.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

/allwaysoft/ProductManagerMaximumSessionsCaptchaNew

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

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。