SpringSecurity

文章来源:https://blog.csdn.net/qq_22172133/article/details/86503223

一、spring security 简介

        spring security 的核心功能主要包括:

  • 认证 (你是谁)
  • 授权 (你能干什么)
  • 攻击防护 (防止伪造身份)

     其核心就是一组过滤器链,项目启动后将会自动配置。最核心的就是 Basic Authentication Filter 用来认证用户的身份,一个在spring security中一种过滤器处理一种认证方式。

比如,对于username password认证过滤器来说, 

会检查是否是一个登录请求;

是否包含username 和 password (也就是该过滤器需要的一些认证信息) ;

如果不满足则放行给下一个。

     下一个按照自身职责判定是否是自身需要的信息,basic的特征就是在请求头中有 Authorization:Basic eHh4Onh4 的信息。中间可能还有更多的认证过滤器。最后一环是 FilterSecurityInterceptor,这里会判定该请求是否能进行访问rest服务,判断的依据是 BrowserSecurityConfig中的配置,如果被拒绝了就会抛出不同的异常(根据具体的原因)。Exception Translation Filter 会捕获抛出的错误,然后根据不同的认证方式进行信息的返回提示。

注意:绿色的过滤器可以配置是否生效,其他的都不能控制。

二、入门项目

     首先创建spring boot项目HelloSecurity,其pom主要依赖如下:


 
 
  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot </groupId>
  4. <artifactId>spring-boot-starter-thymeleaf </artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework.boot </groupId>
  8. <artifactId>spring-boot-starter-test </artifactId>
  9. <scope>test </scope>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.springframework.security </groupId>
  13. <artifactId>spring-security-test </artifactId>
  14. <scope>test </scope>
  15. </dependency>
  16. </dependencies>

然后在src/main/resources/templates/目录下创建页面:

home.html


 
 
  1. <!DOCTYPE html>
  2. <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
  3.     <head>
  4.         <title>Spring Security Example </title>
  5.     </head>
  6.     <body>
  7.         <h1>Welcome! </h1>
  8.         <p>Click <a th:href="@{/hello}">here </a> to see a greeting. </p>
  9.     </body>
  10. </html>

我们可以看到, 在这个简单的视图中包含了一个链接: “/hello”. 链接到了如下的页面,Thymeleaf模板如下:

hello.html


 
 
  1. <!DOCTYPE html>
  2. <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
  3.       xmlns:sec= "http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
  4.     <head>
  5.         <title>Hello World! </title>
  6.     </head>
  7.     <body>
  8.         <h1>Hello world! </h1>
  9.     </body>
  10. </html>

Web应用程序基于Spring MVC。 因此,你需要配置Spring MVC并设置视图控制器来暴露这些模板。 如下是一个典型的Spring MVC配置类。在src/main/java/hello目录下(所以java都在这里):


 
 
  1. @Configuration
  2. public class MvcConfig extends WebMvcConfigurerAdapter {
  3.     @Override
  4.     public void addViewControllers(ViewControllerRegistry registry) {
  5.        registry.addViewController( "/home").setViewName( "home");
  6.        registry.addViewController( "/").setViewName( "home");
  7.        registry.addViewController( "/hello").setViewName( "hello");
  8.        registry.addViewController( "/login").setViewName( "login");
  9.   }
  10. }

     addViewControllers()方法(覆盖WebMvcConfigurerAdapter中同名的方法)添加了四个视图控制器。 两个视图控制器引用名称为“home”的视图(在home.html中定义),另一个引用名为“hello”的视图(在hello.html中定义)。 第四个视图控制器引用另一个名为“login”的视图。 将在下一部分中创建该视图。此时,可以跳过来使应用程序可执行并运行应用程序,而无需登录任何内容。然后启动程序如下:


 
 
  1. @SpringBootApplication
  2. public class Application {
  3.     public static void main(String[] args) throws Throwable {
  4.        SpringApplication.run(Application.class, args);
  5.   }
  6. }

2、加入Spring Security

     假设你希望防止未经授权的用户访问“/ hello”。 此时,如果用户点击主页上的链接,他们会看到问候语,请求被没有被拦截。 你需要添加一个障碍,使得用户在看到该页面之前登录。您可以通过在应用程序中配置Spring Security来实现。 如果Spring Security在类路径上,则Spring Boot会使用“Basic认证”来自动保护所有HTTP端点。 同时,你可以进一步自定义安全设置。首先在pom文件中引入:


 
 
  1. <dependencies>
  2.   ...
  3.         <dependency>
  4.             <groupId>org.springframework.boot </groupId>
  5.             <artifactId>spring-boot-starter-security </artifactId>
  6.         </dependency>
  7.   ...
  8. </dependencies>

如下是安全配置,使得只有认证过的用户才可以访问到问候页面:


 
 
  1. @Configuration
  2. @EnableWebSecurity
  3. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  4.     @Override
  5.     protected void configure(HttpSecurity http) throws Exception {
  6.        http
  7.           .authorizeRequests()
  8.               .antMatchers( "/", "/home").permitAll()
  9.               .anyRequest().authenticated()
  10.               .and()
  11.           .formLogin()
  12.               .loginPage( "/login")
  13.               .permitAll()
  14.               .and()
  15.           .logout()
  16.               .permitAll();
  17.   }
  18.     @Autowired
  19.     public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
  20.        auth
  21.           .inMemoryAuthentication()
  22.               .withUser( "user").password( "password").roles( "USER");
  23.   }
  24. }

     WebSecurityConfig类使用了@EnableWebSecurity注解 ,以启用Spring Security的Web安全支持,并提供Spring MVC集成。它还扩展了WebSecurityConfigurerAdapter,并覆盖了一些方法来设置Web安全配置的一些细节。

     configure(HttpSecurity)方法定义了哪些URL路径应该被保护,哪些不应该。具体来说,“/”和“/ home”路径被配置为不需要任何身份验证。所有其他路径必须经过身份验证。

     当用户成功登录时,它们将被重定向到先前请求的需要身份认证的页面。有一个由 loginPage()指定的自定义“/登录”页面,每个人都可以查看它。

     对于configureGlobal(AuthenticationManagerBuilder) 方法,它将单个用户设置在内存中。该用户的用户名为“user”,密码为“password”,角色为“USER”。

     现在我们需要创建登录页面。前面我们已经配置了“login”的视图控制器,因此现在只需要创建登录页面即可:

login.html


 
 
  1. <!DOCTYPE html>
  2. <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
  3.       xmlns:sec= "http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
  4.     <head>
  5.         <title>Spring Security Example </title>
  6.     </head>
  7.     <body>
  8.         <div th:if="${param.error}">
  9.           Invalid username and password.
  10.         </div>
  11.         <div th:if="${param.logout}">
  12.           You have been logged out.
  13.         </div>
  14.         <form th:action="@{/login}" method="post">
  15.             <div> <label> User Name : <input type="text" name="username"/> </label> </div>
  16.             <div> <label> Password: <input type="password" name="password"/> </label> </div>
  17.             <div> <input type="submit" value="Sign In"/> </div>
  18.         </form>
  19.     </body>
  20. </html>

     你可以看到,这个Thymeleaf模板只是提供一个表单来获取用户名和密码,并将它们提交到“/ login”。 根据配置,Spring Security提供了一个拦截该请求并验证用户的过滤器。 如果用户未通过认证,该页面将重定向到“/ login?error”,并在页面显示相应的错误消息。 注销成功后,我们的应用程序将发送到“/ login?logout”,我们的页面显示相应的登出成功消息。最后,我们需要向用户提供一个显示当前用户名和登出的方法。 更新hello.html 向当前用户打印一句hello,并包含一个“注销”表单,如下所示:


 
 
  1. <!DOCTYPE html>
  2. <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
  3.       xmlns:sec= "http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
  4.     <head>
  5.         <title>Hello World! </title>
  6.     </head>
  7.     <body>
  8.         <h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]! </h1>
  9.         <form th:action="@{/logout}" method="post">
  10.             <input type="submit" value="Sign Out"/>
  11.         </form>
  12.     </body>
  13. </html>

三、参数详解

1、注解 @EnableWebSecurity

     在 Spring boot 应用中使用 Spring Security,用到了 @EnableWebSecurity注解,官方说明为,该注解和 @Configuration 注解一起使用, 注解 WebSecurityConfigurer 类型的类,或者利用@EnableWebSecurity 注解继承 WebSecurityConfigurerAdapter的类,这样就构成了 Spring Security 的配置。

2、抽象类 WebSecurityConfigurerAdapter

     一般情况,会选择继承 WebSecurityConfigurerAdapter 类,其官方说明为:WebSecurityConfigurerAdapter 提供了一种便利的方式去创建 WebSecurityConfigurer的实例,只需要重写 WebSecurityConfigurerAdapter 的方法,即可配置拦截什么URL、设置什么权限等安全控制。

3、方法 configure(AuthenticationManagerBuilder auth) 和 configure(HttpSecurity http)

     Demo 中重写了 WebSecurityConfigurerAdapter 的两个方法:


 
 
  1.   /**
  2.     * 通过 {@link #authenticationManager()} 方法的默认实现尝试获取一个 {@link AuthenticationManager}.
  3.     * 如果被复写, 应该使用{@link AuthenticationManagerBuilder} 来指定 {@link AuthenticationManager}.
  4.     *
  5.     * 例如, 可以使用以下配置在内存中进行注册公开内存的身份验证{@link UserDetailsService}:
  6.     *
  7.     * // 在内存中添加 user 和 admin 用户
  8.     * @Override
  9.     * protected void configure(AuthenticationManagerBuilder auth) {
  10.     *     auth
  11.     *       .inMemoryAuthentication().withUser("user").password("password").roles("USER").and()
  12.     *         .withUser("admin").password("password").roles("USER", "ADMIN");
  13.     * }
  14.     *
  15.     * // 将 UserDetailsService 显示为 Bean
  16.     * @Bean
  17.     * @Override
  18.     * public UserDetailsService userDetailsServiceBean() throws Exception {
  19.     *     return super.userDetailsServiceBean();
  20.     * }
  21.     *
  22.     */
  23.     protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  24.         this.disableLocalConfigureAuthenticationBldr = true;
  25.   }
  26.     /**
  27.     * 复写这个方法来配置 {@link HttpSecurity}.
  28.     * 通常,子类不能通过调用 super 来调用此方法,因为它可能会覆盖其配置。 默认配置为:
  29.     *
  30.     * http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
  31.     *
  32.     */
  33.     protected void configure(HttpSecurity http) throws Exception {
  34.        logger.debug( "Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
  35.        http
  36.           .authorizeRequests()
  37.               .anyRequest().authenticated()
  38.               .and()
  39.           .formLogin().and()
  40.           .httpBasic();
  41.   }

4、final 类 HttpSecurity

HttpSecurity 常用方法及说明:

方法说明
openidLogin()用于基于 OpenId 的验证
headers()将安全标头添加到响应
cors()配置跨域资源共享( CORS )
sessionManagement()允许配置会话管理
portMapper()允许配置一个PortMapper(HttpSecurity#(getSharedObject(class))),其他提供SecurityConfigurer的对象使用 PortMapper 从 HTTP 重定向到 HTTPS 或者从 HTTPS 重定向到 HTTP。默认情况下,Spring Security使用一个PortMapperImpl映射 HTTP 端口8080到 HTTPS 端口8443,HTTP 端口80到 HTTPS 端口443
jee()配置基于容器的预认证。 在这种情况下,认证由Servlet容器管理
x509()配置基于x509的认证
rememberMe允许配置“记住我”的验证
authorizeRequests()允许基于使用HttpServletRequest限制访问
requestCache()允许配置请求缓存
exceptionHandling()允许配置错误处理
securityContext()HttpServletRequests之间的SecurityContextHolder上设置SecurityContext的管理。 当使用WebSecurityConfigurerAdapter时,这将自动应用
servletApi()HttpServletRequest方法与在其上找到的值集成到SecurityContext中。 当使用WebSecurityConfigurerAdapter时,这将自动应用
csrf()添加 CSRF 支持,使用WebSecurityConfigurerAdapter时,默认启用
logout()添加退出登录支持。当使用WebSecurityConfigurerAdapter时,这将自动应用。默认情况是,访问URL”/ logout”,使HTTP Session无效来清除用户,清除已配置的任何#rememberMe()身份验证,清除SecurityContextHolder,然后重定向到”/login?success”
anonymous()允许配置匿名用户的表示方法。 当与WebSecurityConfigurerAdapter结合使用时,这将自动应用。 默认情况下,匿名用户将使用org.springframework.security.authentication.AnonymousAuthenticationToken表示,并包含角色 “ROLE_ANONYMOUS”
formLogin()指定支持基于表单的身份验证。如果未指定FormLoginConfigurer#loginPage(String),则将生成默认登录页面
oauth2Login()根据外部OAuth 2.0或OpenID Connect 1.0提供程序配置身份验证
requiresChannel()配置通道安全。为了使该配置有用,必须提供至少一个到所需信道的映射
httpBasic()配置 Http Basic 验证
addFilterAt()在指定的Filter类的位置添加过滤器

5、类 AuthenticationManagerBuilder


 
 
  1. /**
  2. * {@link SecurityBuilder} used to create an {@link AuthenticationManager}. Allows for
  3. * easily building in memory authentication, LDAP authentication, JDBC based
  4. * authentication, adding {@link UserDetailsService}, and adding
  5. * {@link AuthenticationProvider}'s.
  6. */

        意思是,AuthenticationManagerBuilder 用于创建一个 AuthenticationManager,让我能够轻松的实现内存验证、LADP验证、基于JDBC的验证、添加UserDetailsService、添加AuthenticationProvider。

四、原理讲解

1、校验流程图

 

2、源码分析

  • AbstractAuthenticationProcessingFilter 抽象类

 
 
  1. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
  2. throws IOException, ServletException {
  3. HttpServletRequest request = (HttpServletRequest) req;
  4. HttpServletResponse response = (HttpServletResponse) res;
  5. if (!requiresAuthentication(request, response)) {
  6. chain.doFilter(request, response);
  7. return;
  8. }
  9. if (logger.isDebugEnabled()) {
  10. logger.debug( "Request is to process authentication");
  11. }
  12. Authentication authResult;
  13. try {
  14. authResult = attemptAuthentication(request, response);
  15. if (authResult == null) {
  16. // return immediately as subclass has indicated that it hasn't completed
  17. // authentication
  18. return;
  19. }
  20. sessionStrategy.onAuthentication(authResult, request, response);
  21. }
  22. catch (InternalAuthenticationServiceException failed) {
  23. logger.error(
  24. "An internal error occurred while trying to authenticate the user.",
  25. failed);
  26. unsuccessfulAuthentication(request, response, failed);
  27. return;
  28. }
  29. catch (AuthenticationException failed) {
  30. // Authentication failed
  31. unsuccessfulAuthentication(request, response, failed);
  32. return;
  33. }
  34. // Authentication success
  35. if (continueChainBeforeSuccessfulAuthentication) {
  36. chain.doFilter(request, response);
  37. }
  38. successfulAuthentication(request, response, chain, authResult);
  39. }

        调用 requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。如果需要验证,则会调用 attemptAuthentication(HttpServletRequest, HttpServletResponse) 方法,有三种结果:

  1. 返回一个 Authentication 对象。配置的 SessionAuthenticationStrategy` 将被调用,然后 然后调用 successfulAuthentication(HttpServletRequest,HttpServletResponse,FilterChain,Authentication) 方法。
  2. 验证时发生 AuthenticationException。unsuccessfulAuthentication(HttpServletRequest, HttpServletResponse, AuthenticationException) 方法将被调用。
  3. 返回Null,表示身份验证不完整。假设子类做了一些必要的工作(如重定向)来继续处理验证,方法将立即返回。假设后一个请求将被这种方法接收,其中返回的Authentication对象不为空。
  • UsernamePasswordAuthenticationFilter(AbstractAuthenticationProcessingFilter的子类)

 
 
  1. public Authentication attemptAuthentication(HttpServletRequest request,
  2. HttpServletResponse response) throws AuthenticationException {
  3. if (postOnly && !request.getMethod().equals( "POST")) {
  4. throw new AuthenticationServiceException(
  5. "Authentication method not supported: " + request.getMethod());
  6. }
  7. String username = obtainUsername(request);
  8. String password = obtainPassword(request);
  9. if (username == null) {
  10. username = "";
  11. }
  12. if (password == null) {
  13. password = "";
  14. }
  15. username = username.trim();
  16. UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
  17. username, password);
  18. // Allow subclasses to set the "details" property
  19. setDetails(request, authRequest);
  20. return this.getAuthenticationManager().authenticate(authRequest);
  21. }

        attemptAuthentication () 方法将 request 中的 username 和 password 生成 UsernamePasswordAuthenticationToken 对象,用于 AuthenticationManager 的验证(即 this.getAuthenticationManager().authenticate(authRequest) )。默认情况下注入 Spring 容器的 AuthenticationManager 是 ProviderManager。

  • ProviderManager(AuthenticationManager的实现类)

 
 
  1. public Authentication authenticate(Authentication authentication)
  2. throws AuthenticationException {
  3. Class<? extends Authentication> toTest = authentication.getClass();
  4. AuthenticationException lastException = null;
  5. Authentication result = null;
  6. boolean debug = logger.isDebugEnabled();
  7. for (AuthenticationProvider provider : getProviders()) {
  8. if (!provider.supports(toTest)) {
  9. continue;
  10. }
  11. if (debug) {
  12. logger.debug( "Authentication attempt using "
  13. + provider.getClass().getName());
  14. }
  15. try {
  16. result = provider.authenticate(authentication);
  17. if (result != null) {
  18. copyDetails(authentication, result);
  19. break;
  20. }
  21. }
  22. catch (AccountStatusException e) {
  23. prepareException(e, authentication);
  24. // SEC-546: Avoid polling additional providers if auth failure is due to
  25. // invalid account status
  26. throw e;
  27. }
  28. catch (InternalAuthenticationServiceException e) {
  29. prepareException(e, authentication);
  30. throw e;
  31. }
  32. catch (AuthenticationException e) {
  33. lastException = e;
  34. }
  35. }
  36. if (result == null && parent != null) {
  37. // Allow the parent to try.
  38. try {
  39. result = parent.authenticate(authentication);
  40. }
  41. catch (ProviderNotFoundException e) {
  42. // ignore as we will throw below if no other exception occurred prior to
  43. // calling parent and the parent
  44. // may throw ProviderNotFound even though a provider in the child already
  45. // handled the request
  46. }
  47. catch (AuthenticationException e) {
  48. lastException = e;
  49. }
  50. }
  51. if (result != null) {
  52. if (eraseCredentialsAfterAuthentication
  53. && (result instanceof CredentialsContainer)) {
  54. // Authentication is complete. Remove credentials and other secret data
  55. // from authentication
  56. ((CredentialsContainer) result).eraseCredentials();
  57. }
  58. eventPublisher.publishAuthenticationSuccess(result);
  59. return result;
  60. }
  61. // Parent was null, or didn't authenticate (or throw an exception).
  62. if (lastException == null) {
  63. lastException = new ProviderNotFoundException(messages.getMessage(
  64. "ProviderManager.providerNotFound",
  65. new Object[] { toTest.getName() },
  66. "No AuthenticationProvider found for {0}"));
  67. }
  68. prepareException(lastException, authentication);
  69. throw lastException;
  70. }

        尝试验证 Authentication 对象。AuthenticationProvider 列表将被连续尝试,直到 AuthenticationProvider 表示它能够认证传递的过来的Authentication 对象。然后将使用该 AuthenticationProvider 尝试身份验证。如果有多个 AuthenticationProvider 支持验证传递过来的Authentication 对象,那么由第一个来确定结果,覆盖早期支持AuthenticationProviders 所引发的任何可能的AuthenticationException。 成功验证后,将不会尝试后续的AuthenticationProvider。如果最后所有的 AuthenticationProviders 都没有成功验证 Authentication 对象,将抛出 AuthenticationException。从代码中不难看出,由 provider 来验证 authentication, 核心点方法是:

Authentication result = provider.authenticate(authentication);
 
 

此处的 provider 是 AbstractUserDetailsAuthenticationProvider,AbstractUserDetailsAuthenticationProvider 是AuthenticationProvider的实现,看看它的 authenticate(authentication) 方法:


 
 
  1. // 验证 authentication
  2. public Authentication authenticate(Authentication authentication)
  3. throws AuthenticationException {
  4. Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
  5. messages.getMessage(
  6. "AbstractUserDetailsAuthenticationProvider.onlySupports",
  7. "Only UsernamePasswordAuthenticationToken is supported"));
  8. // Determine username
  9. String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
  10. : authentication.getName();
  11. boolean cacheWasUsed = true;
  12. UserDetails user = this.userCache.getUserFromCache(username);
  13. if (user == null) {
  14. cacheWasUsed = false;
  15. try {
  16. user = retrieveUser(username,
  17. (UsernamePasswordAuthenticationToken) authentication);
  18. }
  19. catch (UsernameNotFoundException notFound) {
  20. logger.debug( "User '" + username + "' not found");
  21. if (hideUserNotFoundExceptions) {
  22. throw new BadCredentialsException(messages.getMessage(
  23. "AbstractUserDetailsAuthenticationProvider.badCredentials",
  24. "Bad credentials"));
  25. }
  26. else {
  27. throw notFound;
  28. }
  29. }
  30. Assert.notNull(user,
  31. "retrieveUser returned null - a violation of the interface contract");
  32. }
  33. try {
  34. preAuthenticationChecks.check(user);
  35. additionalAuthenticationChecks(user,
  36. (UsernamePasswordAuthenticationToken) authentication);
  37. }
  38. catch (AuthenticationException exception) {
  39. if (cacheWasUsed) {
  40. // There was a problem, so try again after checking
  41. // we're using latest data (i.e. not from the cache)
  42. cacheWasUsed = false;
  43. user = retrieveUser(username,
  44. (UsernamePasswordAuthenticationToken) authentication);
  45. preAuthenticationChecks.check(user);
  46. additionalAuthenticationChecks(user,
  47. (UsernamePasswordAuthenticationToken) authentication);
  48. }
  49. else {
  50. throw exception;
  51. }
  52. }
  53. postAuthenticationChecks.check(user);
  54. if (!cacheWasUsed) {
  55. this.userCache.putUserInCache(user);
  56. }
  57. Object principalToReturn = user;
  58. if (forcePrincipalAsString) {
  59. principalToReturn = user.getUsername();
  60. }
  61. return createSuccessAuthentication(principalToReturn, authentication, user);
  62. }

AbstractUserDetailsAuthenticationProvider 内置了缓存机制,从缓存中获取不到的 UserDetails 信息的话,就调用如下方法获取用户信息,然后和 用户传来的信息进行对比来判断是否验证成功。


 
 
  1. // 获取用户信息
  2. UserDetails user = retrieveUser(username,
  3. (UsernamePasswordAuthenticationToken) authentication);

retrieveUser() 方法在 DaoAuthenticationProvider 中实现,DaoAuthenticationProvider 是 AbstractUserDetailsAuthenticationProvider的子类。具体实现如下:


 
 
  1. protected final UserDetails retrieveUser(String username,
  2. UsernamePasswordAuthenticationToken authentication)
  3. throws AuthenticationException {
  4. UserDetails loadedUser;
  5. try {
  6. loadedUser = this.getUserDetailsService().loadUserByUsername(username);
  7. }
  8. catch (UsernameNotFoundException notFound) {
  9. if (authentication.getCredentials() != null) {
  10. String presentedPassword = authentication.getCredentials().toString();
  11. passwordEncoder.isPasswordValid(userNotFoundEncodedPassword,
  12. presentedPassword, null);
  13. }
  14. throw notFound;
  15. }
  16. catch (Exception repositoryProblem) {
  17. throw new InternalAuthenticationServiceException(
  18. repositoryProblem.getMessage(), repositoryProblem);
  19. }
  20. if (loadedUser == null) {
  21. throw new InternalAuthenticationServiceException(
  22. "UserDetailsService returned null, which is an interface contract violation");
  23. }
  24. return loadedUser;
  25. }

可以看到此处的返回对象 userDetails 是由 UserDetailsService 的 #loadUserByUsername(username) 来获取的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值