【Spring Security编程】Spring Security自定义配置

news/2025/2/22 23:24:00/文章来源:https://www.cnblogs.com/o-O-oO/p/18726380

一、基于内存的用户认证

1.1 创建自定义配置

实际开发的过程中,我们需要应用程序更加灵活,可以在SpringSecurity中创建自定义配置文件

官方文档: Java自定义配置(https://docs.spring.io/spring-security/reference/servlet/configuration/java.html)

UserDetailsService用来管理用户信息,InMemoryUserDetailsManager是UserDetailsService的一个实现,用来管理基于内存的用户信息。

创建一个WebSecurityConfig文件:

定义一个 @Bean,类型是 UserDetailsService,实现是 InMemoryUserDetailsManager。

package cn.pottercoding.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;@Configuration
// @EnableWebSecurity  //Spring项目总需要添加此注解,SpringBoot项目中不需要
public class WebSecurityConfig {@Beanpublic UserDetailsService userDetailsService() {InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();manager.createUser( //此行设置断点可以查看创建的user对象User.withDefaultPasswordEncoder().username("potter") //自定义用户名.password("password") //自定义密码.roles("USER") //自定义角色.build());return manager;}
}

测试:使用用户名potter,密码password登录

1.2 基于内存的用户认证流程

【程序启动时】:创建InMemoryUserDetailsManager对象。创建User对象,封装用户名密码。使用InMemoryUserDetailsManager将User存入内存。

【校验用户时】:SpringSecurity自动使用InMemoryUserDetailsManager的loadUserByUsername方法从内存中获取User对象。在UsernamePasswordAuthenticationFilter过滤器中的attemptAuthentication方法中将用户输入的用户名密码和从内存中获取到的用户信息进行比较,进行用户认证。

二、基于数据库的数据源

2.1 SQL

创建三个数据库表并插入测试数据

-- 创建数据库
CREATE DATABASE `security-demo`;
USE `security-demo`;-- 创建用户表
CREATE TABLE `user`(`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,`username` VARCHAR(50) DEFAULT NULL ,`password` VARCHAR(500) DEFAULT NULL,`enabled` BOOLEAN NOT NULL
);
-- 唯一索引
CREATE UNIQUE INDEX `user_username_uindex` ON `user`(`username`); -- 插入用户数据(密码是 "abc" )
INSERT INTO `user` (`username`, `password`, `enabled`) VALUES
('admin', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE),
('Helen', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE),
('Tom', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE);

2.2 引入依赖

<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.30</version>
</dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.4.1</version><exclusions><exclusion><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId></exclusion></exclusions>
</dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>3.0.3</version>
</dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency>

2.3 配置数据源

server:port: 8888
spring:freemarker:suffix: .html# 自定义 security用户名、密码security:user:name: potterpassword: password# MySQL数据源datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/security-demousername: rootpassword: pottercodingmybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

2.4 实体类

package cn.pottercoding.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;@Data
public class User {@TableId(value = "id", type = IdType.AUTO)private Integer id;private String username;private String password;private Boolean enabled;}

2.5 Mapper

接口

package cn.pottercoding.mapper;import cn.pottercoding.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface UserMapper extends BaseMapper<User> {
}

xml

resources/mapper/UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.pottercoding.mapper.UserMapper"></mapper>

2.6 Service

接口

package cn.pottercoding.service;import cn.pottercoding.entity.User;
import com.baomidou.mybatisplus.extension.service.IService;public interface UserService extends IService<User> {
}

实现

package cn.pottercoding.service.impl;import cn.pottercoding.entity.User;
import cn.pottercoding.mapper.UserMapper;
import cn.pottercoding.service.UserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import org.springframework.stereotype.Service;@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

2.7 Controller

package cn.pottercoding.controller;import cn.pottercoding.entity.User;
import cn.pottercoding.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;import java.util.List;@RestController
@RequestMapping("/user")
public class UserController {@Resourcepublic UserService userService;@GetMapping("/list")public List<User> getList(){return userService.list();}
}

测试:localhost:8888/user/list

三、基于数据库的用户认证

3.1 基于数据库的用户认证流程

【程序启动时】:创建DBUserDetailsManager类,实现接口 UserDetailsManager, UserDetailsPasswordService在应用程序中初始化这个类的对象。

【校验用户时】:SpringSecurity自动使用DBUserDetailsManager的loadUserByUsername方法从数据库中获取User对象。在UsernamePasswordAuthenticationFilter过滤器中的attemptAuthentication方法中将用户输入的用户名密码和从数据库中获取到的用户信息进行比较,进行用户认证。

3.2 定义DBUserDetailsManager

package cn.pottercoding.config;import cn.pottercoding.entity.User;
import cn.pottercoding.mapper.UserMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.Collection;@Component
@RequiredArgsConstructor
public class DBUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService {private final UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {LambdaQueryWrapper<User> queryWrapper = Wrappers.<User>lambdaQuery().eq(User::getUsername, username);User user = userMapper.selectOne(queryWrapper);if (user == null) {throw new UsernameNotFoundException(username);} else {Collection<GrantedAuthority> authorities = new ArrayList<>();return new org.springframework.security.core.userdetails.User(user.getUsername(),user.getPassword(),user.getEnabled(),true, //用户账号是否过期true, //用户凭证是否过期true, //用户是否未被锁定authorities); //权限列表}}@Overridepublic UserDetails updatePassword(UserDetails user, String newPassword) {return null;}@Overridepublic void createUser(UserDetails userDetails) {}@Overridepublic void updateUser(UserDetails user) {}@Overridepublic void deleteUser(String username) {}@Overridepublic void changePassword(String oldPassword, String newPassword) {}@Overridepublic boolean userExists(String username) {return false;}
}

3.3 初始化UserDetailsService

修改WebSecurityConfig中的userDetailsService方法如下

@Bean
public UserDetailsService userDetailsService() {DBUserDetailsManager manager = new DBUserDetailsManager();return manager;
}

或者直接在DBUserDetailsManager类上添加@Component注解

测试:使用数据库中配置的用户名和密码进行登录

四、SpringSecurity的默认配置

在WebSecurityConfig中添加如下配置

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {//authorizeRequests():开启授权保护//anyRequest():对所有请求开启授权保护//authenticated():已认证请求会自动被授权http.authorizeRequests(authorize -> authorize.anyRequest().authenticated()).formLogin(withDefaults())//表单授权方式.httpBasic(withDefaults());//基本授权方式return http.build();
}

五、添加用户功能

5.1 Controller

UserController中添加方法

@PostMapping("/add")
public void add(@RequestBody User user){userService.saveUserDetails(user);
}

5.2 Service

UserService接口中添加方法

void saveUserDetails(User user);

UserServiceImpl实现中添加方法

@Resource
private DBUserDetailsManager dbUserDetailsManager;@Override
public void saveUserDetails(User user) {UserDetails userDetails = org.springframework.security.core.userdetails.User.withDefaultPasswordEncoder().username(user.getUsername()) //自定义用户名.password(user.getPassword()) //自定义密码.build();dbUserDetailsManager.createUser(userDetails);}

5.3 修改配置

DBUserDetailsManager中添加方法

@Override
public void createUser(UserDetails userDetails) {User user = new User();user.setUsername(userDetails.getUsername());user.setPassword(userDetails.getPassword());user.setEnabled(true);userMapper.insert(user);
}

5.4 使用Swagger测试

pom中添加配置用于测试

<!--swagger测试-->
<dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId><version>4.1.0</version>
</dependency>

Swagger测试地址:http://localhost:8888/doc.html

5.5 关闭csrf攻击防御

默认情况下SpringSecurity开启了csrf攻击防御的功能,这要求请求参数中必须有一个隐藏的_csrf字段,如下:

在filterChain方法中添加如下代码,关闭csrf攻击防御

//关闭csrf攻击防御
http.csrf((csrf) -> {csrf.disable();
});

六、密码加密算法

参考文档:Password Storage :: Spring Security

https://docs.spring.io/spring-security/reference/features/authentication/password-storage.html

6.1 密码加密方式

明文密码

最初,密码以明文形式存储在数据库中。但是恶意用户可能会通过SQL注入等手段获取到明文密码,或者程序员将数据库数据泄露的情况也可能发生。

Hash算法

Spring Security的PasswordEncoder接口用于对密码进行单向转换,从而将密码安全地存储。对密码单向转换需要用到哈希算法,例如MD5、SHA-256、SHA-512等,哈希算法是单向的,只能加密,不能解密。

因此,数据库中存储的是单向转换后的密码,Spring Security在进行用户身份验证时需要将用户输入的密码进行单向转换,然后与数据库的密码进行比较。

因此,如果发生数据泄露,只有密码的单向哈希会被暴露。由于哈希是单向的,并且在给定哈希的情况下只能通过暴力破解的方式猜测密码。

彩虹表

恶意用户创建称为彩虹表的查找表。

彩虹表就是一个庞大的、针对各种可能的字母组合预先生成的哈希值集合,有了它可以快速破解各类密码。越是复杂的密码,需要的彩虹表就越大,主流的彩虹表都是100G以上,目前主要的算法有LM, NTLM, MD5, SHA1, MYSQLSHA1, HALFLMCHALL, NTLMCHALL, ORACLE-SYSTEM, MD5-HALF。

加盐密码

为了减轻彩虹表的效果,开发人员开始使用加盐密码。不再只使用密码作为哈希函数的输入,而是为每个用户的密码生成随机字节(称为盐)。盐和用户的密码将一起经过哈希函数运算,生成一个唯一的哈希。盐将以明文形式与用户的密码一起存储。然后,当用户尝试进行身份验证时,盐和用户输入的密码一起经过哈希函数运算,再与存储的密码进行比较。唯一的盐意味着彩虹表不再有效,因为对于每个盐和密码的组合,哈希都是不同的。

自适应单向函数

随着硬件的不断发展,加盐哈希也不再安全。原因是,计算机可以每秒执行数十亿次哈希计算。这意味着我们可以轻松地破解每个密码。

现在,开发人员开始使用自适应单向函数来存储密码。使用自适应单向函数验证密码时,故意占用资源(故意使用大量的CPU、内存或其他资源)。自适应单向函数允许配置一个“工作因子”,随着硬件的改进而增加。我们建议将“工作因子”调整到系统中验证密码需要约一秒钟的时间。这种权衡是为了让攻击者难以破解密码。

自适应单向函数包括bcrypt、PBKDF2、scrypt和argon2。

6.2 PasswordEncoder

BCryptPasswordEncoder

使用广泛支持的bcrypt算法来对密码进行哈希。为了增加对密码破解的抵抗力,bcrypt故意设计得较慢。和其他自适应单向函数一样,应该调整其参数,使其在您的系统上验证一个密码大约需要1秒的时间。BCryptPasswordEncoder的默认实现使用强度10。建议您在自己的系统上调整和测试强度参数,以便验证密码时大约需要1秒的时间。

Argon2PasswordEncoder

使用Argon2算法对密码进行哈希处理。Argon2是密码哈希比赛的获胜者。为了防止在自定义硬件上进行密码破解,Argon2是一种故意缓慢的算法,需要大量内存。与其他自适应单向函数一样,它应该在您的系统上调整为大约1秒来验证一个密码。当前的Argon2PasswordEncoder实现需要使用BouncyCastle库。

Pbkdf2PasswordEncoder

使用PBKDF2算法对密码进行哈希处理。为了防止密码破解,PBKDF2是一种故意缓慢的算法。与其他自适应单向函数一样,它应该在您的系统上调整为大约1秒来验证一个密码。当需要FIPS认证时,这种算法是一个很好的选择。

SCryptPasswordEncoder

使用scrypt算法对密码进行哈希处理。为了防止在自定义硬件上进行密码破解,scrypt是一种故意缓慢的算法,需要大量内存。与其他自适应单向函数一样,它应该在您的系统上调整为大约1秒来验证一个密码。

6.3 密码加密测试

在测试类中编写一个测试方法

@Test
void testPassword() {// 工作因子,默认值是10,最小值是4,最大值是31,值越大运算速度越慢PasswordEncoder encoder = new BCryptPasswordEncoder(4);//明文:"password"//密文:result,即使明文密码相同,每次生成的密文也不一致String result = encoder.encode("password");System.out.println(result);//密码校验Assert.isTrue(encoder.matches("password", result), "密码不一致");
}

6.4 DelegatingPasswordEncoder

表中存储的密码形式:{bcrypt}

10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW

通过如下源码可以知道:可以通过{bcrypt}前缀动态获取和密码的形式类型一致的PasswordEncoder对象

目的:方便随时做密码策略的升级,兼容数据库中的老版本密码策略生成的密码

七、自定义登录页面

7.1 创建登录Controller

package cn.pottercoding.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;@Controller
public class LoginController {@GetMapping("/login")public String login() {return "login";}
}

7.2 创建登录页面

resources/templates/login.html

<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org">
<head><title>登录</title>
</head>
<body>
<h1>登录</h1>
<div th:if="${param.error}">错误的用户名和密码.</div><!--method必须为"post"-->
<!--th:action="@{/login}" ,
使用动态参数,表单中会自动生成_csrf隐藏字段,用于防止csrf攻击
login: 和登录页面保持一致即可,SpringSecurity自动进行登录认证-->
<form th:action="@{/login}" method="post"><div><!--name必须为"username"--><input type="text" name="username" placeholder="用户名"/></div><div><!--name必须为"password"--><input type="password" name="password" placeholder="密码"/></div><input type="submit" value="登录" />
</form>
</body>
</html>

7.3 配置SecurityFilterChain

SecurityConfiguration:

package cn.pottercoding.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;@Configuration
// @EnableWebSecurity  //Spring项目总需要添加此注解,SpringBoot项目中不需要
public class WebSecurityConfig {@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {//authorizeRequests():开启授权保护//anyRequest():对所有请求开启授权保护//authenticated():已认证请求会自动被授权//        http
//                .authorizeRequests(authorize -> authorize.anyRequest().authenticated())
//                .formLogin(withDefaults())//表单授权方式
//                .httpBasic(withDefaults());//基本授权方式http.authorizeRequests(authorize -> authorize.anyRequest().authenticated()).formLogin(form -> form.loginPage("/login").permitAll() //登录页面无需授权即可访问.usernameParameter("username") //自定义表单用户名参数,默认是username.passwordParameter("password") //自定义表单密码参数,默认是password.failureUrl("/login?error"));  //使用表单授权方式return http.build();}}

原创 potter 程序员波特

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/887125.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux(ubantu)虚拟机上运行c语言程序保姆级教程(附sudo apt-get update 报错连接失败的一种解决方法)

虚拟机使用的是VMware Workstation+Ubuntu 1.安装GCC 在桌面上右键打开终端然后分别输入 sudo apt-get update sudo apt-get install build-essential 注意:输入后会让你输入password,此时输入任何字符屏幕上会什么都没有,但是实际上已经输入进去了,所以直接盲输完密码按回…

flutter3-trip仿携程酒店预订|Flutter3.27+Getx预约旅游酒店App程序

基于Flutter3.x+Dart3+GetX跨平台仿携程/飞猪旅行酒店客房预订查询app系统。 flutter3_trip原创2025新版flutter3.27.1+dart3.6+getx+flutter_datepicker跨平台仿携程/飞猪/同程旅游app酒店客房预订系统。实现了首页、酒店预订模块、酒店搜索/列表/详情、探索动态、订单、消息聊…

【Linux应用】Linux服务器访问不了外网如何安装软件?

大多数服务器都是在隔离的网络环境中,服务器被限制访问互联网。原因当然是出于安全考虑,一旦服务器有外网访问能力,中毒后会主动更新病毒库。比如各种广告、XX全家桶就是这么来的。 那如果想在服务器上安装软件怎么办❓ 可能有人会说先提前下载好,再传上去不就行了嘛。这对…

国产免费的网络监控与分析系统工具

科来网络分析系统(CSNAS)是一款集数据包采集、协议解码、流量分析、故障诊断、安全检测等功能于一体的专业网络管理工具。其核心能力包括实时监控网络流量、识别异常行为、还原会话数据流以及提供多维度性能分析,帮助用户快速定位网络故障、优化性能并提升安全性。 系统要求…

本地md博客自动化上传博客园

该工具会在你这个文档同一目录下生成一个`xxx-cnblog.md`文件,这个就是在他自动帮你上传了图片后,替换了你本地图片的路径,替换为cnblog的图床url路径。前言叠甲:先帮博客园免费打个广告,要用这个方法也往博客园发文喔~ 我的博客园:https://www.cnblogs.com/dhan 需求 经…

cpu的核心数和线程数

在windows操作系统下,想看看自己cpu的核数,我打开任务管理器-性能-CPU,看到我的cpu是2核。 然后,我打开计算机管理-设备管理器-处理器,发现这里显示四个cpu信息,但这四个不是内核数,是线程数。一、概念: 1. 核心数(Cores):CPU实际的物理核心数量,比如双核、四核、六…

WebService服务需要通过注册路由进行转发到自身的asmx页面问题

背景要求 三方接口自定义了url路径,但是原先代码是通过webservice进行开发的asmx页面。 前面已经有人写好了代码,但是本地部署死活无法通过注册的路由调用! `protected void Application_Start(object sender, EventArgs e) { RegisterRoutes(RouteTable.Routes);}/// <su…

mysql 页存储分布

前言 前文我们已经明白了行数据,如何在页存储中分布的行是怎么存储的。 正文 那么页结构又是怎么样的呢?这里我们唯一知道的就是user records 就是实际存储的行记录内容。这里让我觉得有意思的是infimum 和 suprenum,分别是最小记录和最大记录,这两个是用来做什么呢? 我估…

惠州某全球知名玩具工厂牵手珠海盈致 1个月时间快速迈向精益生产

在竞争激烈的市场环境下,惠州某全球知名玩具工厂为求发展,对仿真火车模型车间进行升级改造,致力于打造更高效、更优质的生产环境。该工厂顺应市场趋势牵手珠海盈致STC-X柔性智能线+MES系统,让该车间仅用1个月正式投产,实现精益生产,实现生产过程的透明化和精细化管理。改…

记一次若依分页total总数返回错误问题解决方案

如上图,在使用若依框架分页时,我对返回的list进行了单独处理,然后发现返回的total字段为10,也就是只返回了当前页的总数。 解决方案是修改genDataTable方法,把流处理过的list带到genDataTable方法中,在setTotal设置oldlist(未进流处理的list总数)原因是在经过处理后的lis…

sql统计最大连续增加条数

趋势特征是业务人员比较青睐的特征,其中涉及到连续变化期数,例如连续增大、减少期数等。这里写一下可以实现的思路。 1.使用变量 这个方法在我常用的sql平台中只有mysql支持。pgsql和hive无法实现。 在查询里声明select @var:=0,通过逻辑语句将起始位置置1,连续行处递增,和…

安川机器人维修SGMAV-A5ANA-YR13伺服电机

在工业自动化领域,安川机器人扮演着至关重要的角色。而其中的SGMAV - A5ANA - YR13伺服电机一旦出现故障,将会影响整个安川机器人的正常运作。 安川机器人SGMAV - A5ANA - YR13伺服电机常见故障一、电气故障线路连接问题在维修中,常常会发现线路松动或者短路的情况。这可能是…