当前位置: 首页 > news >正文

【Spring Boot + Spring Security】从入门到源码精通:藏经阁权限设计与过滤器链深度解析

版本说明:本教程基于 Spring Boot 3.x 和 Spring Security 6.x 版本,采用了新的 Lambda DSL 配置风格。如果你使用的是旧版本,配置方式会略有不同。


第一回:初来乍到,藏经阁危在旦夕

我叫小白,是一名刚飞升上来的"代码修仙者"。我的第一份差事,就是担任"星宿宗"的藏经阁管理员。

藏经阁,那可是宗门的核心重地:

  • 一楼: 公共休息区,谁都能进 (//home)。

  • 二楼: 普通秘籍区,只有本门弟子才能翻阅 (/books/**)。

  • 三楼: 绝学禁区,只有长老才能进入 (/secret/**)。

我刚上任第一天,老阁主就拍拍我的肩膀,语重心长地说:"小白啊,现在的藏经阁,谁都能上三楼,跟逛菜市场似的。我们的《如来神掌》和《九阳神功》秘籍危矣!你的任务,就是给它建立起一套'护阁大阵'!"

我一脸懵:"阁主,阵法一道,晚辈才疏学浅啊..."

老阁主神秘一笑,掏出一本古籍:"此乃 Spring Boot 心法,能让你快速开宗立派。再配合这本 Spring Security 阵法大全,可布下天罗地网!"


第二回:开宗立派,初布大阵 (项目初始化)

推理时刻: 要布阵,先得有地盘。用 Spring Boot 创建项目是最快的方式。

我按照古籍记载,在 pom.xml 这个"灵气汇聚阵"中,引入了两大核心依赖:

<!-- Spring Boot 核心心法,提供内力 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Security 阵法核心,提供规则 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>

然后,我创建了几个简单的"房间"(Controller):

@RestController
public class LibraryController {@GetMapping("/")public String home() {return "欢迎来到藏经阁公共休息区!";}@GetMapping("/books/java")public String getJavaBook() {return "《Java 编程思想》";}@GetMapping("/secret/kungfu")public String getSecretKungfu() {return "《如来神掌》秘籍!";}
}

我信心满满地启动了应用 (SpringBootApplication.run)

诡异的事情发生了! 我访问首页 http://localhost:8080,没有看到欢迎语,反而跳转到了一个陌生的登录页面!用户名是 user,密码则在控制台的一长串乱码里。

严谨推理:

  1. 一旦引入 spring-boot-starter-security,Spring Boot 的 自动配置 机制就启动了。

  2. 它会为应用 自动套上一个默认的安全结界

  3. 这个默认结界规定:所有请求都需要认证

  4. 它还会自动生成一个随机密码的用户,并提供一个基础的登录页。

关于默认密码的详细说明:

当你第一次启动 Spring Security 应用时,会在控制台看到类似这样的信息:

Using generated security password: 9a2b8f7c-3d6e-4a5b-8c9d-0e1f2a3b4c5d

This generated password is for development use only. Your security configuration must be updated before running in production.

这个随机密码的生成逻辑:

  • Spring Boot 检测到项目中存在 Spring Security 但没有显式配置 UserDetailsService 或 AuthenticationManager 时

  • 会自动创建一个 InMemoryUserDetailsManager 实例

  • 生成一个用户名为 user 的账户

  • 密码是通过 UUID 随机生成器 创建的,确保每次启动应用时都不同

  • 这是一种安全措施,强制开发者在生产环境中配置真实的用户管理

示例控制台输出:

org.springframework.security.web.context.SecurityContextHolderFilter@34567890, ...]
2023-10-01T10:30:45.124+08:00  INFO 12345 --- [           main] .s.s.UserDetailsServiceAutoConfiguration : 

Using generated security password: 9a2b8f7c-3d6e-4a5b-8c9d-0e1f2a3b4c5d

This generated password is for development use only. Your security configuration must be updated before running in production.

所以,我还没开始布阵,Spring Security 已经用它的"默认阵法"把我的藏经阁保护起来了——虽然保护得有点蠢,连公共区都进不去。


第三回:自定义大阵,权限分明 (核心配置)

老阁主看了直摇头:"你这阵法敌我不分啊!看我的。"

他带我创建了一个名为 SecurityConfig 的"阵法核心枢纽"。

@Configuration
@EnableWebSecurity // 宣告:此乃本宗自定义安全大阵!
public class SecurityConfig {@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {// HttpSecurity 就是我们的"阵法编织器"http.authorizeHttpRequests(authz -> authz.requestMatchers("/").permitAll()           // 公共区,无需认证.requestMatchers("/books/**").hasRole("USER") // 普通秘籍区,需"弟子"身份.requestMatchers("/secret/**").hasRole("ADMIN") // 禁区,需"长老"身份.anyRequest().authenticated()               // 其他所有请求,都需要登录).formLogin(withDefaults()); // 使用默认的登录页面return http.build();}// 创建"身份令牌"发放处 - 基于真实数据库查询@Beanpublic UserDetailsService userDetailsService(UserRepository userRepository) {return username -> {// 从数据库中根据用户名查询用户信息UserEntity userEntity = userRepository.findByUsername(username);if (userEntity == null) {throw new UsernameNotFoundException("用户不存在: " + username);}// 查询用户的角色权限列表List<SimpleGrantedAuthority> authorities = userRepository.findRolesByUserId(userEntity.getId()).stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role)).collect(Collectors.toList());// 构建Spring Security需要的UserDetails对象return org.springframework.security.core.userdetails.User.builder().username(userEntity.getUsername()).password(userEntity.getPassword()) // 数据库中存储的应该是加密后的密码.authorities(authorities).accountExpired(!userEntity.isAccountNonExpired()).accountLocked(!userEntity.isAccountNonLocked()).credentialsExpired(!userEntity.isCredentialsNonExpired()).disabled(!userEntity.isEnabled()).build();};}// 密码编码器 - 用于密码加密和验证@Beanpublic PasswordEncoder passwordEncoder() {// 使用BCrypt强哈希函数进行密码加密return new BCryptPasswordEncoder();}
}

对应的数据库实体类和Repository:

// 用户实体类
@Entity
@Table(name = "users")
public class UserEntity {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@Column(unique = true, nullable = false)private String username;@Column(nullable = false)private String password;private boolean accountNonExpired = true;private boolean accountNonLocked = true;private boolean credentialsNonExpired = true;private boolean enabled = true;// getters and setters
}// 角色实体类
@Entity
@Table(name = "roles")
public class RoleEntity {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@Column(unique = true, nullable = false)private String name;// getters and setters
}// 用户角色关联实体类
@Entity
@Table(name = "user_roles")
public class UserRoleEntity {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@ManyToOne@JoinColumn(name = "user_id")private UserEntity user;@ManyToOne@JoinColumn(name = "role_id")private RoleEntity role;// getters and setters
}// 用户Repository
@Repository
public interface UserRepository extends JpaRepository<UserEntity, Long> {// 根据用户名查找用户UserEntity findByUsername(String username);// 根据用户ID查找角色列表@Query("SELECT r.name FROM RoleEntity r " +"JOIN UserRoleEntity ur ON ur.role.id = r.id " +"WHERE ur.user.id = :userId")List<String> findRolesByUserId(Long userId);
}

数据库表结构示例:

-- 用户表
CREATE TABLE users (id BIGINT AUTO_INCREMENT PRIMARY KEY,username VARCHAR(50) UNIQUE NOT NULL,password VARCHAR(100) NOT NULL,  -- 存储BCrypt加密后的密码account_non_expired BOOLEAN DEFAULT TRUE,account_non_locked BOOLEAN DEFAULT TRUE,credentials_non_expired BOOLEAN DEFAULT TRUE,enabled BOOLEAN DEFAULT TRUE
);-- 角色表
CREATE TABLE roles (id BIGINT AUTO_INCREMENT PRIMARY KEY,name VARCHAR(50) UNIQUE NOT NULL
);-- 用户角色关联表
CREATE TABLE user_roles (id BIGINT AUTO_INCREMENT PRIMARY KEY,user_id BIGINT NOT NULL,role_id BIGINT NOT NULL,FOREIGN KEY (user_id) REFERENCES users(id),FOREIGN KEY (role_id) REFERENCES roles(id)
);-- 插入测试数据(密码都是"password",但经过BCrypt加密)
INSERT INTO users (username, password) VALUES 
('disciple', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVwUiW'),
('elder', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVwUiW');INSERT INTO roles (name) VALUES ('USER'), ('ADMIN');INSERT INTO user_roles (user_id, role_id) VALUES 
(1, 1), -- disciple 有 USER 角色
(2, 1), -- elder 有 USER 角色
(2, 2); -- elder 有 ADMIN 角色

故事解读与严谨推理:

  1. @EnableWebSecurity: 这是启动我们自定义安全规则的"咒语",它告诉 Spring Boot:"别用你的默认阵法了,用我的!"

  2. HttpSecurity: 这是整个故事的核心,我们的"阵法编织器"。通过配置它,我们可以精确控制访问规则。

  3. authorizeHttpRequests: 这是 权限规则定义,是我们大阵的"识别逻辑"。

    • .requestMatchers("/").permitAll(): 匹配根路径,permitAll() 表示完全放行。推理: 这里不进行任何安全拦截。

    • .requestMatchers("/books/**").hasRole("USER"): 匹配 /books/ 开头的所有请求,hasRole("USER") 表示请求者必须拥有 ROLE_USER 角色。推理: 系统会检查当前登录用户是否具备该角色。

    • .anyRequest().authenticated(): 这是一个兜底策略,对于其他所有请求,只需要登录(认证)即可,不管是什么角色。

  4. UserDetailsService: 这是 用户详情服务,是我们的"身份令牌发放处"。现在我们改为从真实数据库中查询用户信息和角色。推理: 当用户登录时,Spring Security 会调用这个服务,根据用户名从数据库查找用户的密码和角色信息,用于验证身份和授权。

  5. PasswordEncoder: 这是 密码编码器,使用 BCrypt 强哈希算法对密码进行加密和验证,确保密码安全。

现在,让我们测试一下大阵效果:

  • 访问 /: 直接进入!(符合 permitAll)

  • 访问 /books/java: 跳转到登录页。

    • 用 disciple/password 登录:成功看到《Java 编程思想》!

    • 用 elder/password 登录:也能看到!(因为长老也有 USER 角色)

  • 访问 /secret/kungfu: 跳转到登录页。

    • 用 disciple/password 登录:结果:403 Forbidden 错误!禁止访问! 推理: 弟子只有 USER 角色,没有 ADMIN 角色,大阵识别出他权限不足。

    • 用 elder/password 登录:成功看到《如来神掌》!

完美!我们的护阁大阵开始起作用了!


第四回:识破阵法玄机——内置拦截器(过滤器链)

老阁主看我悟性不错,便带我走到大阵的幕后。只见一道道灵光(HTTP请求)进入藏经阁,需要经过一个长长的"过滤走廊",走廊里有各式各样的"拦截器弟子"在执勤。

"看,这就是 Spring Security 的 过滤器链 (Filter Chain),"老阁主说,"每个过滤器都是一个大阵的组成部分。"

几个你必须认识的"核心执勤弟子":

  1. SecurityContextPersistenceFilter (身份凭证保管员)

    • 职责: 当一个请求来时,他从 Session 中取出用户的登录凭证(Authentication)。请求结束时,他再把凭证存回去。这样用户在一个会话中只需要登录一次。

  2. UsernamePasswordAuthenticationFilter (账房先生)

    • 职责: 专管表单登录。当你在登录页提交用户名和密码时,就是他来处理的。他负责验证你的身份,并给你发放"身份令牌"。

  3. FilterSecurityInterceptor (权限判官)

    • 职责: 这是 最重要 的拦截器之一!我们之前在 HttpSecurity 里配置的所有访问规则 (hasRolepermitAll 等),最终都是由他来执行的。他会在请求到达 Controller 之前,根据规则决定是"放行"还是"抛出异常(403)"。

  4. ExceptionTranslationFilter (异常处理外交官)

    • 职责: 他专门处理 FilterSecurityInterceptor 抛出的异常。

    • 推理流程:

      • 如果 FilterSecurityInterceptor 说:"此人未认证!",外交官就会引导用户去登录页(发起认证)。

      • 如果 FilterSecurityInterceptor 说:"此人权限不足!",外交官就会返回 403 Forbidden 错误。

推理链条总结:
一个请求 GET /secret/kungfu 的冒险之旅:

  1. SecurityContextPersistenceFilter 从 Session 中取出令牌,发现是"弟子"。

  2. 请求一路向前,没有触发登录,所以绕过了 UsernamePasswordAuthenticationFilter

  3. 到达 FilterSecurityInterceptor!判官拿出规则手册一查:"/secret/** 需要 ADMIN 角色"。再一看令牌:"弟子,角色 USER"。权限不足!抛出异常!

  4. ExceptionTranslationFilter 接到"权限不足"异常,直接返回 403 状态码。


第五回:阵法玄机——核心拦截器与源码详解

老阁主将我带到藏经阁的"过滤走廊"幕后,指着那一排排正在执勤的"拦截器弟子",说道:"小白,知其然,更要知其所以然。今日,我便传你这护阁大阵的核心运转法则!"

第一式:SecurityContextPersistenceFilter (身份凭证保管员)

职责:他是整个过滤链的第一个和最后一个执勤弟子,负责在请求开始时从Session中取出用户凭证,并在请求结束时清理现场,防止信息泄露。

/*** SecurityContextPersistenceFilter - 身份凭证保管员* * 核心源码逻辑分析:* 1. 在请求开始时,从Session中加载SecurityContext(安全上下文)* 2. 将SecurityContext设置到SecurityContextHolder中,供后续过滤器使用* 3. 在请求结束时,清理SecurityContextHolder,防止线程复用导致的信息泄露*/
public class SecurityContextPersistenceFilter extends GenericFilterBean {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {// 1. 在请求开始时,尝试从Session中获取SecurityContext(安全上下文)// SecurityContext 包含当前用户的认证信息 AuthenticationHttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);SecurityContext contextBeforeChainExecution = repo.loadContext(holder);try {// 2. 将获取到的SecurityContext设置到SecurityContextHolder中// SecurityContextHolder相当于一个全局的、线程安全的储物柜,后续过滤器都从这里拿用户信息// 关键点:使用ThreadLocal实现,确保每个请求线程都有自己的安全上下文SecurityContextHolder.setContext(contextBeforeChainExecution);// 3. 放行,让请求继续走后续的过滤器链// 这里会调用下一个过滤器,最终会调用到FilterSecurityInterceptor进行权限判断chain.doFilter(holder.getRequest(), holder.getResponse());} finally {// 4. 请求结束后,无论如何,清理SecurityContextHolder// 这是非常重要的安全措施!防止线程池复用时,用户信息泄露到其他请求SecurityContextHolder.clearContext();// 同时也会将更新后的SecurityContext保存回Session(如果认证状态有变化)SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());}}
}

第二式:UsernamePasswordAuthenticationFilter (账房先生)

职责:专管表单登录,默认拦截 /login 的POST请求。他负责接收用户提交的用户名密码,并尝试进行认证。

/*** UsernamePasswordAuthenticationFilter - 账房先生* * 核心源码逻辑分析:* 1. 只处理/login路径的POST请求* 2. 从请求参数中提取用户名和密码* 3. 创建未认证的Authentication令牌* 4. 委托给AuthenticationManager进行实际认证*/
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {// 默认只处理 /login 的 POST 请求// 这就是为什么我们提交登录表单时必须是POST到/loginpublic UsernamePasswordAuthenticationFilter() {super(new AntPathRequestMatcher("/login", "POST"));}@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)throws AuthenticationException {// 1. 从请求中获取用户名和密码// 默认从username和password参数获取,但可以重写这些方法String username = obtainUsername(request);String password = obtainPassword(request);if (username == null) {username = "";}if (password == null) {password = "";}username = username.trim();// 2. 使用获取到的信息,创建一个「未认证」的令牌 (Authentication)// 此时的Authentication的authenticated属性为falseUsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);// 3. 设置一些额外的信息,如远程IP地址、Session ID等// 这些信息在后续的审计日志等场景中很有用setDetails(request, authRequest);// 4. 将这个令牌交给「认证经理」(AuthenticationManager) 进行核实// AuthenticationManager会找到合适的AuthenticationProvider来执行认证// 如果认证成功,返回一个充满详细信息的Authentication对象(authenticated=true)// 如果失败,则抛出AuthenticationException异常return this.getAuthenticationManager().authenticate(authRequest);}// 从请求中获取用户名的默认实现protected String obtainUsername(HttpServletRequest request) {return request.getParameter("username");}// 从请求中获取密码的默认实现  protected String obtainPassword(HttpServletRequest request) {return request.getParameter("password");}
}

第三式:ExceptionTranslationFilter (异常处理外交官)

职责:他站在 FilterSecurityInterceptor 的身后,专门处理在安全过滤链中抛出的两类异常:AuthenticationException(认证异常)和 AccessDeniedException(访问拒绝异常)。

/*** ExceptionTranslationFilter - 异常处理外交官* * 核心源码逻辑分析:* 1. 捕获后续过滤器抛出的异常* 2. 如果是AuthenticationException,启动认证流程* 3. 如果是AccessDeniedException,检查用户是否已认证* 4. 根据情况返回登录页面或403错误*/
public class ExceptionTranslationFilter extends GenericFilterBean {@Overridepublic void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) req;HttpServletResponse response = (HttpServletResponse) res;try {// 放行,让请求继续往下走(主要是走向最终的权限判官 FilterSecurityInterceptor)chain.doFilter(request, response);} catch (Exception e) {// 捕获异常并进行判断Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(e);// 1. 如果是「认证异常」(用户未登录或登录失败)RuntimeException ase = (AuthenticationException) this.throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);if (ase != null) {// 启动认证流程 - 比如跳转到登录页handleAuthenticationException(request, response, chain, (AuthenticationException) ase);return;}// 2. 如果是「访问拒绝异常」(权限不足)ase = (AccessDeniedException) this.throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);if (ase != null) {// 处理权限不足的情况handleAccessDeniedException(request, response, chain, (AccessDeniedException) ase);return;}}}private void handleAuthenticationException(HttpServletRequest request,HttpServletResponse response, FilterChain chain, AuthenticationException failed)throws IOException, ServletException {// 将AuthenticationException信息保存到SecurityContextHolder中SecurityContextHolder.getContext().setAuthentication(null);// 重要:触发认证入口点,通常是跳转到登录页面// 在表单登录中,这会重定向到登录页this.authenticationEntryPoint.commence(request, response, failed);}private void handleAccessDeniedException(HttpServletRequest request,HttpServletResponse response, FilterChain chain, AccessDeniedException denied)throws IOException, ServletException {// 获取当前认证信息Authentication authentication = SecurityContextHolder.getContext().getAuthentication();// 关键判断:如果用户是匿名用户(未登录)或者RememberMe用户if (authentication == null || authentication instanceof AnonymousAuthenticationToken) {// 用户未登录,触发认证流程this.authenticationEntryPoint.commence(request, response,new InsufficientAuthenticationException("Full authentication is required to access this resource"));} else {// 用户已登录但权限不足 - 返回403 Forbidden错误this.accessDeniedHandler.handle(request, response, denied);}}
}

第四式:FilterSecurityInterceptor (权限判官)

职责:这是整个安全链的最后一关,负责根据配置的权限规则做出最终的访问决策。

/*** FilterSecurityInterceptor - 权限判官* * 核心源码逻辑分析:* 1. 在请求到达Controller前进行拦截* 2. 从SecurityContextHolder获取当前用户认证信息* 3. 调用AccessDecisionManager进行权限决策* 4. 根据决策结果决定是否放行*/
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {// 创建FilterInvocation对象,封装请求信息FilterInvocation fi = new FilterInvocation(request, response, chain);// 核心:进行权限校验InterceptorStatusToken token = super.beforeInvocation(fi);try {// 如果权限校验通过,执行后续的过滤器链,最终到达Controllerfi.getChain().doFilter(fi.getRequest(), fi.getResponse());} finally {// 请求完成后的清理工作super.finallyInvocation(token);}// 调用完成后的后置处理super.afterInvocation(token, null);}// 在AbstractSecurityInterceptor中定义的核心方法protected InterceptorStatusToken beforeInvocation(Object object) {// 1. 获取当前请求对应的配置属性(就是我们配置的hasRole、permitAll等规则)Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);if (attributes == null || attributes.isEmpty()) {// 如果没有配置安全规则,直接放行return null;}// 2. 从SecurityContextHolder中获取当前认证信息Authentication authentication = SecurityContextHolder.getContext().getAuthentication();// 3. 核心:委托给AccessDecisionManager进行权限决策// AccessDecisionManager会调用一系列的AccessDecisionVoter进行投票try {this.accessDecisionManager.decide(authentication, object, attributes);} catch (AccessDeniedException accessDeniedException) {// 如果决策结果是拒绝访问,抛出AccessDeniedException// 这个异常会被前面的ExceptionTranslationFilter捕获throw accessDeniedException;}// 4. 如果权限校验通过,创建并返回InterceptorStatusTokenreturn new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);}
}

完整请求流程的源码级推演:

/*** 一个请求的完整生命周期 - 源码级推演* 请求:GET /secret/kungfu (由已认证但无权限的"弟子"用户发起)*/
public void demonstrateRequestFlow() {// 1. SecurityContextPersistenceFilter从Session中恢复SecurityContext//    SecurityContext包含弟子用户的Authentication对象(authenticated=true, authorities=[ROLE_USER])// 2. 请求经过一系列过滤器,到达FilterSecurityInterceptor// 3. FilterSecurityInterceptor.beforeInvocation()被调用://    - 获取配置属性: [hasRole('ADMIN')]//    - 获取当前认证: 弟子用户(只有ROLE_USER角色)//    - 调用AccessDecisionManager.decide()// 4. AccessDecisionManager进行投票决策://    - RoleVoter检查:用户有[ROLE_USER],需要[ROLE_ADMIN]//    - 投票结果:ACCESS_DENIED// 5. AccessDecisionManager抛出AccessDeniedException// 6. ExceptionTranslationFilter捕获AccessDeniedException://    - 检查用户已认证 → 调用AccessDeniedHandler//    - 返回403 Forbidden响应// 7. 请求结束,SecurityContextPersistenceFilter清理SecurityContextHolder
}

终回:大道至简,万法归宗

通过这场"藏经阁守护战"和深入的源码分析,我们明白了:

  1. 集成如此简单: 只需一个依赖,Spring Boot 就为你带来了 Spring Security 的强大能力。

  2. 默认密码机制: Spring Security 会自动生成随机密码并在控制台显示,这是开发阶段的保护措施。

  3. 配置核心是 HttpSecurity 就像编织阵法,你用它来精确指定 哪些路径需要什么权限

  4. 用户与角色: 通过 UserDetailsService 可以从数据库查询真实的用户和权限信息。

  5. 密码安全: 使用 BCryptPasswordEncoder 对密码进行强加密存储。

  6. 理解过滤器链: 明白请求背后四大核心过滤器的工作流程和源码实现,是解决复杂权限问题的钥匙。

源码层面的核心收获:

  • SecurityContextPersistenceFilter 通过 ThreadLocal 实现请求级别的安全上下文隔离

  • UsernamePasswordAuthenticationFilter 是认证的入口,负责创建初始的 Authentication 对象

  • FilterSecurityInterceptor 是权限决策的最终执行者,调用 AccessDecisionManager 进行投票

  • ExceptionTranslationFilter 是异常处理的统一出口,将技术异常转换为用户友好的响应

后续修炼方向(你的下一篇博客主题):

  • 自定义登录页面: 替换默认的登录页,设计符合宗门风格的登录界面。

  • 记住我功能: 实现"记住我"功能,让弟子们一段时间内无需重复登录。

  • 登录成功处理: 自定义登录成功后的跳转逻辑,根据用户角色跳转到不同页面。

  • 退出登录: 实现安全的退出登录功能,清理用户会话。

  • 探索 JWT: 为你的前后端分离架构,打造无状态的令牌安全机制。

http://www.dtcms.com/a/544672.html

相关文章:

  • 《嵌入式硬件(十七):基于IMX6ULL的温度传感器LM75a操作》
  • 用 Go 手搓一个内网 DNS 服务器:从此告别 IP 地址,用域名畅游家庭网络!
  • Rust async/await 语法糖的展开原理:从表象到本质
  • Rust 零拷贝技术:从所有权到系统调用的性能优化之道
  • 浪潮服务器装linux系统步骤
  • 视频网站服务器带宽需要多少?视频网站服务器配置要求
  • 《嵌入式硬件(十八):基于IMX6ULL的ADC操作》
  • 注册网站发财的富豪北京公司如何做网站
  • 仓颉语言异常捕获机制深度解析
  • 基于SAP.NET Core Web APP(MVC)的医疗记录管理系统完整开发指南
  • 咖啡网站建设设计规划书wordpress修改首页网址导航
  • C#WPF UI路由事件:事件冒泡与隧道机制
  • 神经网络时序预测融合宏观变量的ETF动态止盈系统设计与实现
  • 分布式Session会话实现方案
  • Java创建【线程池】的方法
  • 相机直播,HDMI线怎么选择
  • 做外贸哪些国外网站可以推广上海中学地址
  • HFSS微带线仿真
  • 推荐常州微信网站建设网站友链怎么做
  • 多模态的大模型文本分类模型代码(二)——模型初步运行
  • 强化特权用户监控,守护Active Directory核心安全
  • Kafka Consumer 消费流程详解
  • 安全守护者:防爆外壳在气体传感器领域的关键应用
  • 【JavaEE初阶】网络经典面试题小小结
  • 以太网多参量传感器:构筑工业安全与环境稳定的“数据堡垒”
  • pinia-storeToRefs方法
  • 基于用户的协同过滤算法理解
  • jsp书城网站开发中国建设银行重庆网站首页
  • 郑州网站建设公司排名湖南省城乡住房建设厅网站
  • 蓝牙钥匙 第4次 蓝牙协议栈深度剖析:从物理层到应用层的完整架构解析