尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

认证授权【Spring Security】

认证授权【Spring Security】
📅 发布时间:2026/7/27 6:26:29

Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。由于它是Spring生态系统中的一员,因此它伴随着整个Spring生态系统不断修正、升级,在spring boot项目中加入spring security更是十分简单,使用Spring Security减少了为企业系统安全控制编写大量重复代码的工作。

快速使用

1. 引入security依赖

<!-- Spring Security依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <scope>provided </scope> </dependency>

启动项目,控制台自动输出密码。每次项目启动,密码都会重新生成。

在浏览器输入本地ip+端口,会跳出一个security默认登录页面,这就是 spring Security生效了。默认用户名为user。

成功后的页面如下所示

基本原理

SpringSecurity 本质上是一个过滤器链,有很多个过滤器。

FilterSecurityInterceptor: 是一个方法级的权限过滤器,基本位于过滤链的最底部
ExceptionTranslationFilter:是个异常过滤器,用来处理在认证授权过程中抛出的异常
UsernamePasswordAuthenticationFilter:对 /login的POST请求做拦截, 校验表单中用户名,密码

用户认证流程如下图所示

客户端进行登录请求,Spring Security 的 UsernamePasswordAuthenticationFilter过滤器将请求封装到 Authentication,在UserDetailsService类调用loadUserByUsername方法返回一个UserDetails,这个UserDetails封装了用户名及加密后的密码等信息,获取UserDetails中的密码与Authentication中的密码通过PasswordEncoder进行加密匹配。

认证核心组件

系统中会有许多用户,确认当前是哪个用户正在使用我们系统就是登录认证的最终目的。整个系统安全都是围绕当前登录用户展开的,这个不难理解,要是当前登录用户都不能确认了,那A下了一个订单,下到了B的账户上这不就乱套了。这一概念在Spring Security中的体现就是Authentication,它存储了认证信息,代表当前登录用户。

我们在程序中如何获取并使用它呢?我们需要通过SecurityContext来获取Authentication,SecurityContext就是我们的上下文对象!这个上下文对象则是交由SecurityContextHolder进行管理,可以在程序任何地方使用它:

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

SecurityContextHolder原理非常简单,就是使用ThreadLocal来保证一个线程中传递同一个对象!

Spring Security中三个核心组件:

​ 1、Authentication:存储了认证信息,代表当前登录用户

​ 2、SeucirtyContext:上下文对象,用来获取Authentication

​ 3、SecurityContextHolder:上下文管理对象,用来在程序任何地方获取SecurityContext

Authentication包含的信息

​ 1、Principal:用户信息,没有认证时一般是用户名,认证后一般是用户对象

​ 2、Credentials:用户凭证,一般是密码

​ 3、Authorities:用户权限

Spring Security 用户认证核心代码如下:

// 生成一个包含账号密码的认证信息 Authentication authenticationToken = new UsernamePasswordAuthenticationToken(username, passwrod); // AuthenticationManager校验这个认证信息,返回一个已认证的Authentication Authentication authentication = authenticationManager.authenticate(authenticationToken); // 将返回的Authentication存到上下文中 SecurityContextHolder.getContext().setAuthentication(authentication);

可以看到上面的核心代码,AuthenticationManager 就是Spring Security用于执行身份验证的组件,只需要调用它的authenticate方法即可完成认证。Spring Security默认的认证方式就是在UsernamePasswordAuthenticationFilter这个过滤器中进行认证的,该过滤器负责认证逻辑。

AuthenticationManager校验

先根据用户名查出用户信息,将查询到的用户密码和当前验证的密码加密后对比,比对不成功则爆出异常提示。

AuthenticationManager校验逻辑源码如下

public Authentication authenticate(Authentication authentication) throws AuthenticationException { ...省略其他代码 // 传递过来的用户名 String username = authentication.getName(); // 调用UserDetailsService的方法,通过用户名查询出用户对象UserDetail(查询不出来UserDetailService则会抛出异常) UserDetails userDetails = this.getUserDetailsService().loadUserByUsername(username); // 获取传递过来的密码 String password = authentication.getCredentials().toString(); // 使用密码解析器PasswordEncoder传递过来的密码是否和真实的用户密码匹配 if (!passwordEncoder.matches(password, userDetails.getPassword())) { // 密码错误则抛出异常 throw new BadCredentialsException("错误信息..."); } // 注意:这里返回的已认证Authentication,是将整个UserDetails放进去充当Principal UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(userDetails, authentication.getCredentials(), userDetails.getAuthorities()); return result; ...省略其他代码 }

问题1:谁来执行查询用户信息的操作呢?

正常业务都是将用户信息存储在数据库中。这个业务操作就在UserDetailsService中进行,UserDetailsService实现了 loadUserByUsername(String username) 方法。

问题2:userDetails 对象有什么属性

这里面包含了用户名和密码及其他常用字段,但在实际开发中业务不一样,实体属性也不一样。所以我们会重新实现该接口。但是这样需要重写很多方法。因此,我们可以继承 Security提供的 User类(org.springframework.security.core.userdetails.User)。

public interface UserDetails extends Serializable { /** * 用户权限集合(这个权限对象现在不管它,到权限时我会讲解) */ Collection<? extends GrantedAuthority> getAuthorities(); /** * 用户密码 */ String getPassword(); /** * 用户名 */ String getUsername(); /** * 用户没过期返回true,反之则false */ boolean isAccountNonExpired(); /** * 用户没锁定返回true,反之则false */ boolean isAccountNonLocked(); /** * 用户凭据(通常为密码)没过期返回true,反之则false */ boolean isCredentialsNonExpired(); /** * 用户是启用状态返回true,反之则false */ boolean isEnabled(); }

即操作如下:

自定义一个User类,然后通过继承Security的User类

import com.atguigu.model.system.SysUser; import lombok.Data; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.User; import java.util.Collection; public class CustomUser extends User { /** * 我们自己的用户实体对象,要调取用户信息时直接获取这个实体对象 */ private SysUser sysUser; public CustomUser(SysUser sysUser, Collection<? extends GrantedAuthority> authorities) { super(sysUser.getUsername(), sysUser.getPassword(), authorities); this.sysUser = sysUser; } public SysUser getSysUser() { return sysUser; } public void setSysUser(SysUser sysUser) { this.sysUser = sysUser; } }

实操

用户认证

设置登录的用户名和密码

方式一:配置文件设置

在上面我们通过security默认的用户名和密码登录了。现在我们想要自定用用户名和密码如何操作呢?在SpringBoot项目中,可以在 .properties 配置文件中指定用户名和密码。再次启动项目发现控制台不再输出密码了。

spring.security.user.name=admin spring.security.user.password=123

方式二:配置类设置

自定义一个配置类,配置类内容如下,之后重新测试

@Configuration public class SecurityConf extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 密码加密 BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); String password = passwordEncoder.encode("123"); auth.inMemoryAuthentication().withUser("lucy").password(password).roles("admin"); } @Bean PasswordEncoder password() { return new BCryptPasswordEncoder(); } }

方式三:自定义设置

1. 创建配置类,设置使用哪个userDetailsService实现类

@Configuration public class SecurityConf2 extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(password()); } @Bean PasswordEncoder password() { return new BCryptPasswordEncoder(); } }

2. 编写实现类,返回User对象,User对象有用户名密码和操作权限

@Service("userDetailsService") // 将配置类中注入的UserDetailsService设定的名称在此引入 public class MyUserDetailService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role"); return new User("lisi", new BCryptPasswordEncoder().encode("123"), auths); } }

上面的方式将用户名和密码固定死了,真实的业务场景肯定是从数据库中查询用户信息的,那么接下来将使用自定义的方式改造代码,从数据库中获取用户信息。

改进版本

1. 自定义加密方法

新建一个CustomMd5PasswordEncoder类,实现 Security的 PasswordEncoder,重写encode方法, 这里采用MD5进行加密。

import com.atguigu.common.util.MD5; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; /** * 密码处理 * */ @Component public class CustomMd5PasswordEncoder implements PasswordEncoder { public String encode(CharSequence rawPassword) { return MD5.encrypt(rawPassword.toString()); } public boolean matches(CharSequence rawPassword, String encodedPassword) { return encodedPassword.equals(MD5.encrypt(rawPassword.toString())); } }

自定义了CustomMd5PasswordEncoder,现在需要告诉 Spring Security"用我的加密规则,别用默认的"。使用的地方在SecurityConfig配置类中。

@Configuration public class SecurityConf2 extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(password()); } @Bean PasswordEncoder password() { // 原来:return new BCryptPasswordEncoder(); // 现在:换成自定义的 return new CustomMd5PasswordEncoder(); // 使用你自定义的 MD5 加密 } }

2. 添加CustomUser对象

import com.atguigu.model.system.SysUser; import lombok.Data; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.User; import java.util.Collection; public class CustomUser extends User { /** * 我们自己的用户实体对象,要调取用户信息时直接获取这个实体对象 */ private SysUser sysUser; public CustomUser(SysUser sysUser, Collection<? extends GrantedAuthority> authorities) { super(sysUser.getUsername(), sysUser.getPassword(), authorities); this.sysUser = sysUser; } public SysUser getSysUser() { return sysUser; } public void setSysUser(SysUser sysUser) { this.sysUser = sysUser; } }

CustomUser在 UserDetailsService 的实现类中被创建并返回。

3. 实现 UserDetailsService 接口

@Service("userDetailsService") public class MyUserDetailService implements UserDetailsService { @Autowired private UserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 根据用户名查询数据库 User sysUser= userMapper.selectByUsername(username); if (sysUser== null) { return null; // 如果查不到用户,返回null,由provider来抛异常 // throw new UsernameNotFoundException("用户名不存在"); } // 2. 查询用户的权限列表 //List<String> permissions = userMapper.selectPermissionsByUserId(sysUser.getId()); // 3. 将权限转换为 GrantedAuthority 集合 // List<GrantedAuthority> authorities = permissions.stream() // .map(SimpleGrantedAuthority::new) // .collect(Collectors.toList()); // 4. 关键:在这里使用 CustomUser! return new CustomUser(sysUser, Collections.emptyList()); // 这里先返回一个空的权限 //return new CustomUser(sysUser, authorities); //第二个参数就是权限相关的 } }

AuthenticationManager校验所调用的三个组件就已经做好实现了。此时我们可以在默认的登录页面查看效果。

用户授权

基于权限控制

方法1:使用 hasAuthority方法

如果当前的主体具有指定的权限,则返回true,否则返回 false。

源码如下所示:

1. 在配置类设置当前访问地址有哪些权限

2. 在UserDetailsService实现的接口中,把返回User对象设置权限

方法2:使用 hasAnyAuthority方法

可以添加多个角色,源码如下

基于角色控制

方法1:使用 hasRole方法

security会自动在角色上添加ROLE_前缀,如写的是 sass, 则最后返回的是 ROLE_sass。源码如下所示:

所以在UserDetailsService实现的接口中,需要添加ROLE_前缀

方法2:使用 hasAnyRole方法

表示拥有其中一个角色就可以访问,用英文逗号隔开

总结角色与权限的区别

权限是具体控制某个操作的,如普通用户不能有查看和删除其他用户的权限。而角色是一个身份标识,在于这个身份有没有某个操作权限,如管理员可以有增删查改用户的权限,普通用户没有增删查改用户的权限。注意:

权限(Authority)- 细粒度,具体操作
hasAuthority("USER_DELETE") // 有删除用户权限?
hasAnyAuthority("USER_DELETE", "ADMIN_DELETE") // 有任一删除权限?


角色(Role)- 粗粒度,身份标识
hasRole("ADMIN") // 是管理员角色?
hasAnyRole("ADMIN", "MANAGER") // 是管理员或经理?

相关新闻

  • 论文信息检索新方法:文本与图像混合检索技术解析
  • Atari 2600电视广告制作技术解析:从80年代营销到现代游戏广告影响
  • AI写作如何真正“一稿通发”全平台?揭秘OpenAPI+动态模板引擎的3层适配架构(附GitHub高星开源方案)

最新新闻

  • C语言环境安装---visualstudio(Windows版)
  • 抖音去水印工具怎么选 2026 免费在线与手机方法整理 - 耶斯去水印
  • TMS320C5x DSP等待状态生成器原理与I/O空间配置实战
  • 戴尔笔记本风扇控制终极指南:如何用开源工具实现智能散热管理
  • 2026年最新教程:视频怎么变成动图 亲测可用的免费方法 - 效率工具研究所
  • AI智能体网站登录自动化:从原理到ChatGPT Work实战

日新闻

  • OpenClaw开源智能体网关:AI助手与即时通讯的完美融合
  • 写一个简单的sh脚本
  • 2026年 西安缝隙天线厂家:5G通信与车载天线专业定制供应商深度分析 - 卓企推荐

周新闻

  • 大连理工大学与东京大学联手打造的“主动型AI助手“
  • 170.2026年国家级科研瓶颈:超精密单点金刚石切削(SPDT)光学表面生成
  • SongBloom:革命性歌曲生成框架深度解析——如何通过交织自回归与扩散模型创作完整音乐

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号