SpringSecurity源码学习三:认证

1. 认证步骤

Spring Security实现认证的主要步骤如下:

  1. 配置用户存储:您可以选择将用户信息存储在内存中、数据库中或其他外部身份验证源中。通过配置UserDetailsService或AuthenticationProvider,Spring Security可以获取用户的凭据和权限信息。

  2. 用户认证:当用户尝试登录时,Spring Security会验证用户提供的凭据(例如用户名和密码)。它使用AuthenticationManager来处理认证过程。AuthenticationManager会调用配置的AuthenticationProvider来验证用户凭据的有效性。

  3. 身份验证过滤器:Spring Security使用身份验证过滤器来拦截登录请求并进行身份验证。通常使用UsernamePasswordAuthenticationFilter来处理基于用户名和密码的认证请求。该过滤器会验证用户提供的凭据,并将认证结果封装成一个Authentication对象。

  4. 认证管理器:AuthenticationManager是Spring Security的核心接口之一,用于管理和执行身份验证过程。它负责调用配置的AuthenticationProvider来验证用户凭据的有效性。

  5. 认证成功处理器:当认证成功时,可以配置一个认证成功处理器来处理成功的认证请求。该处理器可以执行一些自定义逻辑,例如生成和返回访问令牌或重定向到特定页面。

  6. 认证失败处理器:当认证失败时,可以配置一个认证失败处理器来处理失败的认证请求。该处理器可以返回错误消息或重定向到登录页面等。

2. 认证

2.1 WebSecurityConfigurerAdapter配置介绍

WebSecurityConfigurerAdapter是Spring Security提供的一个方便的基类,用于自定义安全配置。通过继承WebSecurityConfigurerAdapter类,并重写其中的方法,可以实现对Spring Security的自定义配置。 通过对configure方法的重新,我们可以做很多配置。

下面是configure方法中常用配置的含义和对应的代码示例(Java):

  1. authorizeRequests(): 配置URL的访问权限规则。可以通过antMatchers()指定URL模式,通过permitAll()允许所有用户访问,通过authenticated()要求进行身份验证。
protected void configure(HttpSecurity http) throws Exception {
       http
           .authorizeRequests()
               .antMatchers("/public").permitAll()
               .anyRequest().authenticated();
   }
  1. formLogin(): 配置表单登录相关的设置,包括登录页面、登录请求的URL、登录成功和失败的处理等。
protected void configure(HttpSecurity http) throws Exception {
       http
           .formLogin()
               .loginPage("/login")
               .loginProcessingUrl("/authenticate")
               .defaultSuccessUrl("/home")
               .failureUrl("/login?error=true");
   }
  1. logout(): 配置登出相关的设置,包括登出URL、登出成功的处理等。
protected void configure(HttpSecurity http) throws Exception {
       http
           .logout()
               .logoutUrl("/logout")
               .logoutSuccessUrl("/login?logout=true");
   }
  1. antMatchers(): 配置URL模式和对应的访问权限。
protected void configure(HttpSecurity http) throws Exception {
       http
           .authorizeRequests()
               .antMatchers("/public").permitAll()
               .antMatchers("/admin").hasRole("ADMIN")
               .anyRequest().authenticated();
   }
  1. permitAll(): 允许所有用户访问指定的URL。
protected void configure(HttpSecurity http) throws Exception {
       http
           .authorizeRequests()
               .antMatchers("/public").permitAll();
   }
  1. authenticated(): 要求用户进行身份验证才能访问指定的URL。
protected void configure(HttpSecurity http) throws Exception {
       http
           .authorizeRequests()
               .anyRequest().authenticated();
   }
  1. 设置自定义过滤器,用于token校验,并把自定义的过滤器加入到过滤器链。我们用addFilterBefore()做例子,还有其他的api方法可以使用
 @Component
public class CustomFilter implements GenericFilterBean{
     @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 过滤器逻辑,在此可以做接口的token校验

        chain.doFilter(request, response);
    }
}

//把自定义的过滤器加入到过滤器链中
public class SecurityConfig extends WebSecurityConfigurerAdapter {
     @Autowired
    private CustomFilter customFilter;
     @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class)
            .authorizeRequests()
                .antMatchers("/public").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .and()
            .logout();
    }
}
  1. 自定义accessDeniedHandler和authenticationEntryPoint
    accessDeniedHandler是对授权失败的处理接口;authenticationEntryPoint是对认证失败的处理。
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
                       org.springframework.security.access.AccessDeniedException accessDeniedException)
            throws IOException, ServletException {
        // 在这里自定义处理访问被拒绝的情况
        response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied");
    }
}

@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException, ServletException {
        // 在这里自定义处理未经身份验证的情况
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
    }
}

public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final AccessDeniedHandler accessDeniedHandler;
    private final AuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    public SecurityConfig(CustomAccessDeniedHandler accessDeniedHandler,
                          CustomAuthenticationEntryPoint authenticationEntryPoint) {
        this.accessDeniedHandler = accessDeniedHandler;
        this.authenticationEntryPoint = authenticationEntryPoint;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .exceptionHandling()
                .accessDeniedHandler(accessDeniedHandler) // 设置自定义的accessDeniedHandler
                .authenticationEntryPoint(authenticationEntryPoint) // 设置自定义的authenticationEntryPoint
                .and()
            // 其他的配置...
    }
}

这些只是一些常用的配置示例,实际使用中可能需要根据需求进行更复杂的配置和自定义。

2.2 使用UsernamePasswordAuthenticationFilter登录认证

下面是使用UsernamePasswordAuthenticationFilter进行登录认证,并将账号密码存储在数据库中的Java代码示例:

public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private DataSource dataSource;
     @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }
     @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.jdbcAuthentication()
            .dataSource(dataSource)
            .usersByUsernameQuery("SELECT username, password, enabled FROM users WHERE username = ?")
            .authoritiesByUsernameQuery("SELECT username, authority FROM authorities WHERE username = ?")
            .passwordEncoder(passwordEncoder());
    }
     @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
     @Bean
    public UsernamePasswordAuthenticationFilter authenticationFilter() throws Exception {
        UsernamePasswordAuthenticationFilter filter = new UsernamePasswordAuthenticationFilter();
        filter.setAuthenticationManager(authenticationManagerBean());
        filter.setFilterProcessesUrl("/login");
        return filter;
    }
     @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
            .authorizeRequests()
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }
}

在上面的代码中,我们首先配置了一个DataSource,用于连接数据库。然后在SecurityConfig类中,通过configure方法配置了HttpSecurity,定义了登录页面、注销页面,以及哪些请求需要进行身份验证。接着,我们通过configure方法配置了AuthenticationManagerBuilder,使用jdbcAuthentication()方法来指定使用数据库进行身份验证,并提供了查询用户信息和权限的SQL语句。我们还使用了passwordEncoder()方法来指定密码的加密方式,这里使用了BCryptPasswordEncoder。最后,我们通过@Bean注解定义了一个UsernamePasswordAuthenticationFilter,并将其添加到了过滤器链中,用于拦截登录请求并进行身份验证。

这只是简单的代码示例,主要是为了理解原理,我们真正使用的时候还是要自定义一个登录过滤器和token校验过滤器。下一章我们会举一个前后端分离,自定义登录认证的代码示例。

2.2.1 UsernamePasswordAuthenticationFilter源码

UsernamePasswordAuthenticationFilter是Spring Security框架中的一个过滤器,用于处理基于用户名和密码的身份验证。它是Spring Security核心过滤器链中的一部分,负责拦截用户的登录请求,并将用户名和密码交给AuthenticationManager进行身份验证处理。如果身份验证成功,该过滤器会创建一个包含用户信息和权限的认证对象,并将其交给SecurityContextHolder进行管理。如果身份验证失败,则会返回错误信息给用户。通过该过滤器,可以实现基于表单的登录认证

 */
public class UsernamePasswordAuthenticationFilter extends org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter {

	public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";

	public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";

	//此过滤器拦截的接口
	private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login",
			"POST");

	private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;

	private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;

	private boolean postOnly = true;

	//无参构造器,使用默认的AuthenticationManager实现
	public UsernamePasswordAuthenticationFilter() {
		super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
	}

	//有参构造器,使用方可自定义AuthenticationManager,通过实现AuthenticationManager接口
	public UsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) {
		super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);
	}

可以看到此过滤器拦截自带的/login接口
UsernamePasswordAuthenticationFilter继承自AbstractAuthenticationProcessingFilter,
UsernamePasswordAuthenticationFilter没有重写doFilter()方法,我们看AbstractAuthenticationProcessingFilter的doFilter()方法。

	private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		if (!requiresAuthentication(request, response)) {
			chain.doFilter(request, response);
			return;
		}
		try {
			//attemptAuthentication是抽象方法,可被子类重写
			Authentication authenticationResult = attemptAuthentication(request, response);
			if (authenticationResult == null) {
				// return immediately as subclass has indicated that it hasn't completed
				return;
			}
			//成功后会
			this.sessionStrategy.onAuthentication(authenticationResult, request, response);
			// Authentication success
			if (this.continueChainBeforeSuccessfulAuthentication) {
				chain.doFilter(request, response);
			}
			//保存用户信息等
			successfulAuthentication(request, response, chain, authenticationResult);
		}
		catch (InternalAuthenticationServiceException failed) {
			this.logger.error("An internal error occurred while trying to authenticate the user.", failed);
			unsuccessfulAuthentication(request, response, failed);
		}
		catch (AuthenticationException ex) {
			// Authentication failed
			unsuccessfulAuthentication(request, response, ex);
		}
	}

认证逻辑主要在attemptAuthentication,attemptAuthentication是抽象方法,被子类UsernamePasswordAuthenticationFilter覆写,我们看下覆写逻辑。

	//将request中的参数提取出来保存到 Authentication 中, 返回给认证器认证, 就这么一步, 如果你是前后端分离, 就有可能需要重写该方法
	@Override
	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
			throws AuthenticationException {
		if (this.postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
		}
		//请求中获取用户名
		String username = obtainUsername(request);
		username = (username != null) ? username : "";
		username = username.trim();
		//从请求中获取密码
		String password = obtainPassword(request);
		password = (password != null) ? password : "";
		//构建个UsernamePasswordAuthenticationToken令牌,继承自Authentication
		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);
		//把令牌传入AuthenticationManager,进行认证
		return this.getAuthenticationManager().authenticate(authRequest);
	}

主要逻辑是this.getAuthenticationManager().authenticate(authRequest);可以看到是把UsernamePasswordAuthenticationToken从传入到AuthenticationManager。AuthenticationManager的接口的主要实现类是ProviderManager,核心认证逻辑就在ProviderManager类中。

2.2.1.1 ProviderManager源码

我们看下ProviderManager类的authenticate()方法,代码过长,主要看下主要逻辑。

	//认证器集合
	private List<org.springframework.security.authentication.AuthenticationProvider> providers = Collections.emptyList();

	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		Class<? extends Authentication> toTest = authentication.getClass();
		AuthenticationException lastException = null;
		AuthenticationException parentException = null;
		Authentication result = null;
		Authentication parentResult = null;
		int currentPosition = 0;
		int size = this.providers.size();
		//支持多种认证,遍历所有AuthenticationProvider
		for (org.springframework.security.authentication.AuthenticationProvider provider : getProviders()) {
			//匹配当前的Authentication
			if (!provider.supports(toTest)) {
				continue;
			}
			if (logger.isTraceEnabled()) {
				logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
						provider.getClass().getSimpleName(), ++currentPosition, size));
			}
			try {
				//执行匹配到的AuthenticationProvider逻辑
				result = provider.authenticate(authentication);
				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}

我们根据supports()方法,匹配到正确的AuthenticationProvider是DaoAuthenticationProvider。DaoAuthenticationProvider继承AbstractUserDetailsAuthenticationProvider。
核心逻辑:

	//校验用户密码并返回用户信息
	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
				() -> this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
						"Only UsernamePasswordAuthenticationToken is supported"));
		String username = determineUsername(authentication);
		boolean cacheWasUsed = true;
		UserDetails user = this.userCache.getUserFromCache(username);
		if (user == null) {
			cacheWasUsed = false;
			try {
				//返回本地用户信息
				user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
			}
			catch (UsernameNotFoundException ex) {
				this.logger.debug("Failed to find user '" + username + "'");
				if (!this.hideUserNotFoundExceptions) {
					throw ex;
				}
				throw new BadCredentialsException(this.messages
						.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
			}
			Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
		}
		try {
			this.preAuthenticationChecks.check(user);
			//请求密码和本地密码匹配
			additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
		}

DaoAuthenticationProvider中覆写的方法。

	//从缓存或者数据库查询用户信息
	@Override
	protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
		prepareTimingAttackProtection();
		try {
			//从缓存或者数据库查询用户信息
			//默认spring security保存在内存中, 如果你需要改从数据库中拿到用户, 就需要重写UserDetailsService
			UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
			if (loadedUser == null) {
				throw new InternalAuthenticationServiceException(
						"UserDetailsService returned null, which is an interface contract violation");
			}
			return loadedUser;
		} catch (UsernameNotFoundException ex) {
			mitigateAgainstTimingAttack(authentication);
			throw ex;
		} catch (InternalAuthenticationServiceException ex) {
			throw ex;
		} catch (Exception ex) {
			throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
		}
	}
	//密码验证方法
	@Override
	@SuppressWarnings("deprecation")
	protected void additionalAuthenticationChecks(UserDetails userDetails,
			UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
		if (authentication.getCredentials() == null) {
			this.logger.debug("Failed to authenticate since no credentials provided");
			throw new BadCredentialsException(this.messages
					.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
		}
		String presentedPassword = authentication.getCredentials().toString();
		//比较密码是否相同
		if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
			this.logger.debug("Failed to authenticate since password does not match stored value");
			throw new BadCredentialsException(this.messages
					.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
		}
	}

可以看到,这段逻辑是:比较用户输入的账号密码和数据库或缓存中的用户密码是否匹配。如果匹配成功,认证通过,存储用户信息到上下文中,发送监听事件。认证失败,报错返回前端。

2.2.2 认证流程总结

Spring Security认证流程总结如下:

  1. 用户发起登录请求,提交用户名和密码。
  2. UsernamePasswordAuthenticationFilter拦截登录请求,获取到用户名和密码。
  3. UsernamePasswordAuthenticationFilter将用户名和密码封装成一个Authentication对象。
  4. AuthenticationManager负责对Authentication对象进行身份验证。
  5. AuthenticationManager选择合适的AuthenticationProvider进行身份验证,通常使用DaoAuthenticationProvider。
  6. DaoAuthenticationProvider使用UserDetailsService从数据库或其他数据源中获取用户信息。
  7. DaoAuthenticationProvider使用PasswordEncoder对输入的密码进行加密,然后与数据库中的密码进行比对。
  8. 如果密码匹配成功,DaoAuthenticationProvider将创建一个包含用户信息和权限的认证对象。
  9. 认证对象将被存储在SecurityContextHolder中,以便在整个请求过程中进行访问控制。
  10. 如果密码匹配失败,DaoAuthenticationProvider将抛出异常,登录失败。
  11. 认证成功后,用户将被重定向到登录成功的页面,或者继续访问原始请求的页面。

这是一个简化的Spring Security认证流程,具体的流程可能会根据配置和需求有所不同。但总体来说,Spring Security提供了一个灵活且可定制的认证框架,可以满足各种身份验证需求。

2.3 自定义登录认证代码示例

  1. 创建一个名为 User 的实体类,表示用户信息,包含用户名、密码和角色等属性。
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;
    private String role;
    // 构造函数、getter和setter方法省略
}
  1. 创建一个名为 UserRepository 的接口,用于操作用户信息的数据库存储。
public interface UserRepository {
	//查询数据库用户信息,接口实现类自定义编写
    User findByUsername(String username);
}
  1. 创建一个名为 UserDetailsServiceImpl 的类,实现Spring Security的 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.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    private final UserRepository userRepository;
    @Autowired
    public UserDetailsServiceImpl(UserRepository userRepository) {
        this.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.getRole())
                .build();
    }
}

返回对象是org.springframework.security.core.userdetails.User,这是springSecurity内部对象,如果不满足我们可以自定义返回对象,自定义返回对象要继承springSecurity的UserDetails。

  1. 创建一个名为 SecurityConfig 的配置类,继承自 WebSecurityConfigurerAdapter ,用于配置Spring Security。
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    private final UserDetailsServiceImpl userDetailsService;
    @Autowired
    public SecurityConfig(UserDetailsServiceImpl userDetailsService) {
        this.userDetailsService = userDetailsService;
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/admin/**").hasRole("ADMIN") // 需要ADMIN角色才能访问
                .antMatchers("/user/**").hasAnyRole("ADMIN", "USER") // 需要ADMIN或USER角色才能访问
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .and()
            .logout()
                .logoutSuccessUrl("/")
                .and()
            .csrf().disable();
    }
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/resources/**");
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

	//密码加密方法
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

可以根据需求修改configure()方法。

  1. 创建一个名为 UserController 的控制器类,用于处理登录请求。
@RestController
public class UserController {
    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    private final AuthenticationManager authenticationManager;
    @Autowired
    public UserController(UserRepository userRepository, PasswordEncoder passwordEncoder, AuthenticationManager authenticationManager) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
        this.authenticationManager = authenticationManager;
    }
    @PostMapping("/login")
    public String login(@RequestBody User user) {
        User storedUser = userRepository.findByUsername(user.getUsername());
        if (storedUser != null && passwordEncoder.matches(user.getPassword(), storedUser.getPassword())) {
            Authentication authentication = authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword())
            );
            SecurityContextHolder.getContext().setAuthentication(authentication);
            return "登录成功";
        } else {
            return "用户名或密码错误";
        }
    }
}
  1. 设置自定义过滤器,用于token校验,并把自定义的过滤器加入到过滤器链。我们用addFilterBefore()做例子,还有其他的api方法可以使用
 @Component
public class CustomFilter implements GenericFilterBean{
     @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 过滤器逻辑,在此可以做接口的token校验

        chain.doFilter(request, response);
    }
}

//把自定义的过滤器加入到过滤器链中
public class SecurityConfig extends WebSecurityConfigurerAdapter {
     @Autowired
    private CustomFilter customFilter;
     @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class)
            .authorizeRequests()
                .antMatchers("/public").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .and()
            .logout();
    }
}

UsernamePasswordAuthenticationToken 是一个基于用户名和密码的身份验证令牌。
此令牌作为参数传入AuthenticationManager。在AuthenticationManager中,和2步骤中我们自定义的 loadUserByUsername()方法返回的用户信息做匹配,匹配成功认证成功,把认证信息设置到SecurityContextHolder中,失败返回错误信息。

其他接口请求后端接口的时候,会通过我们的自定义过滤器CustomFilter 校验token。

以上只是简单的代码示例,主要是帮助我们理解认证流程。真正的项目代码要更完善,细节点与功能点要更多,也会用到其他框架的东西,比如JWT。

2.3.1 认证流程总结

Spring Security认证流程总结如下:

  1. 用户发起登录请求,提交用户名和密码,根据用户名和密码封装成一个Authentication对象。
  2. AuthenticationManager负责对Authentication对象进行身份验证。
  3. AuthenticationManager选择合适的AuthenticationProvider进行身份验证,通常使用DaoAuthenticationProvider。
  4. DaoAuthenticationProvider使用UserDetailsService从数据库或其他数据源中获取用户信息。
  5. DaoAuthenticationProvider使用PasswordEncoder对输入的密码进行加密,然后与数据库中的密码进行比对。
  6. 如果密码匹配成功,DaoAuthenticationProvider将创建一个包含用户信息和权限的认证对象。
  7. 认证对象将被存储在SecurityContextHolder中,以便在整个请求过程中进行访问控制。 登录接口返货token给调用方,方便后续接口调用。
  8. 如果密码匹配失败,DaoAuthenticationProvider将抛出异常,登录失败。
  9. 认证成功后,用户将被重定向到登录成功的页面,或者继续访问原始请求的页面。
    10.其他页面访问, 校验token是否有效。有效正常访问接口,无效返回错误,让用户登录。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值