SpringSecurity源码学习三:认证

目录

  • 1. 认证步骤
  • 2. 认证
    • 2.1 WebSecurityConfigurerAdapter配置介绍
    • 2.2 使用UsernamePasswordAuthenticationFilter登录认证
      • 2.2.1 UsernamePasswordAuthenticationFilter源码
        • 2.2.1.1 ProviderManager源码
      • 2.2.2 认证流程总结
    • 2.3 自定义登录认证代码示例
      • 2.3.1 认证流程总结

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{@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {// 过滤器逻辑,在此可以做接口的token校验chain.doFilter(request, response);}
}//把自定义的过滤器加入到过滤器链中
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate CustomFilter customFilter;@Overrideprotected 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 {@Overridepublic 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 {@Overridepublic 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;@Autowiredpublic SecurityConfig(CustomAccessDeniedHandler accessDeniedHandler,CustomAuthenticationEntryPoint authenticationEntryPoint) {this.accessDeniedHandler = accessDeniedHandler;this.authenticationEntryPoint = authenticationEntryPoint;}@Overrideprotected 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 {@Autowiredprivate DataSource dataSource;@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/login").permitAll().anyRequest().authenticated().and().formLogin().loginPage("/login").permitAll().and().logout().permitAll();}@Overrideprotected 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());}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Beanpublic UsernamePasswordAuthenticationFilter authenticationFilter() throws Exception {UsernamePasswordAuthenticationFilter filter = new UsernamePasswordAuthenticationFilter();filter.setAuthenticationManager(authenticationManagerBean());filter.setFilterProcessesUrl("/login");return filter;}@Overrideprotected 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 completedreturn;}//成功后会this.sessionStrategy.onAuthentication(authenticationResult, request, response);// Authentication successif (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 failedunsuccessfulAuthentication(request, response, ex);}}

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

	//将request中的参数提取出来保存到 Authentication 中, 返回给认证器认证, 就这么一步, 如果你是前后端分离, 就有可能需要重写该方法@Overridepublic 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令牌,继承自AuthenticationUsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);// Allow subclasses to set the "details" propertysetDetails(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();@Overridepublic 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();//支持多种认证,遍历所有AuthenticationProviderfor (org.springframework.security.authentication.AuthenticationProvider provider : getProviders()) {//匹配当前的Authenticationif (!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。
核心逻辑:

	//校验用户密码并返回用户信息@Overridepublic 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中覆写的方法。

	//从缓存或者数据库查询用户信息@Overrideprotected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {prepareTimingAttackProtection();try {//从缓存或者数据库查询用户信息//默认spring security保存在内存中, 如果你需要改从数据库中拿到用户, 就需要重写UserDetailsServiceUserDetails 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;@Autowiredpublic UserDetailsServiceImpl(UserRepository userRepository) {this.userRepository = userRepository;}@Overridepublic 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;@Autowiredpublic SecurityConfig(UserDetailsServiceImpl userDetailsService) {this.userDetailsService = userDetailsService;}@Overrideprotected 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();}@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/resources/**");}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());}//密码加密方法@Beanpublic BCryptPasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Overridepublic 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;@Autowiredpublic 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{@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {// 过滤器逻辑,在此可以做接口的token校验chain.doFilter(request, response);}
}//把自定义的过滤器加入到过滤器链中
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate CustomFilter customFilter;@Overrideprotected 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是否有效。有效正常访问接口,无效返回错误,让用户登录。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/106557.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

php的加密方式汇总

一、单列散列函数加密 1.md5()一般用于密码的不可逆加密 2.password_hash() //密码加密 $password 123456; $passwordHash password_hash($password,PASSWORD_DEFAULT,[cost > 12] );//密码验证 if (password_verify($password, $passwordHash)) {//Success } else {//…

8.Covector Transformation Rules

上一节已知&#xff0c;任意的协向量都可以写成对偶基向量的线性组合&#xff0c;以及如何通过计算基向量穿过的协向量线来获得协向量分量&#xff0c;且看到 协向量分量 以 与向量分量 相反的方式进行变换。 现要在数学上确认协向量变换规则是什么。 第一件事&#xff1a;…

前端小案例 | 一个带切换的登录注册界面(静态)

文章目录 &#x1f4da;HTML&#x1f4da;CSS&#x1f4da;JS &#x1f4da;HTML <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-sc…

紫光同创FPGA实现UDP协议栈网络视频传输,基于YT8511和RTL8211,提供4套PDS工程源码和技术支持

目录 1、前言免责声明 2、相关方案推荐我这里已有的以太网方案紫光同创FPGA精简版UDP方案紫光同创FPGA带ping功能UDP方案 3、设计思路框架OV7725摄像头配置及采集OV5640摄像头配置及采集UDP发送控制视频数据组包数据缓冲FIFOUDP协议栈详解RGMII转GMII动态ARPUDP协议IP地址、端口…

postgresql|数据库|恢复备份的时候报错:pg_restore: implied data-only restore的处理方案

一&#xff0c; 前情回顾 某次在使用pg_dump命令逻辑备份出来的备份文件对指定的几个表恢复的时候&#xff0c;报错pg_restore: implied data-only restore 当然&#xff0c;遇到问题首先就是百度了&#xff0c;但好像没有什么明确的解决方案&#xff0c;具体的报错命令和…

SpringData MongoDB学习总结

目录 一、简介 二、搭建 三、操作 &#xff08;1&#xff09;、集合操作 &#xff08;2&#xff09;、文档操作 相关注解 POJO 添加文档 查询文档 更新文档 删除文档 聚合操作 一、简介 NoSql数据库 键值对key-value 存储redis用户缓存&#xff0c;用户信息回话&a…

【深度学习 | Transformer】释放注意力的力量:探索深度学习中的 变形金刚,一文带你读通各个模块 —— Positional Encoding(一)

&#x1f935;‍♂️ 个人主页: AI_magician &#x1f4e1;主页地址&#xff1a; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 &#x1f468;‍&#x1f4bb;景愿&#xff1a;旨在于能和更多的热爱计算机的伙伴一起成长&#xff01;&#xff01;&…

kettle应用-从数据库抽取数据到excel

本文介绍使用kettle从postgresql数据库中抽取数据到excel中。 首先&#xff0c;启动kettle 如果kettle部署在windows系统&#xff0c;双击运行spoon.bat或者在命令行运行spoon.bat 如果kettle部署在linux系统&#xff0c;需要执行如下命令启动 chmod x spoon.sh nohup ./sp…

C#控制台程序读取输入按键非阻塞方式

参考内容&#xff1a; http://www.dutton.me.uk/2009-02-24/non-blocking-keyboard-input-in-c/ 相关代码&#xff1a; while (true) {if (Console.KeyAvailable){ConsoleKeyInfo key Console.ReadKey(true);switch (key.Key){case ConsoleKey.F1:Console.WriteLine("Y…

【计算机网络笔记】分组交换 vs 电路交换

系列文章目录 什么是计算机网络&#xff1f; 什么是网络协议&#xff1f; 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 系列文章目录 以一个具体的场景为例&#xff1a;N个用户共享这个1M bps链路。假设每一个用户活动时需要的链路带宽是100kb/s&#…

视频监控系统/安防视频平台EasyCVR广场视频细节优化

安防视频监控系统/视频云存储/安防监控EasyCVR视频汇聚平台基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。安防视频汇聚平台EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;可实现视频监控直播、视频轮播、…

Maven 使用教程(二)

一、如何创建JAR并将其安装在本地存储库中&#xff1f; 制作JAR文件非常简单&#xff0c;可以通过执行以下命令来完成&#xff1a; mvn package现在可以查看${project.basedir}/target目录&#xff0c;您将看到生成的JAR文件。 现在&#xff0c;您需要将生成的工件&#xff0…

华为9.20笔试 复现

第一题 丢失报文的位置 思路&#xff1a;从数组最小索引开始遍历 #include <iostream> #include <vector> using namespace std; // 求最小索引值 int getMinIdx(vector<int> &arr) {int minidx 0;for (int i 0; i < arr.size(); i){if (arr[i] …

spring boot Rabbit高级教程

消息可靠性 生产者重试机制 首先第一种情况&#xff0c;就是生产者发送消息时&#xff0c;出现了网络故障&#xff0c;导致与MQ的连接中断。 为了解决这个问题&#xff0c;SpringAMQP提供的消息发送时的重试机制。即&#xff1a;当RabbitTemplate与MQ连接超时后&#xff0c;…

【git】500 Whoops, something went wrong on our end.

在访问公的的git 时出现了500错误提示. 500 Whoops, something went wrong on our end. 哎呀&#xff0c;我们这边出了问题。 TMD 出了什么问题了&#xff1f;&#xff1f;&#xff1f;一脸懵逼。 登录git 服务器。 查看git的状态。 命令&#xff1a; gitlab-ctl statu…

互联网Java工程师面试题·Java 总结篇·第一弹

目录 1、面向对象的特征有哪些方面&#xff1f; 2、访问修饰符 public,private,protected,以及不写&#xff08;默认&#xff09;时的区别&#xff1f; 3、String 是最基本的数据类型吗&#xff1f; 4、float f3.4;是否正确&#xff1f; 5、short s1 1; s1 s1 1;有错吗…

华为OD机考算法题:开心消消乐

题目部分 题目开心消消乐难度易题目说明给定一个 N 行 M 列的二维矩阵&#xff0c;矩阵中每个位置的数字取值为 0 或 1&#xff0c;矩阵示例如&#xff1a; 1 1 0 0 0 0 0 1 0 0 1 1 1 1 1 1 现需要将矩阵中所有的 1 进行反转为 0&#xff0c;规则如下&#xff1a; 1) 当点击一…

java中char类型和byte类型的区别?

在Java中&#xff0c;char 类型和 byte 类型是两种不同的数据类型&#xff0c;以下是它们之间的主要区别&#xff1a; 表示范围&#xff1a;char 类型用于表示Unicode字符&#xff0c;它可以表示从U0000到UFFFF之间的字符。而 byte 类型是一个8位的有符号整数&#xff0c;可以表…

基于Spring Boot开发的汽车租赁管理系统

文章目录 项目介绍主要功能截图:后台前台部分代码展示设计总结项目获取方式🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 项目介绍 基于Spring Boot开发的汽车租赁…

动态规划算法(3)--0-1背包、石子合并、数字三角形

目录 一、0-1背包 1、概述 2、暴力枚举法 3、动态规划 二、石子合并问题 1、概述 2、动态规划 3、环形石子怎么办&#xff1f; 三、数字三角形问题 1、概述 2、递归 3、线性规划 四、租用游艇问题 一、0-1背包 1、概述 0-1背包&#xff1a;给定多种物品和一个固定…