Spring 安全框架概述

Spring Security 是 Spring 生态系统中用于提供身份验证和授权功能的强大框架,它可以无缝集成到 Spring Boot 和 Spring 应用中,帮助开发者保护应用程序的资源,防止未授权的访问。以下是 Spring Security 的一些核心特性:

  • 身份验证(Authentication):验证用户的身份,常见的方式有表单登录、HTTP 基本认证、OAuth 等。
  • 授权(Authorization):确定已认证用户是否有权限访问特定的资源,支持基于角色、权限的访问控制。
  • 防止常见攻击:如跨站请求伪造(CSRF)、会话固定攻击等。
  • 集成多种认证方式:可以与 LDAP、数据库、OAuth 提供商等集成。

实现基于角色的访问控制(RBAC)步骤

1. 添加依赖

如果你使用的是 Spring Boot 项目,在 pom.xml 中添加 Spring Security 的依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

2. 配置 Spring Security

创建一个配置类,继承 WebSecurityConfigurerAdapter 类,并重写相关方法来配置安全规则。以下是一个示例:

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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
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.provisioning.InMemoryUserDetailsManager;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
           .authorizeRequests()
               .antMatchers("/public/**").permitAll() // 允许所有用户访问 /public 路径下的资源
               .antMatchers("/admin/**").hasRole("ADMIN") // 只有具有 ADMIN 角色的用户可以访问 /admin 路径下的资源
               .anyRequest().authenticated() // 其他请求需要用户认证
               .and()
           .formLogin() // 使用表单登录
               .and()
           .httpBasic(); // 支持 HTTP 基本认证
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        UserDetails user =
             User.withDefaultPasswordEncoder()
                .username("user")
                .password("password")
                .roles("USER")
                .build();

        UserDetails admin =
             User.withDefaultPasswordEncoder()
                .username("admin")
                .password("password")
                .roles("ADMIN")
                .build();

        return new InMemoryUserDetailsManager(user, admin);
    }
}

在上述代码中:

  • configure(HttpSecurity http) 方法用于配置 HTTP 请求的访问规则,antMatchers 方法用于指定路径模式,permitAll() 表示允许所有用户访问,hasRole() 表示只有具有指定角色的用户可以访问,authenticated() 表示需要用户认证。
  • userDetailsService() 方法用于创建用户信息,这里使用了内存中的用户存储,实际应用中可以从数据库或其他数据源获取用户信息。

3. 创建控制器

创建一个简单的控制器来测试访问控制:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @GetMapping("/public/hello")
    public String publicHello() {
        return "Hello, public!";
    }

    @GetMapping("/admin/hello")
    public String adminHello() {
        return "Hello, admin!";
    }

    @GetMapping("/user/hello")
    public String userHello() {
        return "Hello, user!";
    }
}

4. 运行和测试

启动 Spring Boot 应用,尝试访问不同的路径:

  • 访问 /public/hello:无需登录即可访问。
  • 访问 /admin/hello:只有使用具有 ADMIN 角色的用户登录后才能访问。
  • 访问 /user/hello:使用任何已认证的用户登录后都可以访问。

基于数据库的用户和角色管理

上述示例使用了内存中的用户存储,实际应用中通常会使用数据库来存储用户和角色信息。以下是一个简单的基于数据库的实现步骤:

  1. 创建数据库表,如 users 表和 roles 表,以及关联表 user_roles
  2. 创建实体类和 Repository 接口来操作数据库。
  3. 实现 UserDetailsService 接口,从数据库中获取用户信息。
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.stereotype.Service;

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("User not found");
        }
        return org.springframework.security.core.userdetails.User
               .withUsername(user.getUsername())
               .password(user.getPassword())
               .roles(user.getRoles().stream().map(Role::getName).toArray(String[]::new))
               .build();
    }
}

完整代码示例

以下是包含上述配置和控制器的完整代码示例:

SecurityConfig.java

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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
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.provisioning.InMemoryUserDetailsManager;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
           .authorizeRequests()
               .antMatchers("/public/**").permitAll()
               .antMatchers("/admin/**").hasRole("ADMIN")
               .anyRequest().authenticated()
               .and()
           .formLogin()
               .and()
           .httpBasic();
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        UserDetails user =
             User.withDefaultPasswordEncoder()
                .username("user")
                .password("password")
                .roles("USER")
                .build();

        UserDetails admin =
             User.withDefaultPasswordEncoder()
                .username("admin")
                .password("password")
                .roles("ADMIN")
                .build();

        return new InMemoryUserDetailsManager(user, admin);
    }
}    

HelloController.java

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @GetMapping("/public/hello")
    public String publicHello() {
        return "Hello, public!";
    }

    @GetMapping("/admin/hello")
    public String adminHello() {
        return "Hello, admin!";
    }

    @GetMapping("/user/hello")
    public String userHello() {
        return "Hello, user!";
    }
}    

通过以上步骤,你可以在 Spring 应用中实现基于角色的访问控制。