Spring Security入门:构建安全应用

Spring Security入门:构建安全应用

在Java开发领域,Spring Security是实现应用安全的首选框架。深入了解这个强大的安全框架,掌握认证与授权的核心技术。

一、什么是Spring Security?

Spring Security是Spring生态系统中最强大的安全框架,它为Java应用提供全面的安全服务。无论是传统Web应用,还是RESTful API,甚至是微服务架构,Spring Security都能提供可靠的安全保障。

为什么选择Spring Security?

  • 功能全面:涵盖认证、授权、防护等所有安全需求
  • 高度可定制:灵活扩展,满足各种业务场景
  • 社区活跃:Spring官方维护,文档完善
  • 与Spring深度集成:无缝衔接Spring Boot

二、核心概念

2.1 认证 vs 授权

  • 认证:确认"你是谁",如登录验证
  • 授权:确认"你能做什么",如权限控制

2.2 核心组件

组件 说明
SecurityContextHolder 存储安全上下文信息
Authentication 认证信息对象
UserDetailsService 加载用户信息
PasswordEncoder 密码加密
SecurityFilterChain 过滤器链

三、项目搭建

3.1 添加依赖

xml 复制代码
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.0</version>
</parent>

<dependencies>
    <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>
</dependencies>

3.2 基础配置类

java 复制代码
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .defaultSuccessUrl("/home")
            );

        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

四、认证流程详解

Spring Security的认证流程是一个精心设计的链式处理过程:

  1. 用户提交认证信息:用户名和密码
  2. 创建Authentication对象:封装认证信息
  3. AuthenticationManager验证:委托给Provider
  4. 加载用户详情:通过UserDetailsService
  5. 验证成功:存入SecurityContext

4.1 自定义用户详情服务

java 复制代码
@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) {
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException("用户不存在"));

        return User.builder()
            .username(user.getUsername())
            .password(user.getPassword())
            .roles(user.getRoles().toArray(new String[0]))
            .build();
    }
}

4.2 自置认证提供者

java 复制代码
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public Authentication authenticate(Authentication authentication) {
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();

        UserDetails userDetails = userDetailsService.loadUserByUsername(username);

        if (passwordEncoder.matches(password, userDetails.getPassword())) {
            return new UsernamePasswordAuthenticationToken(
                username,
                password,
                userDetails.getAuthorities()
            );
        }
        throw new BadCredentialsException("密码错误");
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}

五、授权机制

授权是指确定用户是否有权访问特定资源的过程。Spring Security支持多种授权方式。

5.1 URL级别授权

java 复制代码
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth
            // 公开接口
            .requestMatchers("/api/public/**").permitAll()

            // 管理员专属
            .requestMatchers("/api/admin/**").hasRole("ADMIN")

            // 需要特定权限
            .requestMatchers("/api/write/**").hasAuthority("WRITE")

            // 已认证用户
            .anyRequest().authenticated()
        );

    return http.build();
}

5.2 方法级授权

java 复制代码
@RestController
@RequestMapping("/api")
public class ApiController {

    // 需要USER角色
    @GetMapping("/user")
    @PreAuthorize("hasRole('USER')")
    public String userEndpoint() {
        return "用户专属内容";
    }

    // 需要ADMIN角色
    @GetMapping("/admin")
    @PreAuthorize("hasRole('ADMIN')")
    public String adminEndpoint() {
        return "管理员专属内容";
    }

    // 复杂表达式
    @GetMapping("/secure")
    @PreAuthorize("hasRole('ADMIN') and #username == authentication.name")
    public String secureEndpoint(String username) {
        return "只能访问自己的数据";
    }

    // 返回后过滤
    @GetMapping("/data/{id}")
    @PostAuthorize("returnObject.owner == authentication.name")
    public Data getData(@PathVariable Long id) {
        return dataService.findById(id);
    }
}

5.3 自定义权限注解

java 复制代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('ADMIN') or hasRole('SUPERVISOR')")
public @interface IsAdminOrSupervisor {
}

六、JWT令牌认证

JWT(JSON Web Token)是目前最流行的跨域认证解决方案。

6.1 JWT服务类

java 复制代码
@Service
public class JwtService {

    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expiration}")
    private Long expiration;

    public String generateToken(String username, List<String> roles) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("roles", roles);

        return Jwts.builder()
            .claims(claims)
            .subject(username)
            .issuedAt(new Date())
            .expiration(new Date(System.currentTimeMillis() + expiration))
            .signWith(getSigningKey())
            .compact();
    }

    public String extractUsername(String token) {
        return extractClaims(token).getSubject();
    }

    public boolean validateToken(String token, String username) {
        String extractedUsername = extractUsername(token);
        return extractedUsername.equals(username) && !isTokenExpired(token);
    }

    private Claims extractClaims(String token) {
        return Jwts.parser()
            .verifyWith(getSigningKey())
            .build()
            .parseSignedClaims(token)
            .getPayload();
    }
}

6.2 JWT认证过滤器

java 复制代码
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private JwtService jwtService;

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain) throws ServletException, IOException {

        String token = extractTokenFromRequest(request);

        if (token != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            String username = jwtService.extractUsername(token);

            if (username != null) {
                List<String> roles = jwtService.extractRoles(token);

                UsernamePasswordAuthenticationToken authToken =
                    new UsernamePasswordAuthenticationToken(
                        username,
                        null,
                        roles.stream()
                            .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                            .collect(Collectors.toList())
                    );

                SecurityContextHolder.getContext().setAuthentication(authToken);
            }
        }

        filterChain.doFilter(request, response);
    }

    private String extractTokenFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

6.3 登录控制器

java 复制代码
@RestController
@RequestMapping("/api/auth")
public class AuthController {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private JwtService jwtService;

    @PostMapping("/login")
    public LoginResponse login(@RequestBody LoginRequest request) {
        Authentication authentication = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(
                request.getUsername(),
                request.getPassword()
            )
        );

        User user = (User) authentication.getPrincipal();
        String token = jwtService.generateToken(
            user.getUsername(),
            user.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.toList())
        );

        return new LoginResponse(token, user.getUsername());
    }
}

七、过滤器链机制

Spring Security通过过滤器链处理所有HTTP请求,理解过滤器链的工作原理对定制安全功能至关重要。

7.1 常用过滤器

过滤器 功能
UsernamePasswordAuthenticationFilter 处理表单登录
JwtAuthenticationFilter 处理JWT令牌
LogoutFilter 处理登出
FilterSecurityInterceptor 最终访问决策
ExceptionTranslationFilter 处理安全异常

7.2 自定义过滤器

java 复制代码
public class CustomFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain) throws ServletException, IOException {

        // 前置处理
        String requestId = UUID.randomUUID().toString();
        request.setAttribute("requestId", requestId);

        // 继续过滤器链
        filterChain.doFilter(request, response);

        // 后置处理
        logger.info("Request {} completed", requestId);
    }
}

// 注册自定义过滤器
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) {
    http.addFilterBefore(new CustomFilter(), UsernamePasswordAuthenticationFilter.class);
    return http.build();
}

八、异常处理

java 复制代码
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(
            HttpServletRequest request,
            HttpServletResponse response,
            AuthenticationException authException) throws IOException {

        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.setContentType("application/json;charset=UTF-8");

        ApiResponse<?> apiResponse = ApiResponse.unauthorized("未登录或登录已过期");
        response.getWriter().write(new ObjectMapper().writeValueAsString(apiResponse));
    }
}

@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(
            HttpServletRequest request,
            HttpServletResponse response,
            AccessDeniedException accessDeniedException) throws IOException {

        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        response.setContentType("application/json;charset=UTF-8");

        ApiResponse<?> apiResponse = ApiResponse.error("权限不足");
        response.getWriter().write(new ObjectMapper().writeValueAsString(apiResponse));
    }
}

九、应用场景

9.1 企业后台系统

典型的RBAC(基于角色的访问控制)场景:

  • 用户表、角色表、权限表
  • 用户-角色、角色-权限多对多关系
  • 支持动态权限配置

9.2 API网关

统一认证授权中心:

  • JWT令牌签发与验证
  • 单点登录支持
  • 第三方登录集成

十、总结

Spring Security是企业级Java应用的安全基石:

  • 核心功能:认证与授权
  • 扩展能力:过滤器链、自定义提供者
  • 现代方案:JWT、OAuth2
相关推荐
老毛肚3 小时前
Spring源码探究2.0
java·后端·spring
涵涵(互关)3 小时前
添加了 @TableId(type = IdType.AUTO) 但仍生成超大 ID
数据库·spring·mybatis
运维@小兵3 小时前
Spring AI入门
java·人工智能·spring
仙俊红4 小时前
Spring 构造器注入 vs 字段注入
java·后端·spring
0和1的舞者4 小时前
Spring 事务核心知识点全梳理(编程式 + 声明式 + 注解详解)
java·后端·spring
之歆4 小时前
RAG幻觉评估和解决方案
java·人工智能·spring
之歆4 小时前
Spring ai 指标监控
java·人工智能·spring·ai
hinotoyk4 小时前
SpringBoot集成Line Messaging API
java·spring
十六年开源服务商4 小时前
WordPress运维服务中的内容营销策略
java·运维·spring