php面试常问方法汇总
811
2022-05-30
Spring Security的会话管理
当浏览器调用登录接口登录成功后,服务端会和浏览器之间建立一个会话Session,浏览器在每次发送请求时都会携带一个SessionId,服务器会根据这个SessionId来判断用户身份。当浏览器关闭后,服务端的Session并不会自动销毁,需要开发者手动在服务端调用Session销毁方法,或者等Session过期时间到了自动销毁。
会话并发管理就是指在当前系统中,同一个用户可以同时创建多少个会话,如果一台设备对应一个会话,那么可以简单理解为同一个用户可以同时在多少台设备上登录,默认同一个用户在设备上登录并没有限制,可以在Security中配置。
protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .and() .csrf() .disable() .sessionManagement() .sessionFixation() .none() .maximumSessions(1) .expiredSessionStrategy(event -> { HttpServletResponse response = event.getResponse(); response.setContentType("application/json;charset=utf-8"); Map
在登录过滤器AbstractAuthenticationProcessingFilter的doFilter方法中,调用attemptAuthentication方法进行登录认证后,调用sessionStrategy.onAuthentication方法进行Session并发的管理,默认是CompositeSessionAuthenticationStrategy
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; 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 // authentication return; } sessionStrategy.onAuthentication(authResult, request, response); } catch (InternalAuthenticationServiceException failed) { logger.error( "An internal error occurred while trying to authenticate the user.", failed); unsuccessfulAuthentication(request, response, failed); return; } catch (AuthenticationException failed) { // Authentication failed unsuccessfulAuthentication(request, response, failed); return; } // Authentication success if (continueChainBeforeSuccessfulAuthentication) { chain.doFilter(request, response); } successfulAuthentication(request, response, chain, authResult); }
CompositeSessionAuthenticationStrategy的onAuthentication方法中遍历集合,依次调用集合元素的onAuthentication方法
public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) throws SessionAuthenticationException { for (SessionAuthenticationStrategy delegate : this.delegateStrategies) { if (this.logger.isDebugEnabled()) { this.logger.debug("Delegating to " + delegate); } delegate.onAuthentication(authentication, request, response); } }
sessionStrategy是AbstractAuthenticationFilterConfigurer类的configure方法中进行配置的,可以看到,这里从HttpSecurity的共享对象中获取到SessionAuthenticationStrategy的实例,并设置到authFilter过滤器中
public void configure(B http) throws Exception { PortMapper portMapper = http.getSharedObject(PortMapper.class); if (portMapper != null) { authenticationEntryPoint.setPortMapper(portMapper); } RequestCache requestCache = http.getSharedObject(RequestCache.class); if (requestCache != null) { this.defaultSuccessHandler.setRequestCache(requestCache); } authFilter.setAuthenticationManager(http .getSharedObject(AuthenticationManager.class)); authFilter.setAuthenticationSuccessHandler(successHandler); authFilter.setAuthenticationFailureHandler(failureHandler); if (authenticationDetailsSource != null) { authFilter.setAuthenticationDetailsSource(authenticationDetailsSource); } SessionAuthenticationStrategy sessionAuthenticationStrategy = http .getSharedObject(SessionAuthenticationStrategy.class); if (sessionAuthenticationStrategy != null) { authFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy); } RememberMeServices rememberMeServices = http .getSharedObject(RememberMeServices.class); if (rememberMeServices != null) { authFilter.setRememberMeServices(rememberMeServices); } F filter = postProcess(authFilter); http.addFilter(filter); }
SessionAuthenticationStrategy的实例是在SessionManagementConfigurer的init方法中存入的
public void init(H http) { SecurityContextRepository securityContextRepository = http .getSharedObject(SecurityContextRepository.class); boolean stateless = isStateless(); if (securityContextRepository == null) { if (stateless) { http.setSharedObject(SecurityContextRepository.class, new NullSecurityContextRepository()); } else { HttpSessionSecurityContextRepository httpSecurityRepository = new HttpSessionSecurityContextRepository(); httpSecurityRepository .setDisableUrlRewriting(!this.enableSessionUrlRewriting); httpSecurityRepository.setAllowSessionCreation(isAllowSessionCreation()); AuthenticationTrustResolver trustResolver = http .getSharedObject(AuthenticationTrustResolver.class); if (trustResolver != null) { httpSecurityRepository.setTrustResolver(trustResolver); } http.setSharedObject(SecurityContextRepository.class, httpSecurityRepository); } } RequestCache requestCache = http.getSharedObject(RequestCache.class); if (requestCache == null) { if (stateless) { http.setSharedObject(RequestCache.class, new NullRequestCache()); } } http.setSharedObject(SessionAuthenticationStrategy.class, getSessionAuthenticationStrategy(http)); http.setSharedObject(InvalidSessionStrategy.class, getInvalidSessionStrategy()); }
方法中 首先从HttpSecurity中获取SecurityContextRepository实例,没有则进行创建,创建的时候如果是Session的创建策略是STATELESS,则使用NullSecurityContextRepository来保存SecurityContext,如果不是则构建HttpSessionSecurityContextRepository,并存入HTTPSecurity共享对象中。
如果Session的创建策略是STATELESS,还要把请求缓存对象替换为NullRequestCache
最后构建SessionAuthenticationStrategy的实例和InvalidSessionStrategy的实例,SessionAuthenticationStrategy的实例从getSessionAuthenticationStrategy中获得
private SessionAuthenticationStrategy getSessionAuthenticationStrategy(H http) { if (this.sessionAuthenticationStrategy != null) { return this.sessionAuthenticationStrategy; } List
getSessionAuthenticationStrategy方法中把ConcurrentSessionControlAuthenticationStrategy ChangeSessionIdAuthenticationStrategy RegisterSessionAuthenticationStrategy添加到集合中,并返回代理类CompositeSessionAuthenticationStrategy
而sessionStrategy
ConcurrentSessionControlAuthenticationStrategy
主要用来处理Session并发问题,并发控制实际是由这个类来完成的
public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) { final List
从sessionRegistry中获取当前用户所有未失效的SessionInformation,然后获取当前项目允许的最大session数。如果获取到的SessionInformation实例小于当前项目允许的最大session数,说明当前登录没有问题,直接return
如果允许的最大session数为-1,表示应用并不限制登录并发数,当前登录没有问题,直接return
如果两者相等,判断当前sessionId是否在SessionInformation中,如果存在,直接return
超出最大并发数,进入allowableSessionsExceeded方法
protected void allowableSessionsExceeded(List
allowableSessionsExceeded方法中判断exceptionIfMaximumExceeded属性为true,则直接抛出异常,exceptionIfMaximumExceeded的属性是在SecurityConfig中
通过maxSessionPreventsLogin方法的值来改变,即禁止后来者的登录,抛出异常后,本次登录失败。否则对查询当前用户所有登录的session按照最后一次请求时间进行排序,计算出需要过期的session数量,从session集合中取出来进行遍历,依次调用expireNow方法让session过期。
ChangeSessionIdAuthenticationStrategy
通过修改sessionId来防止会话固定攻击。
所谓会话固定攻击是一种潜在的风险,恶意攻击者可能通过访问当前应用程序来创建会话,然后诱导用户以相同的会话Id登录,进而获取用户登录身份。
RegisterSessionAuthenticationStrategy
在认证成功后把HttpSession信息记录到SessionRegistry中。
public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) { sessionRegistry.registerNewSession(request.getSession().getId(), authentication.getPrincipal()); }
用户使用RememberMe的方式进行身份认证,则会通过SessionManagementFilter的doFilter方法触发Session并发管理。
SessionManagementConfigurer的configure方法中构建了这两个过滤器SessionManagementFilter和ConcurrentSessionFilter
public void configure(H http) { SecurityContextRepository securityContextRepository = http .getSharedObject(SecurityContextRepository.class); SessionManagementFilter sessionManagementFilter = new SessionManagementFilter( securityContextRepository, getSessionAuthenticationStrategy(http)); if (this.sessionAuthenticationErrorUrl != null) { sessionManagementFilter.setAuthenticationFailureHandler( new SimpleUrlAuthenticationFailureHandler( this.sessionAuthenticationErrorUrl)); } InvalidSessionStrategy strategy = getInvalidSessionStrategy(); if (strategy != null) { sessionManagementFilter.setInvalidSessionStrategy(strategy); } AuthenticationFailureHandler failureHandler = getSessionAuthenticationFailureHandler(); if (failureHandler != null) { sessionManagementFilter.setAuthenticationFailureHandler(failureHandler); } AuthenticationTrustResolver trustResolver = http .getSharedObject(AuthenticationTrustResolver.class); if (trustResolver != null) { sessionManagementFilter.setTrustResolver(trustResolver); } sessionManagementFilter = postProcess(sessionManagementFilter); http.addFilter(sessionManagementFilter); if (isConcurrentSessionControlEnabled()) { ConcurrentSessionFilter concurrentSessionFilter = createConcurrencyFilter(http); concurrentSessionFilter = postProcess(concurrentSessionFilter); http.addFilter(concurrentSessionFilter); } }
SessionManagementFilter创建过程中调用getSessionAuthenticationStrategy方法获取SessionAuthenticationStrategy的实例放入过滤器中,然后配置各种回调函数,最终创建的SessionManagementFilter过滤器放入HttpSecurity中。
如果开启会话并发控制(只要maximumSessions不会空就算开启会话并发控制),则创建ConcurrentSessionFilter过滤器 加入到HttpSecurity中。
总结
用户通过用户名密码发起认证请求,当认证成功后,在AbstractAuthenticationProcessingFilter的doFilter方法中触发Session并发管理。默认的sessionStrategy是CompositeSessionAuthenticationStrategy,它代理了三个类ConcurrentSessionControlAuthenticationStrategy ChangeSessionIdAuthenticationStrategy RegisterSessionAuthenticationStrategy。当前请求在这三个SessionAuthenticationStrategy中分别走一圈,第一个用来判断当前用户的Session数是否超过限制,第二个用来修改sessionId(防止会话固定攻击),第三个用来将当前Session注册到SessionRegistry中。
如果用户使用RememberMe的方式进行身份认证,则会通过SessionManagementFilter的doFilter方法触发Session并发管理。当用户认证成功后,以后的每一次请求都会经过ConcurrentSessionFilter,在该过滤器中,判断当前会话是否过期,如果过期执行注销流程,如果没有过期,更新最近一次请求时间。
统一身份认证服务 IAM
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。