SpingSecurity认证流程与使用

tianco / 2024-10-10 / 原文

1、Security的本质:

SpringSecurity的本质就是一个过滤器链,内部包含了提供各种功能的过滤器,基本案例中的过滤器链如下图所示(仅展示了部分核心过滤器)

UsernamePasswordAuthenticationFilter:负责处理我们在登陆页面填写了用户名密码后的登陆请求。基本案例的认证工作主要有它负责

ExceptionTranslationFilter:处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException

FilterSecurityInterceptor:负责权限校验的过滤器

可以通过Debug查看SpingSecurity过滤器链中有哪些过滤器以及它们的先后顺序

2、Security认证流程

Authentication接口: 它的实现类,表示当前访问系统的用户,封装了用户相关信息

AuthenticationManager接口:定义了认证Authentication的方法

UserDetailsService接口:加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法

UserDetails接口:提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中

UsernamePasswordAuthenticationFilter实现类:实现了我们最常用的基于用户名和密码的认证逻辑,封装Authentication对象

DaoAuthenticationProvider实现类:是AuthenticationManager中管理的其中一个Provider,因为是要访问数据库,所以叫Dao准备

3、Security的实现流程

3.1 添加依赖

启动一个Springboot项目,并在pom.xml添加对应的Spring Security的相关依赖

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

3.2 基础配置

Spring Security 默认对所有 HTTP 请求进行保护,要求用户进行身份验证。我们可以通过配置类来自定义这个行为。

创建自定义的 Security 配置类:

需要创建一个继承 WebSecurityConfigurerAdapter 的配置类,或者在 Spring Security 5.7 后使用 SecurityFilterChain 来配置安全性。

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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((requests) -> requests
                .antMatchers("/", "/home").permitAll() // 允许首页和home不认证
                .anyRequest().authenticated() // 其他所有请求都需要认证
            )
            .formLogin((form) -> form
                .loginPage("/login") // 自定义登录页
                .permitAll()
            )
            .logout((logout) -> logout.permitAll());
        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("user")
                .password(passwordEncoder.encode("password"))
                .roles("USER").build());
        manager.createUser(User.withUsername("admin")
                .password(passwordEncoder.encode("admin"))
                .roles("ADMIN").build());
        return manager;
    }

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

3.3 认证流程(Authentication)

Spring Security 的认证流程是通过 Authentication 机制来实现的。

步骤:

1.提交登录请求:当用户通过表单登录时,Spring Security 内置的 UsernamePasswordAuthenticationFilter 过滤器会拦截登录请求(默认拦截 /login)。

2.封装认证对象:UsernamePasswordAuthenticationFilter 会将用户提交的用户名和密码封装成一个 UsernamePasswordAuthenticationToken 对象,该对象实现了 Authentication 接口。

3.调用 AuthenticationManager:这个封装好的 Authentication 对象会被传递给 AuthenticationManager,通常是由 ProviderManager 实现,来执行认证。

4.使用 AuthenticationProvider 验证:ProviderManager 内部维护了多个 AuthenticationProvider(通常是 DaoAuthenticationProvider)。每个 AuthenticationProvider 都会尝试根据特定的策略对用户进行认证。

5.UserDetailsService 和 UserDetails:DaoAuthenticationProvider 会使用 UserDetailsService 加载用户信息,UserDetailsService 返回一个包含用户信息的 UserDetails 对象,这个对象包含用户名、密码、角色等信息。

6.密码校验:DaoAuthenticationProvider 会调用 PasswordEncoder 对密码进行加密和匹配,如果认证成功,返回一个 Authentication 对象,包含用户的完整信息和权限。

7.认证成功/失败处理:如果认证成功,用户会被重定向到上一个请求页面。如果失败,则返回登录页面,并显示错误信息。


@Override
    public  Response login(UserDTO user) {
        // 创建一个新的 UsernamePasswordAuthenticationToken,用于封装用户提交的用户名和密码。
        // 此对象将在认证过程中传递给 AuthenticationManager 进行认证(而不是直接调用默认的校验方式)。
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());

        try {
            // 将封装好的 UsernamePasswordAuthenticationToken 对象传递给 authenticationManager,并使用 authenticate 方法进行认证。
            // 此认证过程涉及的步骤包括 AuthenticationProvider 处理(如 DaoAuthenticationProvider 调用 UserDetailsService 和 PasswordEncoder)。
            Authentication authenticate = authenticationManager.authenticate(authenticationToken);
            // 成功认证后,获取 LoginUser 对象并从中提取用户 ID。
            LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
            String userid = loginUser.getUser().getId().toString();
            String jwt = jwtUtils.generateToken(userid);
            HashMap<String, String> map = new HashMap<>();
            map.put("token", jwt);
            redisTemplate.opsForValue().set(userid,loginUser);
            return new Response<>(200,"success",map);
        }
        catch (BadCredentialsException | UsernameNotFoundException e) {
            return new Response<>(-1, e.getMessage(),null);
        }
    }

3.4 授权流程(Authorization)

授权是决定用户是否有权访问某些资源的过程。在 Spring Security 中,通过配置 HttpSecurity 对不同的 URL 路径进行权限控制。

授权的基本步骤:

1.配置授权规则:在 SecurityConfig 中,使用 authorizeHttpRequests() 配置不同路径的访问权限。例如,可以指定某些路径对特定角色开放。

http
    .authorizeHttpRequests((requests) -> requests
        .antMatchers("/admin/**").hasRole("ADMIN")
        .antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
        .anyRequest().authenticated()
    );

2.角色和权限:角色是基于 GrantedAuthority 的字符串表示。例如,ROLE_USER 或 ROLE_ADMIN。Spring Security 会根据 UserDetails 中的角色信息来决定是否授予用户访问特定资源的权限。

3.访问控制:当用户发起请求时,Spring Security 会检查该用户的角色是否符合指定的访问控制规则。如果符合,用户将被允许访问,否则将被拒绝,并重定向到登录页面或显示 403 错误页面。