我们在Java开发中,可能会经常使用到Spring Security来实现系统的权限控制。在企业级应用中,也有可能会使用Spring Security集成CAS来实现单点登录和权限控制。当然一些独立的系统(如人事管理系统等),也有可能会直接使用本系统的用户名密码认证。但是各位同学有没有想过,当系统由一个页面跳转至登录页时,当完成登录之后是如何跳转回原页面的呢?下面以Spring Security + CAS的场景为Demo介绍。
请求流程
Spring Security作为权限管理框架的粗略实现流程如下:
Spring Security中依赖org.springframework.security.web.FilterChainProxy 来执行所有的Filter。Security包含登录和授权两方面内容,登录主要集中在org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter,它对于用户名密码登录和CAS登录分别有如下的认证过滤器:
授权操作主要集中在 org.springframework.security.web.access.intercept.FilterSecurityInterceptor。所有的请求到了这一个filter,如果这个filter之前没有执行过的话,那么首先执行 super.beforeInvocation(fi) 这个是由AbstractSecurityInterceptor提供,它是Spring Security处理鉴权的入口。
代码实现
ExceptionTranslationFilter
ExceptionTranslationFilter 是Spring Security的核心filter之一,用来处理AuthenticationException和AccessDeniedException两种异常。AuthenticationException指的是未登录状态下访问受保护资源,AccessDeniedException指的是登陆了但是由于权限不足(比如普通用户访问管理员界面)。
当发生异常时,ExceptionTranslationFilter 持有两个处理类,分别是AuthenticationEntryPoint和AccessDeniedHandler。
ExceptionTranslationFilter 对异常的处理是通过这两个处理类实现的,处理规则很简单:
1、如果异常是 AuthenticationException,使用 AuthenticationEntryPoint 处理(我们可以通过自定义AuthenticationEntryPoint来引导用户登录)。
2、如果异常是 AccessDeniedException 且用户是匿名用户,使用 AuthenticationEntryPoint 处理。
3、如果异常是 AccessDeniedException 且用户不是匿名用户,如果否则交给 AccessDeniedHandler 处理。
主要判断逻辑如下所示:
AuthenticationEntryPoint 默认实现是 LoginUrlAuthenticationEntryPoint, 该类的处理是转发或重定向到登录页面,如下所示:
当我们使用Security与CAS集成时,可以重写 AuthenticationEntryPoint 如下:
AccessDeniedHandler 默认实现是 AccessDeniedHandlerImpl。该类对异常的处理是返回403错误码。如下所示:
用户未登录的情况下访问受保护资源,ExceptionTranslationFilter 捕获到AuthenticationException异常。页面需要跳转,ExceptionTranslationFilter在跳转前使用requestCache缓存request。
AbstractAuthenticationProcessingFilter
当用户在CAS完成登录操作之后,会让浏览器重定向至 https://www.moguhu.com/login/cas?ticket=ST-xxx (高版本Security,低版本的后缀为 /j_spring_cas_security_check)。校验通过后会进入到 CasAuthenticationFilter ,如下所示:
此时会调用如下方法(AbstractAuthenticationProcessingFilter):
上面的successHandler.onAuthenticationSuccess() (SavedRequestAwareAuthenticationSuccessHandler) 方法实现如下:
RequestCache的实现是 org.springframework.security.web.savedrequest.HttpSessionRequestCache,其实就是在Session中以key为 SPRING_SECURITY_SAVED_REQUEST 存储链接。到此,上面的跳转流程就通了。
参考:https://blog.coding.net/blog/Explore-the-cache-request-of-Security-Spring