ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Spring Security实战:从认证授权到权限控制完整落地指南

Spring Security实战:从认证授权到权限控制完整落地指南 之前在业务系统里接入 Spring Security 做登录认证和权限控制时说实话踩了不少坑。网上资料虽然多但很多是零散片段要么只讲入门 Demo要么直接甩一堆配置让人照着抄出了问题也不知道怎么排查。这篇文章打算围绕 Spring Security 在 Spring Boot 项目中的落地整理一套完整的实操方案从核心概念、环境搭建、认证流程、权限配置到常见报错和最佳实践尽量做到让新手能看懂原理、让有基础的开发者能快速复用代码。文章里涉及到的代码和配置都基于一个最简单的 RBAC 权限模型来设计没有引入太复杂的微服务、网关、分布式会话等概念方便把注意力集中到 Spring Security 本身。1. 认清 Spring Security 在项目中到底解决什么问题1.1 认证和授权是两个不同的问题很多初学者刚接触 Spring Security 的时候容易把“认证”和“授权”混在一起。实际上这是两件事认证Authentication解决的是“你是谁”的问题也就是说系统需要确认当前访问的人确实是某个合法用户。授权Authorization解决的是“你能干什么”的问题也就是确认这个用户有没有权限访问某个接口、操作某个资源。Spring Security 的底层是一组过滤器链请求进入应用后会先经过认证相关的过滤器确认用户身份然后再根据配置的权限规则判断当前用户能否访问目标资源。理解这一点很重要因为很多配置问题、权限不生效的问题本质上是没有把这两条链路拆开看。1.2 Spring Security 的核心组件我们在项目中接触最多的几个组件包括SecurityFilterChain负责定义哪些请求需要认证、哪些请求放行、使用什么认证方式。AuthenticationManager认证管理器负责协调具体的认证逻辑。UserDetailsService负责根据用户名加载用户信息。PasswordEncoder负责密码加密和校验。SecurityContextHolder存储当前登录用户信息的容器。这些组件之间的关系可以这样理解请求进来之后过滤器把用户名和密码封装成一个 Authentication 对象交给 AuthenticationManager 去校验校验过程中会通过 UserDetailsService 查询用户信息再用 PasswordEncoder 比对密码。校验通过后Authentication 对象会被放到 SecurityContextHolder 中后续的业务代码就能获取当前登录用户了。1.3 为什么需要掌握 Spring Security只要项目里涉及到用户登录、后台管理、接口权限控制就绕不开 Spring Security。虽然也可以自己写拦截器、写注解、写 Session 校验但 Spring Security 提供了一套完整的、经过大量生产环境验证的方案尤其在密码加密、会话管理、CSRF 防护、方法级权限控制等方面比自己造轮子要可靠得多。另外Spring Security 也是 Spring Boot 生态中与 Spring 家族集成最顺畅的安全框架和 Spring Boot、Spring Cloud 搭配使用时各方面的支持都比较完善。2. 环境准备与项目初始化2.1 版本说明本文示例使用以下环境JDK 1.8 或更高版本Spring Boot 2.7.xSpring Security 5.7.xSpring Boot 2.7 默认引入的版本Maven 3.6IDEIntelliJ IDEA需要说明的是Spring Security 5.7 之后配置方式有一些调整WebSecurityConfigurerAdapter 已经废弃官方推荐直接使用 SecurityFilterChain Bean 的方式进行配置。本文采用这种新写法。如果你使用的是 Spring Boot 3.x对应的 Spring Security 是 6.x部分 API 有所变化建议参考官方文档调整。2.2 创建项目并引入依赖先创建一个 Spring Boot 项目在pom.xml中引入 Web 和 Spring Security 依赖。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies !-- Web 依赖 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- Spring Security 依赖 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-security/artifactId /dependency !-- Lombok简化实体类代码 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency !-- 测试依赖 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency /dependencies引入spring-boot-starter-security之后再启动项目控制台会输出一个默认密码。此时访问任意接口都会弹出登录页面默认用户名是user密码就是控制台打印的那串随机 UUID。这就是 Spring Security 的默认保护机制生效了。2.3 项目结构为了方便后续扩展建议把代码按职责分层。本文的完整项目结构如下src/main/java/com/example/securitydemo ├── SecurityDemoApplication.java ├── config │ └── SecurityConfig.java ├── controller │ └── UserController.java ├── entity │ └── User.java ├── mapper │ └── UserMapper.java ├── service │ ├── UserService.java │ └── impl │ └── UserServiceImpl.java └── vo ├── LoginRequest.java └── Result.java下面会按照这个结构依次创建各个文件。为了让示例尽量简单用户数据暂时存在内存中不连接数据库等理解核心流程之后再替换为 MyBatis 或 JPA 实现。3. 核心概念与配置拆解3.1 密码加密PasswordEncoder 不能省略在实际项目中用户密码绝对不能以明文形式存储。Spring Security 提供了PasswordEncoder接口常用的实现有BCryptPasswordEncoder推荐使用BCrypt 算法会自动加盐每次加密结果都不一样。DelegatingPasswordEncoderSpring Boot 默认的编码器支持多种密码格式格式类似{bcrypt}密文。这里推荐直接使用BCryptPasswordEncoder。它的特点是同一个明文密码每次加密得到的密文不同但是校验方法matches可以正确判断。这样即使数据库泄露攻击者也很难通过彩虹表反推明文密码。我们先把密码编码器定义为一个 BeanConfiguration public class SecurityConfig { Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }3.2 核心配置SecurityFilterChain接下来编写核心的安全配置类。在 Spring Security 5.7 之后的写法中我们通过定义一个SecurityFilterChainBean 来完成过滤链配置。Configuration EnableWebSecurity public class SecurityConfig { Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeRequests(auth - auth // 放行登录接口和静态资源 .antMatchers(/api/login, /css/**, /js/**).permitAll() // 其他请求都需要认证 .anyRequest().authenticated() ) .formLogin(form - form .loginProcessingUrl(/api/login) .permitAll() ) .logout(logout - logout .logoutUrl(/api/logout) .permitAll() ) .csrf(csrf - csrf.disable()); return http.build(); } }这段配置的含义如下authorizeRequests定义请求的访问规则。antMatchers(/api/login, /css/**, /js/**)这些路径不拦截可以直接访问。anyRequest().authenticated()除了上面放行的路径其他请求都要求登录后才能访问。formLogin启用表单登录并指定登录处理地址为/api/login。logout配置退出登录地址。csrf(csrf - csrf.disable())关闭 CSRF。如果是在前后端分离的项目中通常后端不维护页面表单CSRF 防护意义不大如果是服务端渲染的传统项目建议保留 CSRF 防护。3.3 用户信息加载UserDetailsServiceSpring Security 需要知道如何根据用户名查询用户信息这个逻辑放在UserDetailsService中。我们在内存中模拟两个用户Service public class UserServiceImpl implements UserDetailsService { Autowired private PasswordEncoder passwordEncoder; private final MapString, User userMap new HashMap(); PostConstruct public void init() { // 模拟两个用户 User admin new User(admin, passwordEncoder.encode(123456), ADMIN); User user new User(zhangsan, passwordEncoder.encode(123456), USER); userMap.put(admin.getUsername(), admin); userMap.put(user.getUsername(), user); } Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user userMap.get(username); if (user null) { throw new UsernameNotFoundException(用户不存在); } return org.springframework.security.core.userdetails.User .withUsername(user.getUsername()) .password(user.getPassword()) .roles(user.getRole()) .build(); } }这里的User是自定义实体类包含username、password、role三个字段。最终通过User.withUsername()构建 Spring Security 的UserDetails对象并设置角色。注意角色命名的时候Spring Security 会在hasRole()判断时自动加上ROLE_前缀。所以这里设置角色为ADMIN实际对应的权限标识是ROLE_ADMIN。3.4 认证入口AuthenticationManager如果使用自定义登录接口我们需要让 Spring Security 暴露AuthenticationManager然后手动执行认证逻辑。首先在 SecurityConfig 中注入并暴露AuthenticationManagerConfiguration EnableWebSecurity public class SecurityConfig { Autowired private UserDetailsService userDetailsService; Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); } Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // 这里需要把自定义的 UserDetailsService 设置进去 http .userDetailsService(userDetailsService) .authorizeRequests(auth - auth .antMatchers(/api/login).permitAll() .antMatchers(/api/admin/**).hasRole(ADMIN) .anyRequest().authenticated() ) .formLogin(form - form .loginProcessingUrl(/api/login) .successHandler((req, res, auth) - { res.setContentType(application/json;charsetutf-8); res.getWriter().write({\code\:200,\msg\:\登录成功\}); }) .failureHandler((req, res, ex) - { res.setContentType(application/json;charsetutf-8); res.getWriter().write({\code\:401,\msg\:\用户名或密码错误\}); }) .permitAll() ) .logout(logout - logout.logoutUrl(/api/logout)) .csrf(csrf - csrf.disable()); return http.build(); } }hasRole(ADMIN)表示访问/api/admin/**路径时当前用户必须拥有ROLE_ADMIN权限。这是最简单的授权方式适合角色固定的后台系统。3.5 自定义登录接口虽然可以通过表单登录处理器来实现认证但在前后端分离项目中更常用的方式是自己写一个登录 Controller调用AuthenticationManager完成认证。这样做的好处是登录逻辑更可控比如可以自定义参数格式、增加验证码校验等。RestController public class AuthController { Autowired private AuthenticationManager authenticationManager; PostMapping(/api/login) public Result login(RequestBody LoginRequest loginRequest) { // 1. 创建认证信息 UsernamePasswordAuthenticationToken authenticationToken new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()); // 2. 执行认证 Authentication authenticate authenticationManager.authenticate(authenticationToken); // 3. 认证成功后把身份信息放入 SecurityContext SecurityContextHolder.getContext().setAuthentication(authenticate); return Result.success(登录成功, authenticate.getAuthorities()); } GetMapping(/api/me) public Result me() { Authentication authentication SecurityContextHolder.getContext().getAuthentication(); return Result.success(authentication.getName()); } }这里有几个细节需要说明UsernamePasswordAuthenticationToken是 Spring Security 提供的最常用的认证凭据对象。authenticationManager.authenticate()方法会调用我们之前配置的UserDetailsService和PasswordEncoder完成校验。如果用户名或密码错误会抛出BadCredentialsException建议在全局异常处理器中统一捕获。3.6 方法级权限控制除了在 URL 层面做授权Spring Security 还支持在方法上通过注解控制权限。这种方式更灵活适合对 Service 层或 Controller 层的方法做细粒度控制。首先在配置类上开启方法安全Configuration EnableWebSecurity EnableGlobalMethodSecurity(prePostEnabled true) public class SecurityConfig { // 配置代码... }然后在方法上使用PreAuthorizeRestController RequestMapping(/api/admin) public class AdminController { GetMapping(/list) PreAuthorize(hasRole(ADMIN)) public Result list() { return Result.success(管理员列表数据); } }PreAuthorize在方法执行前检查权限不满足会抛出AccessDeniedException。通过这种方式我们可以在同一个 Controller 中针对不同方法设置不同的权限要求。4. 完整实战基于内存用户的管理系统登录与权限控制现在我们把上面的内容整合成一个完整的可运行示例覆盖用户登录、获取当前用户、管理员接口、普通用户接口这几个场景。4.1 创建实体类package com.example.securitydemo.entity; import lombok.Data; Data public class User { private String username; private String password; private String role; public User(String username, String password, String role) { this.username username; this.password password; this.role role; } }4.2 创建统一返回结果类package com.example.securitydemo.vo; import lombok.Data; Data public class ResultT { private Integer code; private String msg; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMsg(成功); result.setData(data); return result; } public static T ResultT error(Integer code, String msg) { ResultT result new Result(); result.setCode(code); result.setMsg(msg); return result; } }4.3 创建登录请求体package com.example.securitydemo.vo; import lombok.Data; Data public class LoginRequest { private String username; private String password; }4.4 配置 SecurityConfigpackage com.example.securitydemo.config; import com.example.securitydemo.service.UserServiceImpl; 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.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; Configuration EnableWebSecurity EnableGlobalMethodSecurity(prePostEnabled true) public class SecurityConfig { Autowired private UserServiceImpl userServiceImpl; Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); } Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .userDetailsService(userServiceImpl) // 使用无状态会话适合前后端分离 .sessionManagement(session - session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)) .authorizeRequests(auth - auth .antMatchers(/api/login).permitAll() .antMatchers(/api/admin/**).hasRole(ADMIN) .anyRequest().authenticated() ) .formLogin(form - form.disable()) .httpBasic(basic - basic.disable()) .csrf(csrf - csrf.disable()); return http.build(); } }这里做了两个调整关闭了默认的formLogin和httpBasic因为前后端分离场景中不需要这些默认入口。设置了会话策略为IF_REQUIRED表示如果需要会话就创建 Session默认情况下登录成功后会创建 Session后续请求通过 Session 识别用户。如果想改成真正的无状态 JWT 方案可以在这里设置为STATELESS。4.5 编写用户服务package com.example.securitydemo.service; import com.example.securitydemo.entity.User; 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.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import java.util.HashMap; import java.util.Map; Service public class UserServiceImpl implements UserDetailsService { Autowired private PasswordEncoder passwordEncoder; private final MapString, User userMap new HashMap(); PostConstruct public void init() { userMap.put(admin, new User(admin, passwordEncoder.encode(123456), ADMIN)); userMap.put(zhangsan, new User(zhangsan, passwordEncoder.encode(123456), USER)); } Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user userMap.get(username); if (user null) { throw new UsernameNotFoundException(用户不存在); } return org.springframework.security.core.userdetails.User .withUsername(user.getUsername()) .password(user.getPassword()) .roles(user.getRole()) .build(); } }4.6 编写登录接口和用户接口package com.example.securitydemo.controller; import com.example.securitydemo.vo.LoginRequest; import com.example.securitydemo.vo.Result; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; RestController public class AuthController { Autowired private AuthenticationManager authenticationManager; PostMapping(/api/login) public Result login(RequestBody LoginRequest loginRequest) { UsernamePasswordAuthenticationToken authenticationToken new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()); Authentication authenticate authenticationManager.authenticate(authenticationToken); SecurityContextHolder.getContext().setAuthentication(authenticate); return Result.success(authenticate.getName()); } GetMapping(/api/me) public Result me() { Authentication authentication SecurityContextHolder.getContext().getAuthentication(); if (authentication null || !authentication.isAuthenticated()) { return Result.error(401, 未登录); } return Result.success(authentication.getName()); } GetMapping(/api/user/info) public Result userInfo() { return Result.success(普通用户可访问); } }package com.example.securitydemo.controller; import com.example.securitydemo.vo.Result; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; RestController RequestMapping(/api/admin) public class AdminController { GetMapping(/list) PreAuthorize(hasRole(ADMIN)) public Result list() { return Result.success(管理员接口数据); } }4.7 运行与验证启动SecurityDemoApplication使用 Postman 或 curl 验证接口。第一步访问普通接口未登录时返回 401 或 403GET http://localhost:8080/api/user/info第二步调用登录接口POST http://localhost:8080/api/login Content-Type: application/json { username: admin, password: 123456 }成功后会返回当前用户名同时浏览器或客户端会记录 Session Cookie。之后访问受保护接口时会带着 Cookie 自动认证。第三步使用 zhangsan 登录后访问管理员接口GET http://localhost:8080/api/admin/list因为 zhangsan 的角色是USER没有ROLE_ADMIN权限此时接口会返回 403这说明方法级别权限控制生效了。5. 常见的坑与排查思路5.1 自定义登录接口一直 401问题现象常见原因解决思路POST /api/login 返回 401自定义的登录接口没有放行被过滤链拦截在 SecurityConfig 的antMatchers中加入/api/login并调用permitAll()返回 403CSRF 防护开启请求头缺少 Token前后端分离项目中可以显式关闭 CSRF返回 XML 或网页格式错误默认的认证失败处理器返回的是重定向自定义 AuthenticationEntryPoint 并返回 JSON需要注意的是permitAll()只代表该路径不需要认证就能访问不代表该路径不会经过过滤器。如果自定义登录接口依赖AuthenticationManager手动认证那么登录接口本身放行是没有问题的。5.2 密码加密后登录始终失败常见的原因有两个UserDetails中的密码使用了明文而PasswordEncoder使用的是 BCrypt。初始化用户时数据库中存的密码不是 BCrypt 编码后的值。解决方案是在用户初始化时通过passwordEncoder.encode()存储密码不要直接存明文。5.3 hasRole 和 hasAuthority 混淆hasRole(ADMIN)实际检查的是ROLE_ADMIN权限hasAuthority(ROLE_ADMIN)直接检查ROLE_ADMIN权限。两者在设置权限时写法略有不同// 方式一设置角色 .roles(ADMIN) // 对应 ROLE_ADMIN // 方式二设置权限 .authorities(ROLE_ADMIN)如果设置了角色ADMIN但用了hasAuthority(ADMIN)就会一直返回 403。这是非常常见的配置问题。5.4 引入 Security 后静态资源被拦截如果在传统模板项目中引入了 Spring SecurityCSS、JS、图片等静态资源也会被拦截。解决方法是放行静态资源路径.authorizeRequests(auth - auth .antMatchers(/static/**, /css/**, /js/**, /images/**).permitAll() ... )5.5 方法级权限注解不生效如果加了PreAuthorize但没有生效检查两件事是否在配置类上加了EnableGlobalMethodSecurity(prePostEnabled true)。当前请求是否已经通过认证PreAuthorize是在认证之后才执行权限校验的。5.6 会话失效或每次都要求重新登录如果部署在多实例环境中默认的 Session 无法跨实例共享。常见的解决方案是使用 Spring Session Redis 实现会话共享。改用 JWT 无状态认证每次请求携带 Token。如果只是单机环境默认的 Session 机制是够用的。6. 生产环境落地建议前面讲的都是基础功能真正要把 Spring Security 用到生产环境还需要关注下面几个方面。6.1 用户数据接入数据库内存用户的写法只是为了演示生产环境应该把用户信息放在数据库中通过 MyBatis 或 JPA 查询。核心变化点在UserDetailsService的实现中原来是操作 Map现在改成操作数据库表。Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SysUser sysUser userMapper.selectByUsername(username); if (sysUser null) { throw new UsernameNotFoundException(用户不存在); } return org.springframework.security.core.userdetails.User .withUsername(sysUser.getUsername()) .password(sysUser.getPassword()) .roles(sysUser.getRole()) .build(); }如果后续需要接入用户状态禁用、密码过期等功能可以在UserDetails的实现类中对应设置enabled、accountNonExpired、credentialsNonExpired、accountNonLocked这几个属性。6.2 JWT 无状态认证改造前后端分离 多实例部署的场景下JWT 是比较常见的方案。核心思路是登录成功后生成 JWT Token 返回给前端。前端每次请求在 Header 中携带Authorization: Bearer token。后端写一个过滤器解析 Token 得到用户信息放入 SecurityContext。实现时需要注意JWT 的密钥必须放到配置中心或环境变量中不能硬编码在代码里。Token 需要设置过期时间建议不要过长。网关或认证中心统一处理 Token 解析避免业务服务各自实现一遍。在SecurityFilterChain中把自定义的 JWT 过滤器加入过滤器链并设置为无状态会话。http .sessionManagement(session - session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);6.3 权限模型设计本文使用的是最简单的基于角色的权限模型。实际项目中角色和权限往往需要拆分例如用户属于多个角色角色关联多个权限点。这种情况下UserDetails中的authorities存储的是权限点标识而不是角色名称。推荐的做法是用户表、角色表、权限表、用户角色关联表、角色权限关联表。UserDetailsService加载用户时查询出该用户的所有角色和权限点。接口授权时使用hasAuthority(system:user:list)或PreAuthorize(hasAuthority(system:user:list))。这种权限点字符串的设计便于后续做按钮级权限控制和动态菜单比单纯用角色控制更精细。6.4 统一异常处理Spring Security 在认证和授权失败时默认返回的响应不一定适合前端解析。建议自定义AuthenticationEntryPoint未登录时进入和AccessDeniedHandler已登录但无权限时进入统一返回 JSON 格式的错误信息。Component public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { response.setContentType(application/json;charsetutf-8); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().write({\code\:401,\msg\:\未登录或登录已过期\}); } }然后把这两个处理器配置到 SecurityConfig 中.exceptionHandling(exception - exception .authenticationEntryPoint(restAuthenticationEntryPoint) .accessDeniedHandler(restAccessDeniedHandler) )6.5 密码策略和账号安全生产环境中建议关注密码必须使用 BCrypt 或更高强度的算法加密禁止明文存储。增加登录失败次数限制防止暴力破解。管理后台开启验证码。关键操作如删除数据、修改权限增加操作审计日志。敏感接口增加 IP 白名单或频率限制。7. 总结Spring Security 的内容远不止本文提到的这些但它解决的核心问题始终是认证和授权。对初学者来说先理解过滤器链、AuthenticationManager、UserDetailsService、PasswordEncoder 之间的关系再动手写一个完整的登录实例比直接堆配置要有效得多。本文主要掌握了以下知识点Spring Security 中认证和授权的区别。基于内存用户的完整登录认证流程。SecurityFilterChain的基本配置方式。方法级权限控制PreAuthorize的使用。常见的 401、403、密码校验失败、权限不生效等问题的排查方法。下一步可以继续学习 JWT 无状态认证、Spring Security OAuth2 客户端、基于数据库的 RBAC 权限模型、方法级权限和动态权限控制这些都是实际项目中经常用到的方向。你可以在本文示例的基础上先把用户数据切换到数据库再加上一个简单的 JWT Token 生成与校验就是一个非常接近生产环境的基础框架了。
返回列表