SpringSecurity认证权限教程详解
从现在开始,我们要努力学习啦!今天我给大家带来《Spring Security 认证与权限控制教程》,感兴趣的朋友请继续看下去吧!下文中的内容我们主要会涉及到等等知识点,如果在阅读本文过程中有遇到不清楚的地方,欢迎留言呀!我们一起讨论,一起学习!
Spring Security的认证与授权流程基于Servlet过滤器链式处理。1. 认证流程:请求拦截后,用户提交凭证,由UsernamePasswordAuthenticationFilter提取凭证并交由AuthenticationManager处理;AuthenticationManager委托给DaoAuthenticationProvider等认证提供者,通过UserDetailsService加载用户信息并用PasswordEncoder验证密码;认证成功则将包含权限的Authentication对象存入SecurityContextHolder,失败则抛出AuthenticationException并重定向至登录页。2. 授权流程:已认证用户的Authentication对象存储于SecurityContextHolder,访问受保护资源时由AccessDecisionManager根据配置规则决策是否允许访问,其依赖RoleVoter、WebExpressionVoter等投票器评估角色或表达式;若满足策略则放行,否则抛出AccessDeniedException并重定向至拒绝页面。3. 配置方面:通过SecurityFilterChain Bean定义HttpSecurity对象来设置URL级别的访问规则,如permitAll、hasRole等,并可启用formLogin、logout等功能。4. 自定义逻辑:实现UserDetailsService接口以从数据库等来源加载用户信息;使用@PreAuthorize、@Secured等注解实现方法级别权限控制。5. 调试技巧:查看异常类型如BadCredentialsException、AccessDeniedException;开启DEBUG日志观察过滤器执行、认证授权过程;检查SecurityContextHolder中当前用户信息以定位问题。

Spring Security,这个在Spring生态中举足轻重的框架,它的核心在于回答两个基本问题:你是谁(认证,Authentication)和你能做什么(授权,Authorization)。它提供了一套全面且高度可配置的机制,来保护你的应用程序免受未经授权的访问,并确保用户只能执行他们被允许的操作。理解它的认证与授权流程,是掌握Spring应用安全的关键。

解决方案
Spring Security 的认证与授权流程,本质上是一个基于 Servlet 过滤器的链式处理过程。当一个请求进入你的Spring应用时,它会首先经过由 FilterChainProxy 管理的一系列 Security Filter。
认证流程:

- 请求拦截: 用户尝试访问一个受保护的资源(例如,一个需要登录才能访问的URL)。
- 凭证提交: 用户通常通过登录表单提交用户名和密码。
- 过滤器处理:
UsernamePasswordAuthenticationFilter(或类似的认证过滤器,如OAuth2过滤器)会拦截这个登录请求。 - 认证管理器: 过滤器将从请求中提取的凭证(通常是
UsernamePasswordAuthenticationToken)提交给AuthenticationManager。 - 认证提供者:
AuthenticationManager不直接处理认证,而是委托给一个或多个AuthenticationProvider。这些提供者才是真正执行认证逻辑的地方。- 例如,
DaoAuthenticationProvider会使用你提供的UserDetailsService来加载用户的详细信息(包括加密后的密码、角色等)。 - 然后,它会使用
PasswordEncoder来验证用户提交的密码是否与存储的密码匹配。
- 例如,
- 认证成功/失败:
- 如果认证成功,
AuthenticationProvider会返回一个完全填充的Authentication对象(包含用户的身份、权限等)。这个对象随后会被存储到SecurityContextHolder中,以便在整个会话期间访问。 - 如果认证失败(例如,密码错误),会抛出
AuthenticationException,并由认证失败处理器(AuthenticationFailureHandler)处理,通常是重定向到登录页面并显示错误信息。
- 如果认证成功,
- 会话管理: 认证成功后,Spring Security 还会处理会话管理,如创建或更新会话,以及“记住我”功能。
授权流程:
- 获取认证信息: 一旦用户通过认证,他们的
Authentication对象就存储在SecurityContextHolder中,可以在应用的任何地方访问。 - 资源访问: 用户尝试访问另一个受保护的资源(例如,一个只有管理员才能访问的页面或方法)。
- 授权决策点: 在访问资源之前,Spring Security 会检查当前用户的
Authentication对象所包含的权限(Authorities/Roles)是否满足访问该资源所需的权限。 - 访问决策管理器:
AccessDecisionManager是授权的核心,它会根据配置的授权规则来做出最终决定。 - 访问决策投票器:
AccessDecisionManager不自己做决定,而是咨询一个或多个AccessDecisionVoter。- 例如,
RoleVoter会检查用户是否拥有访问资源所需的特定角色。 WebExpressionVoter则会评估像hasRole('ADMIN')或hasAuthority('READ_PRIVILEGE')这样的Spring EL表达式。
- 例如,
- 授权结果:
- 如果所有投票器都同意或至少没有一个明确拒绝,并且满足了配置的投票策略,
AccessDecisionManager就会授予访问权限。 - 否则,会抛出
AccessDeniedException,并由访问拒绝处理器(AccessDeniedHandler)处理,通常是重定向到错误页面或显示“访问被拒绝”消息。
- 如果所有投票器都同意或至少没有一个明确拒绝,并且满足了配置的投票策略,
这个流程是高度模块化和可扩展的,几乎每个组件都可以被自定义实现所替换,以满足特定的安全需求。

Spring Security 中如何配置基本的认证与授权规则?
在Spring Security中配置认证和授权规则,通常围绕着 SecurityFilterChain Bean的定义展开。过去我们习惯用 WebSecurityConfigurerAdapter,但现在更推荐使用 SecurityFilterChain 来构建你的安全配置。
配置的核心在于 HttpSecurity 对象,它允许你链式地定义各种安全行为。
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity // 启用Spring Security的Web安全功能
public class SecurityConfig {
// 1. 配置密码编码器
@Bean
public PasswordEncoder passwordEncoder() {
// BCrypt 是目前推荐的密码哈希算法
return new BCryptPasswordEncoder();
}
// 2. 配置用户详情服务 (这里使用内存用户,实际应用会连接数据库)
@Bean
public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
UserDetails user = User.withUsername("user")
.password(passwordEncoder.encode("password")) // 密码需要编码
.roles("USER") // 赋予USER角色
.build();
UserDetails admin = User.withUsername("admin")
.password(passwordEncoder.encode("adminpass"))
.roles("ADMIN", "USER") // 赋予ADMIN和USER角色
.build();
return new InMemoryUserDetailsManager(user, admin);
}
// 3. 配置安全过滤器链
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/public/**").permitAll() // 允许所有用户访问 /public/** 路径
.requestMatchers("/admin/**").hasRole("ADMIN") // 只有ADMIN角色可以访问 /admin/**
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN") // USER或ADMIN角色可以访问 /user/**
.anyRequest().authenticated() // 其他所有请求都需要认证
)
.formLogin(form -> form
.loginPage("/login") // 自定义登录页面的URL
.defaultSuccessUrl("/dashboard", true) // 登录成功后跳转的URL,true表示总是跳转
.permitAll() // 登录相关的页面和请求允许所有用户访问
)
.logout(logout -> logout
.logoutUrl("/logout") // 登出URL
.logoutSuccessUrl("/login?logout") // 登出成功后跳转的URL
.permitAll()
)
.csrf(csrf -> csrf.disable()); // 禁用CSRF保护,仅为简化示例,生产环境不推荐
return http.build();
}
}这段代码展示了几个关键点:
PasswordEncoder: 这是个强制性的好习惯。密码绝不能明文存储,BCryptPasswordEncoder是业界推荐的方案。它会为每个密码生成一个随机的盐值,并进行多次哈希迭代,大大增加了破解难度。UserDetailsService: 这是Spring Security获取用户认证信息(用户名、密码、权限)的接口。在实际项目中,你会实现这个接口,从数据库或其他数据源加载用户数据。这里为了快速演示,用了内存用户。SecurityFilterChain: 这是配置HTTP请求安全的核心。authorizeHttpRequests():配置基于URL的授权规则。requestMatchers("/public/**").permitAll():这是一个常见的配置,允许任何人访问公共资源,比如静态文件、注册页面等。requestMatchers("/admin/**").hasRole("ADMIN"):只有拥有ADMIN角色的用户才能访问/admin下的所有路径。注意,hasRole会自动加上ROLE_前缀,所以如果你数据库里存的是ADMIN,这里就写ADMIN。anyRequest().authenticated():这是一个兜底规则,意味着除了前面明确放行的,所有其他请求都需要用户登录(认证)。
formLogin():启用表单登录。你可以指定自定义的登录页面 (loginPage),以及登录成功和失败后的跳转逻辑。logout():启用登出功能。csrf().disable():CSRF(跨站请求伪造)保护是Spring Security默认开启的,对于无状态API或一些特定场景可以禁用,但对于传统的Web应用,强烈建议保持开启。禁用它只是为了让示例更简单,避免在POST请求中额外处理CSRF令牌。
配置这些规则后,Spring Security 会自动为你处理用户认证、会话管理以及URL级别的权限检查。
如何实现自定义的用户认证逻辑和精细化权限控制?
当内置的内存用户或简单的基于角色的授权无法满足需求时,你需要深入定制Spring Security。这通常涉及到自定义 UserDetailsService、选择合适的 PasswordEncoder,以及利用方法级别的安全注解来实现更精细的权限控制。
1. 自定义 UserDetailsService
这是从数据库或其他外部源加载用户信息的关键。你需要实现 org.springframework.security.core.userdetails.UserDetailsService 接口,并重写 loadUserByUsername 方法。
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.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
// 假设这是一个用户仓库接口
interface UserRepository {
// 模拟从数据库查找用户
UserEntity findByUsername(String username);
}
// 模拟用户实体
class UserEntity {
private String username;
private String password; // 存储的是BCrypt加密后的密码
private List<String> roles; // 例如 "ROLE_ADMIN", "ROLE_USER"
// 构造函数、getter、setter省略
public UserEntity(String username, String password, String... roles) {
this.username = username;
this.password = password;
this.roles = Arrays.asList(roles);
}
public String getUsername() { return username; }
public String getPassword() { return password; }
public List<String> getRoles() { return roles; }
}
@Service // 标记为Spring组件
public class MyUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder; // 注入密码编码器
public MyUserDetailsService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
// 实际项目中,userRepository 会通过Spring Data JPA等注入
// 这里简单模拟一个用户
// 生产环境不应该这样初始化用户,应该通过注册等方式
if (this.userRepository instanceof MockUserRepository) {
((MockUserRepository) this.userRepository).addUser(
new UserEntity("dev", passwordEncoder.encode("devpass"), "ROLE_DEVELOPER", "ROLE_USER"),
new UserEntity("manager", passwordEncoder.encode("mgrpass"), "ROLE_MANAGER")
);
}
}
// 模拟一个简单的UserRepository实现
@Service
static class MockUserRepository implements UserRepository {
private final List<UserEntity> users = new ArrayList<>();
public void addUser(UserEntity... userEntities) {
users.addAll(Arrays.asList(userEntities));
}
@Override
public UserEntity findByUsername(String username) {
return users.stream()
.filter(u -> u.getUsername().equals(username))
.findFirst()
.orElse(null);
}
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserEntity userEntity = userRepository.findByUsername(username);
if (userEntity == null) {
throw new UsernameNotFoundException("用户 '" + username + "' 未找到");
}
// 构建Spring Security的UserDetails对象
// 注意:这里的roles需要转换为GrantedAuthority
return User.builder()
.username(userEntity.getUsername())
.password(userEntity.getPassword()) // 数据库中已加密的密码
.roles(userEntity.getRoles().toArray(new String[0])) // 传入角色名
.build();
}
}在你的 SecurityConfig 中,Spring Security 会自动发现并使用你定义的 UserDetailsService bean。
2. 方法级别的安全控制
除了URL级别的权限控制,Spring Security 还支持在方法级别进行更细粒度的权限检查。这通过 @EnableMethodSecurity (Spring Security 5.6+) 或 @EnableGlobalMethodSecurity (旧版本) 注解来启用。
在Spring Boot主类或配置类上添加:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; // 5.6+
@SpringBootApplication
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true) // 启用方法安全
public class YourApplication {
public static void main(String[] args) {
SpringApplication.run(YourApplication.class, args);
}
}然后,你可以在Service或Controller层的方法上使用以下注解:
@PreAuthorize: 在方法执行前进行权限检查。@PreAuthorize("hasRole('ADMIN')"): 只有ADMIN角色才能执行。@PreAuthorize("hasAuthority('product:write')"): 只有拥有 'product:write' 权限的用户才能执行。@PreAuthorize("#userId == authentication.principal.id"): 检查传入的userId参数是否与当前登录用户的ID一致。这对于“用户只能编辑自己的数据”这类场景非常有用。authentication.principal通常是你UserDetailsService返回的UserDetails对象。
@PostAuthorize: 在方法执行后进行权限检查。通常用于返回对象后的权限验证。@PostAuthorize("returnObject.owner == authentication.name"): 只有当返回对象的owner是当前用户时才允许返回。
@Secured: 基于角色的简单权限控制。@Secured({"ROLE_ADMIN", "ROLE_DEVELOPER"}): 只有ADMIN或DEVELOPER角色才能访问。
@RolesAllowed(JSR-250): 类似于@Secured,也是基于角色的。@RolesAllowed({"ADMIN", "MANAGER"}): 只有ADMIN或MANAGER角色才能访问。
示例:
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
@Service
public class ProductService {
@PreAuthorize("hasRole('ADMIN')")
public String createProduct(String productName) {
// 只有管理员才能创建产品
return "Product '" + productName + "' created by Admin.";
}
@PreAuthorize("hasAuthority('product:read') or hasRole('MANAGER')")
public String getProductDetails(Long productId) {
// 拥有 'product:read' 权限或 MANAGER 角色才能查看产品详情
return "Details for product ID: " + productId;
}
@PreAuthorize("#ownerId == authentication.principal.id")
public String updateProduct(Long productId, Long ownerId, String newName) {
// 只有产品所有者才能更新产品
// 假设 authentication.principal 是你的自定义 UserDetails 实例,其中有getId()方法
return "Product " + productId + " updated by owner " + ownerId + " to " + newName;
}
}通过这些方法,你可以构建一个既灵活又强大的权限模型,满足从粗粒度的角色控制到细粒度的资源实例级权限的各种需求。
常见问题与调试技巧:Spring Security 报错了怎么办?
Spring Security 的配置和流程虽然强大,但也确实有一些“坑”和让人困惑的地方。当遇到问题时,掌握一些调试技巧能让你事半功倍。
1. 识别异常类型
首先,看清楚抛出的异常是什么。这是最直接的线索:
BadCredentialsException: 认证失败,通常是用户名或密码不正确。UsernameNotFoundException:UserDetailsService找不到对应的用户。检查用户名是否正确,或loadUserByUsername实现是否有问题。DisabledException,LockedException,AccountExpiredException,CredentialsExpiredException: 用户账户状态异常。检查UserDetails实现中isEnabled(),isAccountNonLocked(),isAccountNonExpired(),isCredentialsNonExpired()方法的返回值。AccessDeniedException: 授权失败,用户没有访问资源的权限。这是最常见的授权错误。InvalidCsrfTokenException: CSRF令牌无效。通常发生在POST请求中没有正确携带CSRF令牌,或者令牌过期。AuthenticationCredentialsNotFoundException: 请求未认证就尝试访问受保护资源。
2. 开启 Spring Security Debug 日志
这是排查问题的“瑞士军刀”。将 org.springframework.security 包的日志级别设置为 DEBUG,你会看到Spring Security处理请求的详细过程,包括:
- 哪些过滤器被执行了?
- 认证尝试的每一步(
AuthenticationManager如何委托给AuthenticationProvider)。 - 权限评估的详细过程(
AccessDecisionManager如何咨询AccessDecisionVoter)。 - 哪些URL模式被匹配了,以及它们对应的权限要求。
在 application.properties 或 application.yml 中:
# application.properties logging.level.org.springframework.security=DEBUG
# application.yml
logging:
level:
org.springframework.security: DEBUG3. 检查 SecurityContextHolder
在认证成功后,当前用户的 Authentication 对象会被存储在 SecurityContextHolder 中。你可以在任何地方通过 SecurityContextHolder.getContext().getAuthentication() 来获取它。
- 登录后检查: 登录成功后,在某个控制器或服务方法中打印
authentication.getPrincipal()和authentication.getAuthorities()。这能帮你确认当前用户是否被正确认证,以及拥有哪些权限。 - 授权失败时检查: 如果发生
AccessDeniedException,在异常处理或调试时检查SecurityContextHolder,看看当前用户是否已经认证,以及其权限是否符合预期。有时候,用户可能登录了,但分配的角色不对,或者权限名称写错了。
**4
今天带大家了解了的相关知识,希望对你有所帮助;关于文章的技术知识我们会一点点深入介绍,欢迎大家关注golang学习网公众号,一起学习编程~
HTML画布基础:6个图形教程详解
- 上一篇
- HTML画布基础:6个图形教程详解
- 下一篇
- Golang物联网搭建:支持MQTT与CoAP协议
-
- 文章 · java教程 | 20分钟前 |
- Jackson解析JSON教程:Java数据处理指南
- 346浏览 收藏
-
- 文章 · java教程 | 35分钟前 | 异常处理 try-with-resources IOException 日志框架 堆栈日志
- Java捕获IOException并记录堆栈日志方法
- 119浏览 收藏
-
- 文章 · java教程 | 38分钟前 |
- Java实现多人协同编辑文档方法解析
- 123浏览 收藏
-
- 文章 · java教程 | 48分钟前 |
- 国际化错误提示处理方法详解
- 178浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Windows安装Java环境配置教程
- 357浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Java类型转换技巧与实战应用
- 150浏览 收藏
-
- 文章 · java教程 | 1小时前 | java GUI 数据处理 JFreeChart 学生成绩可视化
- Java学生成绩可视化实现教程
- 263浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Kafka整合Java微服务实战教程
- 309浏览 收藏
-
- 文章 · java教程 | 2小时前 |
- Java多线程生产者消费者实现详解
- 362浏览 收藏
-
- 文章 · java教程 | 2小时前 |
- JDK8安装后IDE不识别解决方法
- 350浏览 收藏
-
- 文章 · java教程 | 3小时前 |
- JavaList排序优化方法解析
- 225浏览 收藏
-
- 文章 · java教程 | 3小时前 |
- Java中toMap构建字典的技巧
- 488浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3172次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3383次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3412次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4517次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3792次使用
-
- 提升Java功能开发效率的有力工具:微服务架构
- 2023-10-06 501浏览
-
- 掌握Java海康SDK二次开发的必备技巧
- 2023-10-01 501浏览
-
- 如何使用java实现桶排序算法
- 2023-10-03 501浏览
-
- Java开发实战经验:如何优化开发逻辑
- 2023-10-31 501浏览
-
- 如何使用Java中的Math.max()方法比较两个数的大小?
- 2023-11-18 501浏览

