Bootstrap

【权限管理】Spring Security 一看就会的详细教程

Spring Security 是一套功能强大且高度可定制的身份验证和访问控制框架,广泛应用于 Spring 应用程序中。它提供了对应用程序的认证、授权、保护(防止 CSRF、XSS 攻击等)等全面的安全功能。

1. Spring Security 主要功能

  1. 认证(Authentication)

    • 用户身份验证:Spring Security 提供了多种认证机制,如表单登录、基本认证、LDAP、OAuth2 等。
    • 集成身份认证提供者:支持与常见的认证系统集成(如 LDAP、数据库、OAuth2 等)。
    • 自定义认证:你可以自定义认证流程来适应不同的业务需求。
  2. 授权(Authorization)

    • 基于角色的访问控制(RBAC):根据用户的角色控制其访问特定资源的权限。
    • 方法级权限控制:你可以在方法级别对访问权限进行控制(使用 @PreAuthorize@Secured 注解)。
    • URL 级权限控制:你可以通过配置 Spring Security 来限制访问特定 URL 的权限。
  3. 跨站请求伪造(CSRF)防护

    • 默认启用 CSRF 防护,通过生成和验证每个请求的 CSRF token 来防止恶意用户伪造请求。
  4. 跨站脚本攻击(XSS)防护

    • 提供默认的 HTML 转义功能,防止 XSS 攻击。
  5. 会话管理

    • 支持自定义会话控制策略,如并发会话控制、会话超时等。
  6. 安全事件监控

    • 提供日志和事件处理,允许监控和响应认证和授权事件。

2. Spring Security 的核心概念

  1. Authentication(认证): Spring Security 通过 Authentication 接口表示一个经过身份验证的用户。常见的实现类包括 UsernamePasswordAuthenticationTokenOAuth2AuthenticationToken。用户登录后,Authentication 对象会被存储在 SecurityContext 中。

  2. Authorization(授权): 授权控制是 Spring Security 的另一个重要功能。通过角色、权限或者 URL 配置来控制用户能访问哪些资源。常见的控制方式有:

    • 基于角色的访问控制(RBAC):通过角色来控制访问权限。
    • 基于表达式的访问控制:可以在配置中使用 Spring EL 表达式来定义访问规则。
  3. SecurityContext: Spring Security 使用 SecurityContext 来存储认证信息。通常,它会存储在 ThreadLocal 中,允许不同线程访问当前用户的身份信息。

  4. Security Filter Chain(安全过滤链): Spring Security 通过过滤器链来处理 HTTP 请求,并将安全功能应用到请求中。常见的过滤器包括:

    • UsernamePasswordAuthenticationFilter:用于处理用户名密码认证。
    • BasicAuthenticationFilter:用于处理 HTTP 基本认证。
    • CsrfFilter:用于处理 CSRF 防护。

3. Spring Security 配置

Spring Security 可以通过 XML 配置、Java 配置或者基于注解的方式来进行配置。以下是基于 Java 配置的示例。

1. 添加依赖

如果你使用 Maven,在 pom.xml 中添加 Spring Security 的依赖:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>5.8.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>5.8.0</version>
</dependency>

2. 创建安全配置类

Spring Security 的配置类通常会继承 WebSecurityConfigurerAdapter 类,并覆盖其方法来定义安全策略。

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 配置 HTTP 安全性
        http
            .authorizeRequests()
                .antMatchers("/login", "/register").permitAll()  // 放行登录和注册页面
                .antMatchers("/admin/**").hasRole("ADMIN")  // 只有 ADMIN 角色的用户可以访问
                .anyRequest().authenticated()  // 其他请求需要认证
            .and()
            .formLogin()  // 使用表单登录
                .loginPage("/login")  // 自定义登录页面
                .permitAll()
            .and()
            .logout()  // 自定义注销行为
                .permitAll();
    }
}

3. 配置认证管理器

Spring Security 的认证管理器用于验证用户身份,通常通过 AuthenticationManagerBuilder 配置。在简单的表单认证中,可以配置 InMemoryAuthenticationJdbcAuthentication 来进行用户验证。

import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("user").password(passwordEncoder().encode("password")).roles("USER")
            .and()
            .withUser("admin").password(passwordEncoder().encode("admin")).roles("ADMIN");
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

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

4. 自定义用户服务(UserDetailsService)

如果需要从数据库中读取用户信息,可以实现 UserDetailsService 接口,并重写 loadUserByUsername 方法:

import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 从数据库或其他存储中查找用户信息
        if ("admin".equals(username)) {
            return User.withUsername("admin")
                .password("{bcrypt}$2a$10$D/6X8YYI0n2zW3mK3DEz6z/HpWqbzG9s9k47NnE5R7iPbH8V6C0Zy")
                .roles("ADMIN")
                .build();
        } else {
            throw new UsernameNotFoundException("User not found");
        }
    }
}

4. 授权控制

Spring Security 提供了多种方式来控制用户访问权限:

  • URL 级别权限控制: 使用 HttpSecurity 配置请求的访问权限。例如:

    http.authorizeRequests()
        .antMatchers("/admin/**").hasRole("ADMIN")
        .antMatchers("/user/**").hasRole("USER")
        .anyRequest().authenticated();
    

  • 方法级权限控制: 使用 @PreAuthorize 注解来控制方法的访问权限。

    @PreAuthorize("hasRole('ADMIN')")
    public void someAdminMethod() {
        // 只有 ADMIN 角色的用户可以执行
    }
    

  • 基于注解的授权: 使用 @Secured 注解来指定访问权限:

    @Secured("ROLE_USER")
    public void someUserMethod() {
        // 只有 ROLE_USER 角色的用户可以执行
    }
    

5. 会话管理

Spring Security 提供了强大的会话管理功能,能够配置并发会话控制、会话过期等。以下是一些常见的会话管理配置:

http.sessionManagement()
    .maximumSessions(1)  // 限制同一用户只能有一个会话
    .expiredUrl("/session-expired");  // 会话过期后跳转的页面

6. CSRF 防护

默认情况下,Spring Security 启用了 CSRF 防护,它会生成并验证每个请求的 CSRF Token。若需要禁用 CSRF 防护,可以如下配置:

http.csrf().disable();

7. Spring Security 配置 OAuth2

1. OAuth2 简介

OAuth2 是一种开放标准,用于允许用户通过访问令牌对第三方应用进行授权,而无需暴露用户的凭据。Spring Security 提供了对 OAuth2 的全面支持,包括授权服务器和资源服务器的实现。


2. 配置依赖

pom.xml 文件中添加以下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<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>


3. 配置 OAuth2 客户端

Spring Security 提供了默认的 OAuth2 登录支持,您可以通过在 application.ymlapplication.properties 文件中配置 OAuth2 客户端:

spring:
  security:
    oauth2:
      client:
        registration:
          google: # Google OAuth2 客户端配置
            client-id: your-google-client-id
            client-secret: your-google-client-secret
            scope:
              - profile
              - email
            redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
            authorization-grant-type: authorization_code
            client-name: Google
        provider:
          google:
            authorization-uri: https://accounts.google.com/o/oauth2/v2/auth
            token-uri: https://oauth2.googleapis.com/token
            user-info-uri: https://openidconnect.googleapis.com/v1/userinfo
            user-name-attribute: sub


4. 配置 Spring Security

创建一个配置类启用 OAuth2 登录支持:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests()
                .requestMatchers("/login", "/oauth2/**").permitAll()
                .anyRequest().authenticated()
            .and()
            .oauth2Login() // 启用 OAuth2 登录
                .defaultSuccessUrl("/home")
                .failureUrl("/login?error=true");
        return http.build();
    }
}


5. 添加登录成功后的处理逻辑(可选)

如果需要自定义登录成功后的处理逻辑,可以实现 AuthenticationSuccessHandler

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        // 获取用户信息
        System.out.println("User authenticated: " + authentication.getName());
        // 跳转到指定页面
        response.sendRedirect("/dashboard");
    }
}

将其与 OAuth2 配置结合:

http
    .oauth2Login()
    .successHandler(new CustomAuthenticationSuccessHandler());


6. 测试流程
  1. 在浏览器中访问 /login,点击相应的 OAuth2 登录选项(如 Google)。
  2. 输入 Google 的用户凭据,完成授权后重定向到 defaultSuccessUrl 或自定义处理器定义的页面。
  3. 登录成功后,可以通过 SecurityContextHolder 获取用户信息。

通过以上步骤,您可以轻松实现 Spring Security 的 OAuth2 登录支持。


8. Spring Security 与 JWT 集成

Spring Security 可以与 JWT(JSON Web Token)配合使用,实现无状态认证和授权,适合于分布式系统或微服务架构。

1. 添加依赖

确保在项目中添加必要的依赖,包括 Spring Security 和 JWT 相关库:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>5.8.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>5.8.0</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>


2. 编写 JWT 工具类

JWT 工具类负责生成和验证 Token。

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Date;

@Component
public class JwtUtil {

    private static final Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
    private static final long EXPIRATION_TIME = 1000 * 60 * 60; // 1 hour

    // 生成 JWT Token
    public String generateToken(String username) {
        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                .signWith(key)
                .compact();
    }

    // 验证 Token 并解析用户名
    public String validateToken(String token) {
        try {
            Claims claims = Jwts.parserBuilder()
                    .setSigningKey(key)
                    .build()
                    .parseClaimsJws(token)
                    .getBody();
            return claims.getSubject();
        } catch (JwtException | IllegalArgumentException e) {
            throw new RuntimeException("Invalid JWT Token");
        }
    }
}


3. 编写 JWT 过滤器

JWT 过滤器用于拦截请求并校验 JWT Token。

import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private final JwtUtil jwtUtil;
    private final UserDetailsService userDetailsService;

    public JwtAuthenticationFilter(JwtUtil jwtUtil, UserDetailsService userDetailsService) {
        this.jwtUtil = jwtUtil;
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        String authorizationHeader = request.getHeader("Authorization");

        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            String token = authorizationHeader.substring(7);
            String username = jwtUtil.validateToken(token);

            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                if (userDetails != null) {
                    JwtAuthenticationToken authenticationToken =
                            new JwtAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                }
            }
        }
        chain.doFilter(request, response);
    }
}


4. 配置 Spring Security 集成 JWT

修改 SecurityConfig 类,添加 JWT 过滤器。

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.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final JwtUtil jwtUtil;
    private final CustomUserDetailsService userDetailsService;

    public SecurityConfig(JwtUtil jwtUtil, CustomUserDetailsService userDetailsService) {
        this.jwtUtil = jwtUtil;
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeRequests()
                .antMatchers("/login", "/register").permitAll()
                .anyRequest().authenticated()
            .and()
            .addFilterBefore(new JwtAuthenticationFilter(jwtUtil, userDetailsService), UsernamePasswordAuthenticationFilter.class);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

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

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}


5. 生成和验证 JWT 的 Controller

添加一个用于登录并生成 JWT 的接口,以及一个测试接口验证 JWT。

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/auth")
public class AuthController {

    private final AuthenticationManager authenticationManager;
    private final JwtUtil jwtUtil;

    public AuthController(AuthenticationManager authenticationManager, JwtUtil jwtUtil) {
        this.authenticationManager = authenticationManager;
        this.jwtUtil = jwtUtil;
    }

    @PostMapping("/login")
    public String login(@RequestBody LoginRequest loginRequest) {
        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        return jwtUtil.generateToken(userDetails.getUsername());
    }

    @GetMapping("/test")
    public String test() {
        return "JWT Token Validated Successfully!";
    }
}

class LoginRequest {
    private String username;
    private String password;
    // Getters and Setters
}


6. 测试流程
  1. 使用 /auth/login 接口发送用户名和密码,获取 JWT Token。
  2. 使用返回的 Token,在后续请求的 Authorization Header 中添加 Bearer <token>
  3. 测试 /auth/test 接口,验证 JWT Token 是否有效。

通过以上步骤,完成了 Spring Security 和 JWT 的集成,实现了基于 JWT 的无状态认证机制。


总结

本文介绍了如何使用 Spring Security 实现身份验证和授权机制,涵盖了多种常见的安全配置,包括基于表单的认证、JWT 集成、OAuth2、以及配置权限控制等。以下是本文的主要内容总结:

  1. Spring Security 概述:Spring Security 提供了强大且灵活的安全机制,包括认证、授权、攻击防护等功能,适用于大多数 Java Web 应用程序。

  2. 身份验证:介绍了如何使用 基于表单的认证(UsernamePasswordAuthenticationFilter)和 JWT(JSON Web Token)来实现无状态的认证机制,帮助开发人员理解如何配置和集成这些身份验证方案。

  3. 授权管理:涵盖了如何配置访问权限控制,包括基于 URL 的授权(authorizeRequests())、基于角色的权限控制等。并展示了如何定制化权限管理来确保应用的安全性。

  4. OAuth2 集成:讲解了如何配置 OAuth2 登录,允许应用通过第三方身份提供商(如 Google)进行授权,减少了密码管理的复杂度,提升了安全性和用户体验。

  5. 安全性增强:通过介绍多种防护机制(如 CSRF 防护、会话管理、异常处理等),使应用具备更高的安全性,减少了潜在的攻击面。


应用场景

Spring Security 的配置可以适用于以下应用场景:

  • 企业应用:用于实现用户身份认证、权限管理和防止安全漏洞(如 CSRF、XSS 等)。
  • 微服务架构:结合 JWT 和 OAuth2,可以在微服务之间实现无状态认证和跨服务授权。
  • 单点登录(SSO):支持通过 OAuth2 协议实现单点登录,减少用户的重复登录操作。

扩展与自定义

  • 自定义认证和授权:开发人员可以通过自定义过滤器、认证逻辑、权限控制等,进一步满足特定的安全需求。
  • 集成外部系统:Spring Security 可以与 LDAP、OAuth2 认证服务器等外部身份认证系统集成,实现统一认证和权限管理。

通过合理使用 Spring Security,您可以大大提高应用程序的安全性,同时提供灵活的配置选项来应对不同的业务需求。无论是处理复杂的权限控制,还是支持现代的无状态认证方式,Spring Security 都能提供强大的支持。

;