在任何一个对外提供服务的后端系统中,认证授权都是第一道防线。认证解决的是"你是谁"的问题,授权解决的是"你能做什么"的问题。Spring Security是Spring官方推出的安全框架,功能强大但配置相对繁琐,很多初学者在整合时容易被过滤器链、UserDetailsService这些概念绕晕。本文将以一个完整的实战流程,演示如何在Spring Boot项目中整合Spring Security,从最基础的表单登录到前后端分离场景下的JWT认证,一步步搭建起一套完整的认证授权体系。

一、Spring Security的核心概念与整合准备
在动手写代码之前,有必要先理解Spring Security的工作原理。Spring Security的底层是基于Servlet规范中的Filter机制实现的,框架在启动时会向Servlet容器注册一个名为springSecurityFilterChain的过滤器链,所有请求都会先经过这条过滤器链,依次通过认证过滤器、权限校验过滤器等环节,全部通过后才能到达我们自己的Controller。
这条过滤器链中有几个关键角色值得记住。SecurityFilterChain是过滤器链本身,负责编排各个过滤器的顺序;AuthenticationManager是认证的入口,它协调多个认证提供者完成认证动作;UserDetailsService是我们最常接触的接口,框架通过它加载用户信息,包括用户名、密码和权限集合;PasswordEncoder负责密码的加密与比对,绝不能让明文密码进入数据库。理解了这四者的关系,后面的配置就不会显得杂乱。
准备工作很简单,在pom.xml中加入以下依赖即可:
<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>
</dependency>引入依赖后重启项目,Spring Security会立即生效,默认拦截所有请求,并自动生成一个随机密码输出在控制台日志中。此时访问任何接口都会被重定向到框架自带的/login登录页,用户名为user,密码就是控制台打印的那串字符。这是验证整合是否成功最快的方式。
二、自定义用户认证:从内存用户到数据库用户
默认的随机密码显然无法用于生产环境,我们需要接入自己的用户体系。最直接的方式是自定义一个UserDetailsService实现类,从数据库中查询用户信息并交给框架处理。
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private SysUserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser user = userMapper.selectByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
// 将数据库中的角色信息转换为权限集合
List<SimpleGrantedAuthority> authorities = user.getRoles().stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.getRoleCode()))
.collect(Collectors.toList());
return new org.springframework.security.core.userdetails.User(
user.getUsername(), user.getPassword(), authorities);
}
}loadUserByUsername方法只负责返回用户信息,密码比对由框架内部的AuthenticationManager调用PasswordEncoder完成,我们不需要手动比对密码。这里有个细节需要注意:如果权限是角色,前缀必须写成ROLE_,否则后面使用hasRole表达式时会匹配不上。也可以在权限集合中直接使用hasAuthority来校验具体的功能权限,两种方式可以并存。
接下来需要编写配置类,指定密码加密方式和授权规则。Spring Security 5.7之后推荐使用SecurityFilterChainBean的方式替代旧的WebSecurityConfigurerAdapter,新写法如下:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/login", "/register").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated())
.formLogin(form -> form
.loginProcessingUrl("/doLogin")
.successHandler((req, resp, auth) -> {
resp.setContentType("application/json;charset=UTF-8");
resp.getWriter().write("{\"code\":200,\"msg\":\"登录成功\"}");
})
.failureHandler((req, resp, e) -> {
resp.setContentType("application/json;charset=UTF-8");
resp.getWriter().write("{\"code\":401,\"msg\":\"用户名或密码错误\"}");
}));
return http.build();
}
}这段配置做了几件事:关闭CSRF防护以简化前后端分离场景的开发;放行登录注册接口;对/admin路径下的接口要求ADMIN角色;其余接口一律需要认证。登录成功和失败都返回JSON而不是页面跳转,这是适配前端分离项目的常见做法。密码加密使用BCryptPasswordEncoder,它内部自动加盐,同一个密码每次加密结果都不同,但验证时仍能正确比对,安全性远高于MD5加盐的方式。
三、基于注解的细粒度权限控制
URL级别的拦截只能控制到接口维度,实际业务中经常需要更细粒度的控制,比如同一个接口不同角色能看到的字段不同。这时可以开启方法级的注解校验,在配置类上加@EnableGlobalMethodSecurity注解即可。
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
}开启之后,就可以在Service方法或Controller方法上使用@PreAuthorize注解。这个注解支持SpEL表达式,表达能力非常强,既能校验角色,也能校验具体权限,甚至可以传入方法参数做动态判断。
@RestController
public class OrderController {
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/order/{id}")
public Result deleteOrder(@PathVariable Long id) {
orderService.deleteById(id);
return Result.ok();
}
@PreAuthorize("hasAuthority('order:query') or #userId == authentication.principal.username")
@GetMapping("/order/list")
public Result listOrders(String userId) {
return Result.ok(orderService.listByUser(userId));
}
}第二个例子体现了注解方式的灵活性:拥有order:query权限的用户可以查看所有人的订单,普通用户只能查看自己的订单,通过#userId引用方法参数与当前登录用户比对,一行代码就实现了数据权限的控制。使用注解校验时要注意,校验发生在方法调用之前,一旦不满足条件会抛出AccessDeniedException,建议配合全局异常处理器统一转换为友好的错误响应,避免前端收到原始的500错误。
四、前后端分离场景下的JWT无状态认证
传统的Session认证在前后端分离和分布式部署场景下会遇到Cookie跨域、Session无法共享等问题。JWT方案将用户信息签名后存放在Token中,服务端不需要保存任何会话状态,天然适合水平扩展。整合思路是:在过滤器链中自定义一个JWT校验过滤器,每次请求先解析请求头中的Token,验证通过后手动构造认证信息放入SecurityContextHolder,让后续流程认为该请求已经登录。
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7);
if (jwtUtil.validate(token)) {
String username = jwtUtil.getUsername(token);
List<SimpleGrantedAuthority> authorities = jwtUtil.getAuthorities(token);
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(username, null, authorities);
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
chain.doFilter(request, response);
}
}然后在配置类中把这个过滤器插入到用户名密码认证过滤器之前,并禁用Session与登录页面,让整个认证流程完全交给Token驱动。
@Bean
public SecurityFilterChain filterChain(HttpSecurity http,
JwtAuthenticationFilter jwtFilter) throws Exception {
http.csrf(csrf -> csrf.disable())
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/login").permitAll()
.anyRequest().authenticated())
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}登录接口收到用户名密码后,调用AuthenticationManager完成认证,认证成功则签发Token返回给前端,前端后续请求在Header中携带该Token即可。JWT方案需要注意两点:一是Token签名密钥必须妥善保管,不能硬编码提交到代码仓库;二是Token一旦签发无法主动作废,如果要实现强制下线功能,需要配合Redis维护一份Token黑名单,在校验过滤器中先查黑名单再验签。
五、常见问题与排查思路
整合过程中有几个高频踩坑点。第一个是密码比对失败,报错信息中带有There is no PasswordEncoder mapped,多半是数据库中存的是明文密码,或者配置的加密器与存储格式不匹配,存库时一定要用同一个PasswordEncoder的encode方法加密。第二个是跨域问题,Spring Security的过滤器执行在CORS预检处理之前,即使配置了Spring MVC的跨域也不生效,需要在Security配置中通过http.cors()显式启用,并注入对应的CorsConfigurationSource。第三个是角色校验不生效,检查权限字符串是否带了ROLE_前缀,以及使用的是hasRole还是hasAuthority,两者对前缀的处理规则不同。
排查过滤器链相关问题的有效手段是打开调试日志,在配置文件中添加logging.level.org.springframework.security=debug,日志会完整打印出当前请求依次经过了哪些过滤器、在哪一步被拦截,定位问题效率非常高。掌握了这些内容后,无论是传统的管理后台还是前后端分离的API服务,都能基于Spring Security快速搭建出一套安全可靠的认证授权体系。
Spring BootSpring Security认证授权修改时间:2026-09-14 09:18:54