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

 2019-11-02 21:26  阅读(1488)
文章分类:Spring boot

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

子类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. if (username == null) {
  10. username = “”;
  11. }
  12. if (password == null) {
  13. password = “”;
  14. }
  15. username = username.trim();
  16. //构造未认证的UsernamePasswordAuthenticationToken
  17. UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
  18. // Place the last username attempted into HttpSession for views
  19. HttpSession session = request.getSession(false);
  20. //如果session不为空,添加username到session中
  21. if (session != null || getAllowSessionCreation()) {
  22. request.getSession().setAttribute(SPRING_SECURITY_LAST_USERNAME_KEY, TextEscapeUtils.escapeEntities(username));
  23. }
  24. // Allow subclasses to set the “details” property
  25. //设置details,这里就是设置org.springframework.security.web.
  26. //authentication.WebAuthenticationDetails实例到details中
  27. setDetails(request, authRequest);
  28. //通过AuthenticationManager:ProviderManager完成认证任务
  29. return this.getAuthenticationManager().authenticate(authRequest);
  30. }

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

代码片段为:

Java代码

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

继续看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. logger.debug(“Authentication attempt using “ + provider.getClass().getName());
  12. try {
  13. result = provider.authenticate(authentication);
  14. if (result != null) {
  15. //复制details
  16. copyDetails(authentication, result);
  17. break;
  18. }
  19. } catch (AccountStatusException e) {
  20. // SEC-546: Avoid polling additional providers if auth failure is due to invalid account status
  21. eventPublisher.publishAuthenticationFailure(e, authentication);
  22. throw e;
  23. } catch (AuthenticationException e) {
  24. lastException = e;
  25. }
  26. }
  27. if (result == null && parent != null) {
  28. // Allow the parent to try.
  29. try {
  30. result = parent.authenticate(authentication);
  31. } catch (ProviderNotFoundException e) {
  32. // ignore as we will throw below if no other exception occurred prior to calling parent and the parent
  33. // may throw ProviderNotFound even though a provider in the child already handled the request
  34. } catch (AuthenticationException e) {
  35. lastException = e;
  36. }
  37. }
  38. if (result != null) {
  39. eventPublisher.publishAuthenticationSuccess(result);
  40. return result;
  41. }
  42. // Parent was null, or didn’t authenticate (or throw an exception).
  43. if (lastException == null) {
  44. lastException = new ProviderNotFoundException(messages.getMessage(“ProviderManager.providerNotFound”,
  45. new Object[] {toTest.getName()}, “No AuthenticationProvider found for {0}”));
  46. }
  47. //由注入进来的org.springframework.security.authentication.DefaultAuthenticationEventPublisher完成事件发布任务
  48. eventPublisher.publishAuthenticationFailure(lastException, authentication);
  49. throw lastException;
  50. }

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

继续看DaoAuthenticationProvider的retrieveUser方法

Java代码

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

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

其他的provider,如

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

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


来源:http://ddrv.cn

点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> Spring Security3源码分析(7)-UsernamePasswordAuthenticationFilter分析

相关推荐