Spring Security3源码分析(7)-UsernamePasswordAuthenticationFilter分析

UsernamePasswordAuthenticationFilter过滤器对应的类路径为 

org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter 

实际上这个Filter类的doFilter是父类AbstractAuthenticationProcessingFilter的 

Java代码  

  1. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)  
  2.         throws IOException, ServletException {  
  3.   
  4.     HttpServletRequest request = (HttpServletRequest) req;  
  5.     HttpServletResponse response = (HttpServletResponse) res;  
  6.     //判断form-login标签是否包含login-processing-url属性  
  7.       //如果没有采用默认的url:j_spring_security_check  
  8.     //如果拦截的url不需要认证,直接跳过  
  9.     if (!requiresAuthentication(request, response)) {  
  10.         chain.doFilter(request, response);  
  11.   
  12.         return;  
  13.     }  
  14.   
  15.     if (logger.isDebugEnabled()) {  
  16.         logger.debug(“Request is to process authentication”);  
  17.     }  
  18.   
  19.     Authentication authResult;  
  20.   
  21.     try {  
  22.         //由子类完成认证  
  23.         authResult = attemptAuthentication(request, response);  
  24.         if (authResult == null) {  
  25.             // return immediately as subclass has indicated that it hasn’t completed authentication  
  26.             return;  
  27.         }  
  28.         //session策略处理认证信息  
  29.           //sessionStrategy是通过session-management标签中定义的  
  30.           //session管理策略构造的SessionAuthenticationStrategy  
  31.         //具体的session管理比较复杂,部分后面单个篇幅讲解  
  32.         sessionStrategy.onAuthentication(authResult, request, response);  
  33.     }  
  34.     catch (AuthenticationException failed) {  
  35.         // Authentication failed  
  36.         //认证失败处理  
  37.         unsuccessfulAuthentication(request, response, failed);  
  38.   
  39.         return;  
  40.     }  
  41.   
  42.     // Authentication success  
  43.     if (continueChainBeforeSuccessfulAuthentication) {  
  44.         chain.doFilter(request, response);  
  45.     }  
  46.     //认证成功处理  
  47.      //1.向SecurityContext中设置Authentication认证信息  
  48.      //2.如果有remember me服务,则查找请求参数中是否包含_spring_security_remember_me,如果该参数值为true、yes、on、1则执行remember me功能:添加cookie、入库。为下次请求时自动登录做准备  
  49.      //3.发布认证成功事件  
  50.      //4.执行跳转  
  51.     successfulAuthentication(request, response, authResult);  
  52. }  

子类UsernamePasswordAuthenticationFilter的认证方法attemptAuthentication 

Java代码  

  1. public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {  
  2.     //只处理post提交的请求  
  3.     if (postOnly && !request.getMethod().equals(“POST”)) {  
  4.         throw new AuthenticationServiceException(“Authentication method not supported: “ + request.getMethod());  
  5.     }  
  6.     //获取用户名、密码数据  
  7.     String username = obtainUsername(request);  
  8.     String password = obtainPassword(request);  
  9.   
  10.     if (username == null) {  
  11.         username = “”;  
  12.     }  
  13.   
  14.     if (password == null) {  
  15.         password = “”;  
  16.     }  
  17.   
  18.     username = username.trim();  
  19.     //构造未认证的UsernamePasswordAuthenticationToken  
  20.     UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);  
  21.   
  22.     // Place the last username attempted into HttpSession for views  
  23.     HttpSession session = request.getSession(false);  
  24.     //如果session不为空,添加username到session中  
  25.     if (session != null || getAllowSessionCreation()) {  
  26.         request.getSession().setAttribute(SPRING_SECURITY_LAST_USERNAME_KEY, TextEscapeUtils.escapeEntities(username));  
  27.     }  
  28.   
  29.     // Allow subclasses to set the “details” property  
  30.     //设置details,这里就是设置org.springframework.security.web.  
  31.     //authentication.WebAuthenticationDetails实例到details中  
  32.     setDetails(request, authRequest);  
  33.     //通过AuthenticationManager:ProviderManager完成认证任务  
  34.     return this.getAuthenticationManager().authenticate(authRequest);  
  35. }  

这里的authenticationManager变量也是通过解析form-login标签,构造bean时注入的,具体解析类为:org.springframework.security.config.http.AuthenticationConfigBuilder 

代码片段为: 

Java代码  

  1. void createFormLoginFilter(BeanReference sessionStrategy, BeanReference authManager) {  
  2.   
  3.     Element formLoginElt = DomUtils.getChildElementByTagName(httpElt, Elements.FORM_LOGIN);  
  4.   
  5.     if (formLoginElt != null || autoConfig) {  
  6.         FormLoginBeanDefinitionParser parser = new FormLoginBeanDefinitionParser(“/j_spring_security_check”,  
  7.                 AUTHENTICATION_PROCESSING_FILTER_CLASS, requestCache, sessionStrategy);  
  8.   
  9.         parser.parse(formLoginElt, pc);  
  10.         formFilter = parser.getFilterBean();  
  11.         formEntryPoint = parser.getEntryPointBean();  
  12.     }  
  13.   
  14.     if (formFilter != null) {  
  15.         formFilter.getPropertyValues().addPropertyValue(“allowSessionCreation”new Boolean(allowSessionCreation));  
  16.         //设置authenticationManager的bean依赖  
  17.         formFilter.getPropertyValues().addPropertyValue(“authenticationManager”, authManager);  
  18.   
  19.   
  20.         // Id is required by login page filter  
  21.         formFilterId = pc.getReaderContext().generateBeanName(formFilter);  
  22.         pc.registerBeanComponent(new BeanComponentDefinition(formFilter, formFilterId));  
  23.         injectRememberMeServicesRef(formFilter, rememberMeServicesId);  
  24.     }  
  25. }  

继续看ProviderManager代码。实际上authenticate方法由ProviderManager的父类定义,并且authenticate方法内调用子类的doAuthentication方法,记得这是设计模式中的模板模式 

Java代码  

  1. public Authentication doAuthentication(Authentication authentication) throws AuthenticationException {  
  2.     Class<? extends Authentication> toTest = authentication.getClass();  
  3.     AuthenticationException lastException = null;  
  4.     Authentication result = null;  
  5.     //循环ProviderManager中的providers,由具体的provider执行认证操作  
  6.     for (AuthenticationProvider provider : getProviders()) {  
  7.         System.out.println(“AuthenticationProvider: “ + provider.getClass().getName());  
  8.         if (!provider.supports(toTest)) {  
  9.             continue;  
  10.         }  
  11.   
  12.         logger.debug(“Authentication attempt using “ + provider.getClass().getName());  
  13.   
  14.         try {  
  15.             result = provider.authenticate(authentication);  
  16.   
  17.             if (result != null) {  
  18.                 //复制details  
  19.                 copyDetails(authentication, result);  
  20.                 break;  
  21.             }  
  22.         } catch (AccountStatusException e) {  
  23.             // SEC-546: Avoid polling additional providers if auth failure is due to invalid account status  
  24.             eventPublisher.publishAuthenticationFailure(e, authentication);  
  25.             throw e;  
  26.         } catch (AuthenticationException e) {  
  27.             lastException = e;  
  28.         }  
  29.     }  
  30.   
  31.     if (result == null && parent != null) {  
  32.         // Allow the parent to try.  
  33.         try {  
  34.             result = parent.authenticate(authentication);  
  35.         } catch (ProviderNotFoundException e) {  
  36.             // ignore as we will throw below if no other exception occurred prior to calling parent and the parent  
  37.             // may throw ProviderNotFound even though a provider in the child already handled the request  
  38.         } catch (AuthenticationException e) {  
  39.             lastException = e;  
  40.         }  
  41.     }  
  42.   
  43.     if (result != null) {  
  44.         eventPublisher.publishAuthenticationSuccess(result);  
  45.         return result;  
  46.     }  
  47.   
  48.     // Parent was null, or didn’t authenticate (or throw an exception).  
  49.   
  50.     if (lastException == null) {  
  51.         lastException = new ProviderNotFoundException(messages.getMessage(“ProviderManager.providerNotFound”,  
  52.                     new Object[] {toTest.getName()}, “No AuthenticationProvider found for {0}”));  
  53.     }  
  54.     //由注入进来的org.springframework.security.authentication.DefaultAuthenticationEventPublisher完成事件发布任务  
  55.     eventPublisher.publishAuthenticationFailure(lastException, authentication);  
  56.   
  57.     throw lastException;  
  58. }  

ProviderManager类中的providers由哪些provider呢?如果看完authentication-manager标签解析的讲解,应该知道注入到providers中的provider分别为: 

org.springframework.security.authentication.dao.DaoAuthenticationProvider 

org.springframework.security.authentication.AnonymousAuthenticationProvider 

其他的provider根据特殊情况,再添加到providers中的,如remember me功能的provider 

org.springframework.security.authentication.RememberMeAuthenticationProvider 

可以看出来,ProviderManager仅仅是管理provider的,具体的authenticate认证任务由各自provider来完成。 

现在来看DaoAuthenticationProvider的认证处理,实际上authenticate由父类AbstractUserDetailsAuthenticationProvider完成。代码如下 

Java代码  

  1. public Authentication authenticate(Authentication authentication) throws AuthenticationException {  
  2.     …………  
  3.      //获取登录的用户名  
  4.     String username = (authentication.getPrincipal() == null) ? “NONE_PROVIDED” : authentication.getName();  
  5.   
  6.     boolean cacheWasUsed = true;  
  7.     //如果配置了缓存,从缓存中获取UserDetails实例  
  8.     UserDetails user = this.userCache.getUserFromCache(username);  
  9.   
  10.     if (user == null) {  
  11.         cacheWasUsed = false;  
  12.   
  13.         try {  
  14.             //如果UserDetails为空,则由具体子类DaoAuthenticationProvider  
  15.             //根据用户名、authentication获取UserDetails  
  16.             user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);  
  17.         } catch (UsernameNotFoundException notFound) {  
  18.             if (hideUserNotFoundExceptions) {  
  19.                 throw new BadCredentialsException(messages.getMessage(  
  20.                         “AbstractUserDetailsAuthenticationProvider.badCredentials”“Bad credentials”));  
  21.             } else {  
  22.                 throw notFound;  
  23.             }  
  24.         }  
  25.   
  26.         Assert.notNull(user, “retrieveUser returned null – a violation of the interface contract”);  
  27.     }  
  28.   
  29.     try {  
  30.         //一些认证检查(账号是否可用、是否过期、是否被锁定)  
  31.         preAuthenticationChecks.check(user);  
  32.         //额外的密码检查(salt、passwordEncoder)  
  33.         additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);  
  34.     } catch (AuthenticationException exception) {  
  35.         if (cacheWasUsed) {  
  36.             // There was a problem, so try again after checking  
  37.             // we’re using latest data (i.e. not from the cache)  
  38.             cacheWasUsed = false;  
  39.             user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);  
  40.             preAuthenticationChecks.check(user);  
  41.             additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);  
  42.         } else {  
  43.             throw exception;  
  44.         }  
  45.     }  
  46.     //检查账号是否过期  
  47.     postAuthenticationChecks.check(user);  
  48.     //添加UserDetails到缓存中  
  49.     if (!cacheWasUsed) {  
  50.         this.userCache.putUserInCache(user);  
  51.     }  
  52.   
  53.     Object principalToReturn = user;  
  54.   
  55.     if (forcePrincipalAsString) {  
  56.         principalToReturn = user.getUsername();  
  57.     }  
  58.     //返回成功认证后的Authentication  
  59.     return createSuccessAuthentication(principalToReturn, authentication, user);  
  60. }  

继续看DaoAuthenticationProvider的retrieveUser方法 

Java代码  

  1. protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)  
  2.         throws AuthenticationException {  
  3.     UserDetails loadedUser;  
  4.   
  5.     try {  
  6.         //最关键的部分登场了  
  7.           //UserDetailService就是authentication-provider标签中定义的  
  8.           //属性user-service-ref  
  9.         loadedUser = this.getUserDetailsService().loadUserByUsername(username);  
  10.     }  
  11.     catch (DataAccessException repositoryProblem) {  
  12.         throw new AuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem);  
  13.     }  
  14.   
  15.     if (loadedUser == null) {  
  16.         throw new AuthenticationServiceException(  
  17.                 “UserDetailsService returned null, which is an interface contract violation”);  
  18.     }  
  19.     return loadedUser;  
  20. }  

实际上,只要实现UserDetailsService接口的loadUserByUsername方法,就完成了登录认证的工作 

Xml代码  

  1. <authentication-manager alias=“authenticationManager”>  
  2.     <authentication-provider user-service-ref=“userDetailsManager”/>  
  3. </authentication-manager>  

很多教程上说配置JdbcUserDetailsManager这个UserDetailsService,实际上该类的父类 

JdbcDaoImpl方法loadUserByUsername代码如下: 

Java代码  

  1.     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {  
  2.         //根据username从数据库中查询User数据  
  3.         List<UserDetails> users = loadUsersByUsername(username);  
  4.   
  5.         if (users.size() == 0) {  
  6.             throw new UsernameNotFoundException(  
  7.                     messages.getMessage(“JdbcDaoImpl.notFound”new Object[]{username}, “Username {0} not found”), username);  
  8.         }  
  9.   
  10.         UserDetails user = users.get(0); // contains no GrantedAuthority[]  
  11.   
  12.         Set<GrantedAuthority> dbAuthsSet = new HashSet<GrantedAuthority>();  
  13.         //添加授权信息  
  14.         if (enableAuthorities) {  
  15.             dbAuthsSet.addAll(loadUserAuthorities(user.getUsername()));  
  16.         }  
  17.         //是否使用了Group  
  18.         if (enableGroups) {  
  19.             dbAuthsSet.addAll(loadGroupAuthorities(user.getUsername()));  
  20.         }  
  21.   
  22.         List<GrantedAuthority> dbAuths = new ArrayList<GrantedAuthority>(dbAuthsSet);  
  23.   
  24.         addCustomAuthorities(user.getUsername(), dbAuths);  
  25.   
  26.         if (dbAuths.size() == 0) {  
  27.             throw new UsernameNotFoundException(  
  28.                     messages.getMessage(“JdbcDaoImpl.noAuthority”,  
  29.                             new Object[] {username}, “User {0} has no GrantedAuthority”), username);  
  30.         }  
  31.   
  32.         return createUserDetails(username, user, dbAuths);  
  33.     }  
  34.   
  35.     //usersByUsernameQuery查询语句可配置  
  36.      //直接从数据库中查询该username对应的数据,并构造User对象  
  37.     protected List<UserDetails> loadUsersByUsername(String username) {  
  38.         return getJdbcTemplate().query(usersByUsernameQuery, new String[] {username}, new RowMapper<UserDetails>() {  
  39.             public UserDetails mapRow(ResultSet rs, int rowNum) throws SQLException {  
  40.                 String username = rs.getString(1);  
  41.                 String password = rs.getString(2);  
  42.                 boolean enabled = rs.getBoolean(3);  
  43.                 return new User(username, password, enabled, truetruetrue, AuthorityUtils.NO_AUTHORITIES);  
  44.             }  
  45.   
  46.         });  
  47.     }  
  48.   
  49. ……  
  50.     protected UserDetails createUserDetails(String username, UserDetails userFromUserQuery,  
  51.             List<GrantedAuthority> combinedAuthorities) {  
  52.         String returnUsername = userFromUserQuery.getUsername();  
  53.   
  54.         if (!usernameBasedPrimaryKey) {  
  55.             returnUsername = username;  
  56.         }  
  57.         //根据用户名、密码、enabled、授权列表构造UserDetails实例User  
  58.         return new User(returnUsername, userFromUserQuery.getPassword(), userFromUserQuery.isEnabled(),  
  59.                 truetruetrue, combinedAuthorities);  
  60.     }  

其他的provider,如 

RememberMeAuthenticationProvider、AnonymousAuthenticationProvider的认证处理都很简单,首先判断是否支持Authentication,不支持直接返回null,支持也不处理直接返回该Authentication 

这里需要强调一下,DaoAuthenticationProvider只支持UsernamePasswordAuthenticationToken这个Authentication。如果对其他的Authentication,DaoAuthenticationProvider是不做处理的

    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/benjamin_whx/article/details/39204637
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞