2019独角兽企业重金招聘Python工程师标准>>>

UsernamePasswordAuthenticationFilter过滤器对应的类路径为
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
实际上这个Filter类的doFilter是父类AbstractAuthenticationProcessingFilter的

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) req;HttpServletResponse response = (HttpServletResponse) res;//判断form-login标签是否包含login-processing-url属性//如果没有采用默认的url:j_spring_security_check//如果拦截的url不需要认证,直接跳过if (!requiresAuthentication(request, response)) {chain.doFilter(request, response);return;}if (logger.isDebugEnabled()) {logger.debug("Request is to process authentication");}Authentication authResult;try {//由子类完成认证authResult = attemptAuthentication(request, response);if (authResult == null) {// return immediately as subclass has indicated that it hasn't completed authenticationreturn;}//session策略处理认证信息//sessionStrategy是通过session-management标签中定义的//session管理策略构造的SessionAuthenticationStrategy//具体的session管理比较复杂,部分后面单个篇幅讲解sessionStrategy.onAuthentication(authResult, request, response);}catch (AuthenticationException failed) {// Authentication failed//认证失败处理unsuccessfulAuthentication(request, response, failed);return;}// Authentication successif (continueChainBeforeSuccessfulAuthentication) {chain.doFilter(request, response);}//认证成功处理//1.向SecurityContext中设置Authentication认证信息//2.如果有remember me服务,则查找请求参数中是否包含_spring_security_remember_me,如果该参数值为true、yes、on、1则执行remember me功能:添加cookie、入库。为下次请求时自动登录做准备//3.发布认证成功事件//4.执行跳转successfulAuthentication(request, response, authResult);}

子类UsernamePasswordAuthenticationFilter的认证方法attemptAuthentication

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {//只处理post提交的请求if (postOnly && !request.getMethod().equals("POST")) {throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());}//获取用户名、密码数据String username = obtainUsername(request);String password = obtainPassword(request);if (username == null) {username = "";}if (password == null) {password = "";}username = username.trim();//构造未认证的UsernamePasswordAuthenticationTokenUsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);// Place the last username attempted into HttpSession for viewsHttpSession session = request.getSession(false);//如果session不为空,添加username到session中if (session != null || getAllowSessionCreation()) {request.getSession().setAttribute(SPRING_SECURITY_LAST_USERNAME_KEY, TextEscapeUtils.escapeEntities(username));}// Allow subclasses to set the "details" property//设置details,这里就是设置org.springframework.security.web.//authentication.WebAuthenticationDetails实例到details中setDetails(request, authRequest);//通过AuthenticationManager:ProviderManager完成认证任务return this.getAuthenticationManager().authenticate(authRequest);}

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

void createFormLoginFilter(BeanReference sessionStrategy, BeanReference authManager) {Element formLoginElt = DomUtils.getChildElementByTagName(httpElt, Elements.FORM_LOGIN);if (formLoginElt != null || autoConfig) {FormLoginBeanDefinitionParser parser = new FormLoginBeanDefinitionParser("/j_spring_security_check",AUTHENTICATION_PROCESSING_FILTER_CLASS, requestCache, sessionStrategy);parser.parse(formLoginElt, pc);formFilter = parser.getFilterBean();formEntryPoint = parser.getEntryPointBean();}if (formFilter != null) {formFilter.getPropertyValues().addPropertyValue("allowSessionCreation", new Boolean(allowSessionCreation));//设置authenticationManager的bean依赖formFilter.getPropertyValues().addPropertyValue("authenticationManager", authManager);// Id is required by login page filterformFilterId = pc.getReaderContext().generateBeanName(formFilter);pc.registerBeanComponent(new BeanComponentDefinition(formFilter, formFilterId));injectRememberMeServicesRef(formFilter, rememberMeServicesId);}}

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

public Authentication doAuthentication(Authentication authentication) throws AuthenticationException {Class<? extends Authentication> toTest = authentication.getClass();AuthenticationException lastException = null;Authentication result = null;//循环ProviderManager中的providers,由具体的provider执行认证操作for (AuthenticationProvider provider : getProviders()) {System.out.println("AuthenticationProvider: " + provider.getClass().getName());if (!provider.supports(toTest)) {continue;}logger.debug("Authentication attempt using " + provider.getClass().getName());try {result = provider.authenticate(authentication);if (result != null) {//复制detailscopyDetails(authentication, result);break;}} catch (AccountStatusException e) {// SEC-546: Avoid polling additional providers if auth failure is due to invalid account statuseventPublisher.publishAuthenticationFailure(e, authentication);throw e;} catch (AuthenticationException e) {lastException = e;}}if (result == null && parent != null) {// Allow the parent to try.try {result = parent.authenticate(authentication);} catch (ProviderNotFoundException e) {// ignore as we will throw below if no other exception occurred prior to calling parent and the parent// may throw ProviderNotFound even though a provider in the child already handled the request} catch (AuthenticationException e) {lastException = e;}}if (result != null) {eventPublisher.publishAuthenticationSuccess(result);return result;}// Parent was null, or didn't authenticate (or throw an exception).if (lastException == null) {lastException = new ProviderNotFoundException(messages.getMessage("ProviderManager.providerNotFound",new Object[] {toTest.getName()}, "No AuthenticationProvider found for {0}"));}//由注入进来的org.springframework.security.authentication.DefaultAuthenticationEventPublisher完成事件发布任务eventPublisher.publishAuthenticationFailure(lastException, authentication);throw lastException;}

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完成。代码如下

public Authentication authenticate(Authentication authentication) throws AuthenticationException {…………//获取登录的用户名String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();boolean cacheWasUsed = true;//如果配置了缓存,从缓存中获取UserDetails实例UserDetails user = this.userCache.getUserFromCache(username);if (user == null) {cacheWasUsed = false;try {//如果UserDetails为空,则由具体子类DaoAuthenticationProvider//根据用户名、authentication获取UserDetailsuser = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);} catch (UsernameNotFoundException notFound) {if (hideUserNotFoundExceptions) {throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));} else {throw notFound;}}Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");}try {//一些认证检查(账号是否可用、是否过期、是否被锁定)preAuthenticationChecks.check(user);//额外的密码检查(salt、passwordEncoder)additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);} catch (AuthenticationException exception) {if (cacheWasUsed) {// There was a problem, so try again after checking// we're using latest data (i.e. not from the cache)cacheWasUsed = false;user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);preAuthenticationChecks.check(user);additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);} else {throw exception;}}//检查账号是否过期postAuthenticationChecks.check(user);//添加UserDetails到缓存中if (!cacheWasUsed) {this.userCache.putUserInCache(user);}Object principalToReturn = user;if (forcePrincipalAsString) {principalToReturn = user.getUsername();}//返回成功认证后的Authenticationreturn createSuccessAuthentication(principalToReturn, authentication, user);}

继续看DaoAuthenticationProvider的retrieveUser方法

protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {UserDetails loadedUser;try {//最关键的部分登场了//UserDetailService就是authentication-provider标签中定义的//属性user-service-refloadedUser = this.getUserDetailsService().loadUserByUsername(username);}catch (DataAccessException repositoryProblem) {throw new AuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem);}if (loadedUser == null) {throw new AuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");}return loadedUser;}

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

<authentication-manager alias="authenticationManager"><authentication-provider user-service-ref="userDetailsManager"/>
</authentication-manager>

很多教程上说配置JdbcUserDetailsManager这个UserDetailsService,实际上该类的父类
JdbcDaoImpl方法loadUserByUsername代码如下:

public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {//根据username从数据库中查询User数据List<UserDetails> users = loadUsersByUsername(username);if (users.size() == 0) {throw new UsernameNotFoundException(messages.getMessage("JdbcDaoImpl.notFound", new Object[]{username}, "Username {0} not found"), username);}UserDetails user = users.get(0); // contains no GrantedAuthority[]Set<GrantedAuthority> dbAuthsSet = new HashSet<GrantedAuthority>();//添加授权信息if (enableAuthorities) {dbAuthsSet.addAll(loadUserAuthorities(user.getUsername()));}//是否使用了Groupif (enableGroups) {dbAuthsSet.addAll(loadGroupAuthorities(user.getUsername()));}List<GrantedAuthority> dbAuths = new ArrayList<GrantedAuthority>(dbAuthsSet);addCustomAuthorities(user.getUsername(), dbAuths);if (dbAuths.size() == 0) {throw new UsernameNotFoundException(messages.getMessage("JdbcDaoImpl.noAuthority",new Object[] {username}, "User {0} has no GrantedAuthority"), username);}return createUserDetails(username, user, dbAuths);}//usersByUsernameQuery查询语句可配置//直接从数据库中查询该username对应的数据,并构造User对象protected List<UserDetails> loadUsersByUsername(String username) {return getJdbcTemplate().query(usersByUsernameQuery, new String[] {username}, new RowMapper<UserDetails>() {public UserDetails mapRow(ResultSet rs, int rowNum) throws SQLException {String username = rs.getString(1);String password = rs.getString(2);boolean enabled = rs.getBoolean(3);return new User(username, password, enabled, true, true, true, AuthorityUtils.NO_AUTHORITIES);}});}……protected UserDetails createUserDetails(String username, UserDetails userFromUserQuery,List<GrantedAuthority> combinedAuthorities) {String returnUsername = userFromUserQuery.getUsername();if (!usernameBasedPrimaryKey) {returnUsername = username;}//根据用户名、密码、enabled、授权列表构造UserDetails实例Userreturn new User(returnUsername, userFromUserQuery.getPassword(), userFromUserQuery.isEnabled(),true, true, true, combinedAuthorities);}

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

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

转载于:https://my.oschina.net/snakerflow/blog/194515

Spring Security3源码分析-UsernamePasswordAuthenticationFilter分析相关推荐

  1. Spring Security3源码分析-http标签解析(转)

    为什么80%的码农都做不了架构师?>>>    在FilterChainProxy初始化的过程中,大概描述了标签解析的一些步骤,但不够详细   <http auto-confi ...

  2. Spring Cloud源码分析(二)Ribbon(续)

    因文章长度限制,故分为两篇.上一篇:<Spring Cloud源码分析(二)Ribbon> 负载均衡策略 通过上一篇对Ribbon的源码解读,我们已经对Ribbon实现的负载均衡器以及其中 ...

  3. Spring AOP 源码分析 - 拦截器链的执行过程

    1.简介 本篇文章是 AOP 源码分析系列文章的最后一篇文章,在前面的两篇文章中,我分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在我们的得 ...

  4. Spring AOP 源码分析 - 创建代理对象

    1.简介 在上一篇文章中,我分析了 Spring 是如何为目标 bean 筛选合适的通知器的.现在通知器选好了,接下来就要通过代理的方式将通知器(Advisor)所持有的通知(Advice)织入到 b ...

  5. Spring AOP 源码分析 - 筛选合适的通知器

    1.简介 从本篇文章开始,我将会对 Spring AOP 部分的源码进行分析.本文是 Spring AOP 源码分析系列文章的第二篇,本文主要分析 Spring AOP 是如何为目标 bean 筛选出 ...

  6. spring AOP源码分析(一)

    spring AOP源码分析(一) 对于springAOP的源码分析,我打算分三部分来讲解:1.配置文件的解析,解析为BeanDefination和其他信息然后注册到BeanFactory中:2.为目 ...

  7. Spring源码分析-Spring事务源码分析

    导语      在配置Spring事务管理的时候会用到一个类TransactionInterceptor,从下面的类关系图中可以看到TransactionInterceptor继承了MethodInt ...

  8. 一步一步手绘Spring AOP运行时序图(Spring AOP 源码分析)

    相关内容: 架构师系列内容:架构师学习笔记(持续更新) 一步一步手绘Spring IOC运行时序图一(Spring 核心容器 IOC初始化过程) 一步一步手绘Spring IOC运行时序图二(基于XM ...

  9. Spring源码总结与分析

    前言 Spring是什么?它是一个应用程序框架,为应用程序的开发提供强大的支持,例如对事务处理和持久化的支持等:它也是一个bean容器,管理bean对象的整个生命周期,维护bean的各种存在状态,例如 ...

最新文章

  1. 密码学摘要算法之MD5
  2. 高级程序设计 c语言 在线作业,[北京师范大学]19秋《高级程序设计(C)》离线作业(资料)...
  3. 【错误记录】Kotlin 编译报错 ( Smart cast to ‘Xxx‘ is impossible, because ‘xxx‘ is a mutable property ... )
  4. Java高级工程师学习路径
  5. apache-tomcat-6.0.39的配置
  6. MySQL怎么存base64编码_MySQL中如何将字符串转为base64编码?
  7. leetcode - 621. 任务调度器
  8. redis keys 模糊匹配_别找了,你要的Redis命令都在这了
  9. vscode 使用flask
  10. Linux 命令(71)—— ldconfig 命令
  11. Oracle 在安装时,安装文件的目录不能有汉字。
  12. linux 环境变量详解,Linux 环境变量详解及实例
  13. 如何让cmd一直默认以管理员身份打开
  14. 微信小程序canvas绘制圆形头像
  15. 行车记录仪摄像头4线
  16. Java开发常用英语单词表
  17. 习惯养成微信小程序的设计与实现
  18. Open3D键盘切换上下帧显示点云
  19. 故宫养心殿文物特展在沈阳展出
  20. opencv 实现视频倒放

热门文章

  1. java实现js取反_特定位取反(js实现)
  2. 【思考?】什么时候会触发这个策略呢?
  3. zookeeper的名词复盘-版本-保证分布式数据原子性
  4. 通道Channel-使用NIO 写入数据
  5. ConcurrentHashMap的源码分析-treeifyBin
  6. 完成AOP 顶层设计-CglibAopProxy
  7. 注解方式使用 Redis 缓存
  8. 什么是声明式事务控制
  9. canal数据同步(canal安装)
  10. 数据库-null值和notnull操作