Spring Security3源码分析

RememberMeAuthenticationFilter过滤器对应的类路径为
org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter
看主要的doFilter方法

1public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 2 throws IOException, ServletException { 3 HttpServletRequest request = (HttpServletRequest) req; 4 HttpServletResponse response = (HttpServletResponse) res; 5 //判断SecurityContext实例中是否存在Authentication认证信息 6 //没有认证的情况,才会做autoLogin的操作 7 if (SecurityContextHolder.getContext().getAuthentication() == null) { 8 //具体autoLogin过程后面分析,主要返回一个认证实体Authentication 9 Authentication rememberMeAuth = rememberMeServices.autoLogin(request, response); 10 11 if (rememberMeAuth != null) { 12 // Attempt authenticaton via AuthenticationManager 13 try { 14 //通过authenticationManager对该认证实体验证 15 //之前登录的Filter已经说明了,authenticationManager将 16 //将具体的认证工作交给provider完成 17 //而provider只负责自己关心的认证实体 18 //所以返回的认证实体RememberMeAuthenticationToken由 19 //RememberMeAuthenticationProvider完成认证的工作 20 //而这个provider也仅仅判断key是否相同,然后直接返回Authentication 21 rememberMeAuth = authenticationManager.authenticate(rememberMeAuth); 22 23 //设置认证信息到SecurityContext中 24 SecurityContextHolder.getContext().setAuthentication(rememberMeAuth); 25 //登录成功后的处理 26 onSuccessfulAuthentication(request, response, rememberMeAuth); 27 // Fire event 28 if (this.eventPublisher != null) { 29 eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent( 30 SecurityContextHolder.getContext().getAuthentication(), this.getClass())); 31 } 32 } catch (AuthenticationException authenticationException) { 33 …… 34 } 35 } 36 37 chain.doFilter(request, response); 38 } else { 39 …… 40 chain.doFilter(request, response); 41 } 42 }

下面看具体的autoLogin执行过程。
autoLogin是由RememberMeServices接口定义的方法,那么在RememberMeAuthenticationFilter类中的RememberMeServices实例也是通过解析http标签时注入到Filter中的。具体解析过程代码就不分析了。
如果remember-me标签有data-source-ref或token-repository-ref属性,RememberMeServices接口实现类为
PersistentTokenBasedRememberMeServices
其他情况下,没有设置services-ref属性,RememberMeServices接口实现类为
TokenBasedRememberMeServices
这两个实现类都继承了AbstractRememberMeServices抽象类

而autoLogin是AbstractRememberMeServices类完成的,看源码

1public final Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) { 2 //从cookie中获取remember me的相关信息 3 //cookie名称是SPRING_SECURITY_REMEMBER_ME_COOKIE 4 //cookie的value实际上是经过Base64做了两次编码算法处理的结果 5 //对应的是数据库中persistent_logins表中的series、token的数据 6 //数据库中保存的值是经过一次Base64编码的处理 7 String rememberMeCookie = extractRememberMeCookie(request); 8 9 if (rememberMeCookie == null) { 10 return null; 11 } 12 13 logger.debug("Remember-me cookie detected"); 14 15 UserDetails user = null; 16 17 try { 18 //所以这里只要解码一次就可以了 19 //这个数组中包含两个字符串,分别对应persistent_logins 20 //表中的series、token的数据 21 String[] cookieTokens = decodeCookie(rememberMeCookie); 22 //执行autoLogin的处理 23 user = processAutoLoginCookie(cookieTokens, request, response); 24 //账号状态检查,主要检查是否被锁,是否可用等 25 userDetailsChecker.check(user); 26 27 logger.debug("Remember-me cookie accepted"); 28 //返回UserDetails的实现类RememberMeAuthenticationToken 29 //注意这里构造的不是之前登录的那个UsernamePasswordAuthenticationToken了 30 return createSuccessfulAuthentication(request, user); 31 } catch (CookieTheftException cte) { 32 cancelCookie(request, response); 33 throw cte; 34 } catch (UsernameNotFoundException noUser) { 35 logger.debug("Remember-me login was valid but corresponding user not found.", noUser); 36 } catch (InvalidCookieException invalidCookie) { 37 logger.debug("Invalid remember-me cookie: " + invalidCookie.getMessage()); 38 } catch (AccountStatusException statusInvalid) { 39 logger.debug("Invalid UserDetails: " + statusInvalid.getMessage()); 40 } catch (RememberMeAuthenticationException e) { 41 logger.debug(e.getMessage()); 42 } 43 44 cancelCookie(request, response); 45 return null; 46 }

继续跟踪processAutoLoginCookie方法,该方法由具体子类实现,下面的代码从
PersistentTokenBasedRememberMeServices中截取

1protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) { 2 3 if (cookieTokens.length != 2) { 4 throw new InvalidCookieException("Cookie token did not contain " + 2 + 5 " tokens, but contained '" + Arrays.asList(cookieTokens) + "'"); 6 } 7 final String presentedSeries = cookieTokens[0]; 8 final String presentedToken = cookieTokens[1]; 9 //根据cookie中获取的Series数据从持久化载体(一般为数据库)中 10 //获取该Series对应的登录信息,构造PersistentRememberMeToken实例 11 PersistentRememberMeToken token = tokenRepository.getTokenForSeries(presentedSeries); 12 //此处省略一部分验证…… 13 //重新产生一个token并由当前时间构造新的PersistentRememberMeToken实例 14 PersistentRememberMeToken newToken = new PersistentRememberMeToken(token.getUsername(), 15 token.getSeries(), generateTokenData(), new Date()); 16 17 try { 18 //根据Series更新persistent_logins 19 tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(), newToken.getDate()); 20 //重新设置cookie 21 addCookie(newToken, request, response); 22 } catch (DataAccessException e) { 23 logger.error("Failed to update token: ", e); 24 throw new RememberMeAuthenticationException("Autologin failed due to data access problem"); 25 } 26 //直接根据username通过UserDetailsService实例的loadUserByUsername方法 27 //获取UserDetails对象并返回 28 UserDetails user = getUserDetailsService().loadUserByUsername(token.getUsername()); 29 30 return user; 31 }

这里需要注意的是getUserDetailsService方法返回的userDetailsService变量从哪里注入进来的呢?通过解析remember标签构造bean时注入的么?不是的,进一步观察remember标签解析代码

1public BeanDefinition parse(Element element, ParserContext pc) { 2 //此处省略了获取标签属性、判断等代码 3 boolean isPersistent = dataSourceSet | tokenRepoSet; 4 //如果配置了data-source-ref或token-repository-ref属性 5 if (isPersistent) { 6 Object tokenRepo; 7 services = new RootBeanDefinition(PersistentTokenBasedRememberMeServices.class); 8 9 if (tokenRepoSet) { 10 tokenRepo = new RuntimeBeanReference(tokenRepository); 11 } else { 12 //设置data-source-ref属性的情况 13 tokenRepo = new RootBeanDefinition(JdbcTokenRepositoryImpl.class); 14 ((BeanDefinition)tokenRepo).getPropertyValues().addPropertyValue("dataSource", 15 new RuntimeBeanReference(dataSource)); 16 } 17 //tokenRepository主要处理存储的功能 18 services.getPropertyValues().addPropertyValue("tokenRepository", tokenRepo); 19 } else if (!servicesRefSet) { 20 services = new RootBeanDefinition(TokenBasedRememberMeServices.class); 21 } 22 23 if (services != null) { 24 //注意:这里仅仅判断是否配置了user-service-ref属性 25 //如果配置了该属性,就会注入userDetailsService 26 //实际上很少配置该标签 27 if (userServiceSet) { 28 services.getPropertyValues().addPropertyValue("userDetailsService", new RuntimeBeanReference(userServiceRef)); 29 } 30 31 if ("true".equals(element.getAttribute(ATT_SECURE_COOKIE))) { 32 services.getPropertyValues().addPropertyValue("useSecureCookie", true); 33 } 34 35 if (tokenValiditySet) { 36 Integer tokenValidity = new Integer(tokenValiditySeconds); 37 if (tokenValidity.intValue() < 0 && isPersistent) { 38 pc.getReaderContext().error(ATT_TOKEN_VALIDITY + " cannot be negative if using" + 39 " a persistent remember-me token repository", source); 40 } 41 services.getPropertyValues().addPropertyValue("tokenValiditySeconds", tokenValidity); 42 } 43 services.setSource(source); 44 services.getPropertyValues().addPropertyValue("key", key); 45 servicesName = pc.getReaderContext().generateBeanName(services); 46 pc.registerBeanComponent(new BeanComponentDefinition(services, servicesName)); 47 } else { 48 servicesName = rememberMeServicesRef; 49 } 50 51 if (StringUtils.hasText(element.getAttribute(ATT_SERVICES_ALIAS))) { 52 pc.getRegistry().registerAlias(servicesName, element.getAttribute(ATT_SERVICES_ALIAS)); 53 } 54 55 BeanDefinition filter = createFilter(pc, source); 56 pc.popAndRegisterContainingComponent(); 57 58 return filter; 59 }

上面的解析过程已经很明确了,确实没有在这里注入userDetailsService。如果细心的话,会发现在解析http标签时,有这么一段代码

authBldr.createUserServiceInjector();

没错,就是这个方法完成动态注入的了

1void createUserServiceInjector() { 2 BeanDefinitionBuilder userServiceInjector = BeanDefinitionBuilder.rootBeanDefinition(UserDetailsServiceInjectionBeanPostProcessor.class); 3 userServiceInjector.addConstructorArgValue(x509ProviderId); 4 userServiceInjector.addConstructorArgValue(rememberMeServicesId); 5 userServiceInjector.addConstructorArgValue(openIDProviderId); 6 userServiceInjector.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); 7 pc.getReaderContext().registerWithGeneratedName(userServiceInjector.getBeanDefinition()); 8 }

UserDetailsServiceInjectionBeanPostProcessor实现了两个接口BeanPostProcessor, BeanFactoryAware,既可以动态改变bean,又能直接获取到BeanFactory对象再从ioc容器中获取具体的bean。
主要看postProcessBeforeInitialization方法

1public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { 2 if(beanName == null) { 3 return bean; 4 } 5 if (beanName.equals(x509ProviderId)) { 6 injectUserDetailsServiceIntoX509Provider((PreAuthenticatedAuthenticationProvider) bean); 7 } else if (beanName.equals(rememberMeServicesId)) { 8 injectUserDetailsServiceIntoRememberMeServices(bean); 9 } else if (beanName.equals(openIDProviderId)) { 10 injectUserDetailsServiceIntoOpenIDProvider(bean); 11 } 12 13 return bean; 14 }

只对x509ProviderId、rememberMeServicesId、openIDProviderId的bean动态注入userDetailsService实例。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )