记SpringBoot3.x + SpringSecurity6.x的实现
系列文章目录
第一章 记SpringBoot3.x + Thymeleaf 项目实现(MVC架构模式)
第二章 记SpringBoot3.x + SpringSecurity6.x的实现
目录
前言
一、SpringSecurity能做什么
二、使用SpringSecurity
1. 在上一章新建的wqinfo项目中引入起步依赖
2. 运行项目体验SpringSecurity的防护
3. 放行我们自己的登录页面不被SpringSecurity保护拦截
4. 实现SpringSecurity的用户登录认证
(1)修改User实体对象实现UserDetails接口
(2)让SpringSecurity获得登录用户详情信息
(3)将MyUserDetailsService注入到SpringBean,修改配置类
5. 运行项目看效果
6. 简单总结下实现流程
7. Spring Security 登录认证流程
8. Spring Security退出
总结
前言
本章讲解SpringSecurity6.x的使用,基于java的配置。接着使用上一章的项目wqinfo继续编码。更早的时候还有基于XML配置文件的配置,想了解的可以关注我以前的文章。
一、SpringSecurity能做什么
用户认证(Authentication):就是验证用户合法登录。
用户授权(Authorization):判断用户拥有什么权限,可以访问什么资源
攻击防护:防御跨站请求伪造攻击,session攻击等
二、使用SpringSecurity
1. 在上一章新建的wqinfo项目中引入起步依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>
2. 运行项目体验SpringSecurity的防护
SpringSecurity依赖包引入之后就启动了防护作用,启动项目后在后台生成了密码,如下:
启动项目后浏览器访问:http://127.0.0.1:8080/index.html网页或http://127.0.0.1:8080/user/index请求都会拦截并跳转到SpringSecurity默认的登录页面。默认的用户为:user
登录成功后访问到了index.html页面
3. 放行我们自己的登录页面不被SpringSecurity保护拦截
我们需要将下图的/index请求放行:
编写配置类,此处先关闭csrf:
package com.wq.wqinfo.securityconfig;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.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;/*** @author lv* @date 2025年8月18日17点16分* SpringSecurity配置类*/
@Configuration //表明是一个配置类bean
@EnableWebSecurity //此注解1: 加载了WebSecurityConfiguration配置类, 配置安全认证策略。2: 加载了AuthenticationConfiguration, 配置了认证信息。
public class SecurityConfig {/*** 权限相关的配置,设置一些链接不要拦截* @param http* @return* @throws Exception*/@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {// 关闭csrfhttp.csrf(it->it.disable());// 权限http.authorizeHttpRequests(it->it.requestMatchers("/user/index").permitAll() //设置需要放行的路径.anyRequest().authenticated() //其他路径都要进行拦截);return http.build();}
}
此时运行项目就可以访问http://127.0.0.1:8080/user/index请求了。
成功访问到了我们自己写的登录页面。
4. 实现SpringSecurity的用户登录认证
(1)修改User实体对象实现UserDetails接口
package com.wq.wqinfo.domain;import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Date;/*** 用户领域实体bean数据表映射*/
public class User implements UserDetails {private static final long serialVersionUID = 1L;private long id;private String password;private String username;private Date addtime;public long getId() {return id;}public void setId(long id) {this.id = id;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public Date getAddtime() {return addtime;}public void setAddtime(Date addtime) {this.addtime = addtime;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}
User实体类实现了UserDetails这个接口,并重写了UserDetails接口的5个方法。其中4个boolean方法默认值为false,而我在此处设置成true。因为设置为false在后面的示例中将无法正常登录。
(2)让SpringSecurity获得登录用户详情信息
package com.wq.wqinfo.service.impl;import com.wq.wqinfo.domain.User;
import com.wq.wqinfo.persistence.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;/*** @author lv* @date 2025年8月19日10点11分* 实现security提供的UserDetailsService接口,获取用户详细信息*/
@Component
public class MyUserDetailsService implements UserDetailsService {/*** 注入UserMapper获取数据库用户信息*/@Autowiredprivate UserMapper userMapper;/*** 实现UserDetailsService接口提供的方法,查询你数据库中用户表的信息,返回UserDetails* @param username* @return* @throws UsernameNotFoundException*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {System.out.println("----loadUserByUsername用户详情查询----用户名:" + username);/*** 此处返回用户详情有两种方式。* 一是将自定义的用户实体类User实现UserDetails接口,并返回。此时User中既包含你自定义的属性,也包含UserDetails接口的属性* 二是创建UserDetails对象,并返回。即通过用户名查询出用户信息后填装到org.springframework.security.core.userdetails.User默认提供的User对象中返回。*//*** 先将User实现UserDetails接口,返回*/User user = userMapper.findByUsername(username);if (user != null) {return user;}throw new UsernameNotFoundException(username + "用户名不存在!");}
}
代码中实现了UserDetailsService接口,重写了loadUserByUsername方法,方法中调用了查询数据库用户信息的方法userMapper.findByUsername(username)。这样SpringSecurity就可以查询到我们数据库中的用户信息了。
(3)将MyUserDetailsService注入到SpringBean,修改配置类
package com.wq.wqinfo.config;import com.wq.wqinfo.service.impl.MyUserDetailsService;
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.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;/*** @author lv* @date 2025年8月18日17点16分* SpringSecurity配置类*/
@Configuration //表明是一个配置类bean
@EnableWebSecurity //此注解1: 加载了WebSecurityConfiguration配置类, 配置安全认证策略。2: 加载了AuthenticationConfiguration, 配置了认证信息。
public class SecurityConfig {@Autowiredprivate MyUserDetailsService userDetailsService;@Beanpublic AuthenticationManager authenticationManager(PasswordEncoder passwordEncoder){System.out.println("用户认证管理配置---AuthenticationManager.........");//关联UserDetails和PasswordEncoderDaoAuthenticationProvider provider = new DaoAuthenticationProvider();//将编写的MyUserDetailsService注入进来provider.setUserDetailsService(userDetailsService);//将使用的密码编译器加入进来provider.setPasswordEncoder(passwordEncoder);//将DaoAuthenticationProvider对象放置到AuthenticationManager 中管理ProviderManager providerManager = new ProviderManager(provider);return providerManager;}/*** 装备一个密码转码器* @return*/@Beanpublic PasswordEncoder encoderPwd(){System.out.println("密码转码器配置---PasswordEncoder。。。。。。。。");return new BCryptPasswordEncoder();//进行转码,指定加密机制}/*** 权限相关的配置,设置一些链接不要拦截* @param http* @return* @throws Exception*/@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {System.out.println("权限配置---使用SecurityFilterChain过滤器进行配置。。。。。。。。。");// 关闭csrfhttp.csrf(it->it.disable());// 权限http.authorizeHttpRequests(it ->it.requestMatchers("/user/index").permitAll() //设置需要放行的路径/user/index.anyRequest().authenticated() //其他路径都要进行拦截);// 基于表单的登录认证http.formLogin(from->from.loginPage("/user/index") //跳转到自定义的登录页面.loginProcessingUrl("/security_login") //处理前端的登录请求,与from表单的action一致。这个请求会进入Spring Security的登录验证方法,而非自己写的。.defaultSuccessUrl("/user/success") //默认请求成功之后的跳转路径,一般情况路径指向自定义方法或页面);return http.build();}
}
配置类中自动装配了MyUserDetailsService通过AuthenticationManager将UserDetails和PasswordEncoder关联,以Bean的方式注入到Spring容器中。SecurityFilterChain过滤器中加入基于表单的登录认证,通过loginProcessingUrl配置关联自定义登录页的请求地址,并将请求发送到Spring Security的登录验证方法,而非自己写的。
关于formLogin配置,是Spring Security提供的基于表单的登录认证。用于传统的web应用,Spring Security 负责处理表单登录请求、验证用户和密码、生成会话等操作。
到此我们就完成了Spring Security6.x的自定义表单页面用户登录认证。下面去看看运行效果。
5. 运行项目看效果
先看看项目结构:
启动项目时运行了配置类,注册了密码转码器、认证管理、权限配置过滤器
访问:http://127.0.0.1:8080/hello?name=lisi 进行了拦截,回到了登录页
输入用户名密码后登录成功,访问到了http://127.0.0.1:8080/hello?name=lisi 地址:
我们再直接到登录页面:http://127.0.0.1:8080/user/index ,输入用户和密码:
点击登录后登录成功如下图:
我们看看后台打印情况:
1、2、3步加载了配置文件,4、5访问到登录页面,6查询用户,7输入用户密码后登录成功。4,5步因为配置类中权限放行和表单认证配置时都配置了/user/index请求,所以打印了两次。从运行效果来看我们已经实现了Spring Security6.x的自定义登录页用户登录的认证。
6. 简单总结下实现流程
1. 配置类放行登录页请求路径/user/index(logins.html)
2. User实体类实现UserDetails接口及方法
3. 实现UserDetailsService接口,查询数据库用户详情信息
4. 配置类注入AuthenticationManager将UserDetailsService和PasswordEncoder关联,然后再配置
SecurityFilterChain过滤器的formLogin,其中loginProcessingUrl中的请求路径就是登录页的表单提交路径、保持一致。如此便能将页面提交的用户名和密码交给Spring Security管理,进行用户的匹配认证(我们不用关心认证过程)。认证的过程其实就是在AuthenticationManager中将页面提交的用户名,密码和UserDetailsService中获得的用户信息进行匹配认证。
7. Spring Security 登录认证流程
图中流程可分为以下几个核心步骤:
-
请求拦截与令牌生成 (UsernamePasswordAuthenticationFilter)
-
拦截请求:过滤器默认拦截
/login
(POST) 请求。 -
提取凭证:从
HttpServletRequest
中获取用户名和密码参数(默认是username
和password
)。 -
创建令牌:使用获取到的凭证创建一个 未认证 的
UsernamePasswordAuthenticationToken
对象(它是Authentication
接口的一个实现)。
-
-
认证调度 (AuthenticationManager)
-
过滤器将上一步创建的令牌交给
AuthenticationManager
的authenticate()
方法,启动认证流程。 -
AuthenticationManager
是一个认证调度器,它本身不直接处理认证,而是根据令牌类型委托给相应的AuthenticationProvider
(最常用的是DaoAuthenticationProvider
)来处理。
-
-
加载用户数据 (UserDetailsService)
-
DaoAuthenticationProvider
会调用UserDetailsService
的loadUserByUsername(String username)
方法。 -
UserDetailsService
根据用户名从数据源(如数据库、内存等)中查询用户信息,并封装成一个UserDetails
对象返回。如果用户不存在,则抛出UsernameNotFoundException
。
-
-
密码校验 (PasswordEncoder)
-
认证提供者获取到
UserDetails
后,会使用PasswordEncoder
来比对用户提交的原始密码(来自令牌)和数据库中存储的加密密码(来自UserDetails
)。 -
这是一个至关重要的安全步骤,确保了密码不以明文形式存储和对比。
-
-
认证结果与上下文存储
-
成功:如果所有检查都通过(用户存在且密码正确),认证提供者会创建一个已认证的
Authentication
对象(通常包含UserDetails
和权限信息),并将其返回给过滤器。 -
过滤器 将这个已认证的对象存入
SecurityContextHolder
的SecurityContext
中。这意味着该用户的认证状态对于本次会话(当前线程)是立即可用的,后续的过滤器或控制器可以通过SecurityContextHolder
获取到当前用户信息。
-
-
成功处理 (AuthenticationSuccessHandler)
-
认证成功后,过滤器会调用配置的
AuthenticationSuccessHandler
。 -
默认行为:重定向到之前缓存的请求页面或默认首页(
defaultSuccessUrl
)。 -
常见自定义行为:返回登录成功的 JSON 数据、重定向到特定页面等。
-
-
失败处理 (AuthenticationFailureHandler)
-
如果在上述任何步骤中出现异常(用户不存在、密码错误、账户锁定等),认证过程都会中止并抛出
AuthenticationException
。 -
过滤器会捕获异常,并调用
AuthenticationFailureHandler
进行处理。 -
默认行为:重定向回登录页面并附带错误信息。
-
常见自定义行为:返回包含具体错误原因的 JSON 数据。
-
有兴趣的可以再看看UsernamePasswordAuthenticationFilter类的代码:
8. Spring Security退出
Spring Security默认的退出请求“/logout”,也可以自设置。支持get和post提交。退出登陆时,会清除内存中的登录认证用户主体信息,销毁会话session等。
//添加退出的映射地址,退出后放行/user/loginOut,否则会受SpringSecurity保护拦截http.logout(logout -> {logout.logoutUrl("/logout").logoutSuccessUrl("/user/loginOut").permitAll(); //SpringSecurity默认退出地址是/logout,也可以自定。});
总结
用户认证(Authentication):就是验证用户合法登录。到这就结束了。该说的不该说的都说了。也就没什么总结的了。