导读:本期聚焦于周翰文创作的《Spring Boot整合Spring Security如何实现认证授权功能?》,敬请观看详情。认证和授权是后台系统绕不开的两个基础能力,Spring Security作为Spring生态中的安全框架,提供了过滤器链、认证管理器和权限校验等一整套解决方案。本文从零开始演示Spring Boot项目整合Spring Security的完整流程,涵盖依赖引入、自定义UserDetailsService、密码加密配置、登录接口处理、基于角色和注解的权限控制,以及前后端分离场景下JWT无状态认证的实现思路,帮你快速搭建一套安全可用的权限体系。

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

Spring Boot整合Spring Security如何实现认证授权功能?

一、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,多半是数据库中存的是明文密码,或者配置的加密器与存储格式不匹配,存库时一定要用同一个PasswordEncoderencode方法加密。第二个是跨域问题,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

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260914/56596.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。