Springboot security cas源码陶冶-CasAuthenticationFilter
Springboot security cas整合方案中不可或缺的校验Filter类或者称为认证Filter类,其内部包含校验器、权限获取等,特开辟新地啃啃
继承结构
- AbstractAuthenticationProcessingFilter
- CasAuthenticationFilter
其中父类AbstractAuthenticationProcessingFilter#doFilter()
是模板处理逻辑方法,而子类主要实现了校验方法CasAuthenticationFilter#attemptAuthentication()
方法。下面就对这两块进行代码层面的分析
AbstractAuthenticationProcessingFilter#doFilter-处理逻辑
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
//是否需要验证,这里cas子类对其进行了复写
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;
}
//session策略校验,默认不校验
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
//认证成功后是否还往下走,默认为false
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
//直接跳转至配置好的登录成功页面,这里cas子类对其进行了复写
successfulAuthentication(request, response, chain, authResult);
}
其中CasAuthenticationFilter
对以下方法进行了复写,分别为requiresAuthentication()
、successfulAuthentication()
、attemptAuthentication()
方法
CasAuthenticationFilter#requiresAuthentication-是否校验判断
protected boolean requiresAuthentication(final HttpServletRequest request,
final HttpServletResponse response) {
//是否与设置的登录路径匹配
final boolean serviceTicketRequest = serviceTicketRequest(request, response);
//对含有ticket参数的请求会返回true
final boolean result = serviceTicketRequest || proxyReceptorRequest(request)
|| (proxyTicketRequest(serviceTicketRequest, request));
if (logger.isDebugEnabled()) {
logger.debug("requiresAuthentication = " + result);
}
return result;
}
对login请求以及token请求则返回true表示需要验证
CasAuthenticationFilter#attemptAuthentication-具体校验处理
@Override
public Authentication attemptAuthentication(final HttpServletRequest request,
final HttpServletResponse response) throws AuthenticationException,
IOException {
// if the request is a proxy request process it and return null to indicate the
// request has been processed
//代理服务的请求处理,涉及PGT
if (proxyReceptorRequest(request)) {
logger.debug("Responding to proxy receptor request");
//直接响应输出
CommonUtils.readAndRespondToProxyReceptorRequest(request, response,
this.proxyGrantingTicketStorage);
return null;
}
//判断是否对应指定的请求(login请求),支持ant-style方式
final boolean serviceTicketRequest = serviceTicketRequest(request, response);
//login请求为"_cas_stateful_",非login请求为"_cas_stateless_"
final String username = serviceTicketRequest ? CAS_STATEFUL_IDENTIFIER
: CAS_STATELESS_IDENTIFIER;
//获取ticket
String password = obtainArtifact(request);
//passwprd一般不可为空,这在provider处理类中会抛异常
if (password == null) {
logger.debug("Failed to obtain an artifact (cas ticket)");
password = "";
}
final UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
//通过CasAuthenticationProvider来进行具体的校验,包含ticket验证以及当前用户权限集合获取
return this.getAuthenticationManager().authenticate(authRequest);
}
具体的校验通过CasAuthenticationProvider
来实现
CasAuthenticationProvider-cas校验器
cas校验器,看下主要的实现方法
- CasAuthenticationProvider#afterPropertiesSet()
主要是检验必须的属性是否设置public void afterPropertiesSet() throws Exception {
//权限获取处理对象
Assert.notNull(this.authenticationUserDetailsService,
"An authenticationUserDetailsService must be set");
//ticket校验器
Assert.notNull(this.ticketValidator, "A ticketValidator must be set");
//stateless对应的缓存,默认为NullStatelessTicketCache
Assert.notNull(this.statelessTicketCache, "A statelessTicketCache must be set");
//必须设置key
Assert.hasText(
this.key,
"A Key is required so CasAuthenticationProvider can identify tokens it previously authenticated");
//默认为SpringSecurityMessageSource.getAccessor()
Assert.notNull(this.messages, "A message source must be set");
}
- CasAuthenticationProvider#authenticate
校验处理方法,源码如下//此处传过来的authentication类型为UsernamePasswordAuthenticationToken
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
//此处为true
if (!supports(authentication.getClass())) {
return null;
} if (authentication instanceof UsernamePasswordAuthenticationToken
&& (!CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER
.equals(authentication.getPrincipal().toString()) && !CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER
.equals(authentication.getPrincipal().toString()))) {
// UsernamePasswordAuthenticationToken not CAS related
return null;
} // If an existing CasAuthenticationToken, just check we created it
if (authentication instanceof CasAuthenticationToken) {
if (this.key.hashCode() == ((CasAuthenticationToken) authentication)
.getKeyHash()) {
return authentication;
}
else {
throw new BadCredentialsException(
messages.getMessage("CasAuthenticationProvider.incorrectKey",
"The presented CasAuthenticationToken does not contain the expected key"));
}
} // Ensure credentials are presented,确保ticket不为空,否则将抛出异常
if ((authentication.getCredentials() == null)
|| "".equals(authentication.getCredentials())) {
throw new BadCredentialsException(messages.getMessage(
"CasAuthenticationProvider.noServiceTicket",
"Failed to provide a CAS service ticket to validate"));
} boolean stateless = false; if (authentication instanceof UsernamePasswordAuthenticationToken
&& CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER.equals(authentication
.getPrincipal())) {
stateless = true;
} CasAuthenticationToken result = null;
//对非login请求的尝试从缓存中获取
if (stateless) {
// Try to obtain from cache
result = statelessTicketCache.getByTicketId(authentication.getCredentials()
.toString());
} if (result == null) {
//第一次校验则用ticketValidator去cas服务端进行ticket校验
result = this.authenticateNow(authentication);
result.setDetails(authentication.getDetails());
}
//对非login请求的castoken进行缓存
if (stateless) {
// Add to cache
statelessTicketCache.putTicketInCache(result);
} return result;
}
- CasAuthenticationProvider#authenticateNow
实际的校验处理方法,源码如下private CasAuthenticationToken authenticateNow(final Authentication authentication)
throws AuthenticationException {
try {
//TicketValidator一般只需要设置casServerUrlPrefix前缀,实际的请求全路径如下,以Cas20ServiceTicketValidator为例
//https://example.casserver.com/cas/serviceValidator?service=https://example.casclient.com/
final Assertion assertion = this.ticketValidator.validate(authentication
.getCredentials().toString(), getServiceUrl(authentication));
//调用authenticationUserDetailsService获取当前用户所拥有的权限
final UserDetails userDetails = loadUserByAssertion(assertion);
userDetailsChecker.check(userDetails);
//组装成CasAuthenticationToken来保存校验信息,供保存至spring的安全上下文中
return new CasAuthenticationToken(this.key, userDetails,
authentication.getCredentials(),
authoritiesMapper.mapAuthorities(userDetails.getAuthorities()),
userDetails, assertion);
}
catch (final TicketValidationException e) {
//ticket校验失败则抛出异常,此异常会被父类获取而调用failerhandler将错误写向页面
throw new BadCredentialsException(e.getMessage(), e);
}
}
- CasAuthenticationProvider的必要属性含义
- authenticationUserDetailsService-权限获取对象
- ticketValidator-ticket校验器,其中需要设置cas服务端的校验地址前缀
casServerUrlPrefix
- key-设置唯一标识
- CasAuthenticationProvider校验过程中如果ticket为空或者ticket校验失败都会由
AbstractAuthenticationProcessingFilter
类抓取并将错误信息写入到页面中,从而关于ticket的异常信息都会显示至前端页面- CasAuthenticationProvider校验成功后会生成
CasAuthenticationToken
,且设置authenticated
为true
并保存至spring的安全上下文中,这在FilterSecurityInterceptor
Filter类会有所作用
CasAuthenticationFilter#successfulAuthentication-校验成功处理
protected final void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
//如果请求含有ticket参数,返回true
//login请求则直接返回false从而调用父类的successfulAuthentication()来直接响应页面
boolean continueFilterChain = proxyTicketRequest(
serviceTicketRequest(request, response), request);
if (!continueFilterChain) {
super.successfulAuthentication(request, response, chain, authResult);
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
+ authResult);
}
//保存Authentication凭证信息
SecurityContextHolder.getContext().setAuthentication(authResult);
// Fire event
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
authResult, this.getClass()));
}
//往下继续走
chain.doFilter(request, response);
}
小结
CasAuthenticationFilter的放行策略:非登录请求;非代理接收请求;非ticket请求
对登录请求的成功处理是直接跳转至指定的页面,可通过
SimpleUrlAuthenticationSuccessHandler#setDefaultTargetUrl(String url)
设置;
对非登录请求比如token请求
的操作将保存校验通过的Authentication
对象至SecurityContextHolder.getContext()
上下文中再放行CasAuthenticationProvider校验过程中如果ticket为空或者ticket校验失败都会由
AbstractAuthenticationProcessingFilter
类抓取并将错误信息写入到页面中,从而关于ticket的异常信息都会显示至前端页面
温馨提示:cas服务端登录成功后的service路径不要为login请求,避免token没拿到就被拦截从而输出错误页面其中对ticket进行校验的是
CasAuthenticationProvider
对象,包括ticket校验以及权限获取
Springboot security cas源码陶冶-CasAuthenticationFilter的更多相关文章
- Springboot security cas源码陶冶-ExceptionTranslationFilter
拦截关键的两个异常,对异常进行处理.主要应用异常则跳转至cas服务端登录页面 ExceptionTranslationFilter#doFilter-逻辑入口 具体操作逻辑如下 public void ...
- Springboot security cas源码陶冶-FilterSecurityInterceptor
前言:用户登录信息校验成功后,都会获得当前用户所拥有的全部权限,所以对访问的路径当前用户有无权限则需要拦截验证一发 Spring security过滤器的执行顺序 首先我们需要验证为啥FilterSe ...
- Springboot security cas整合方案-原理篇
前言:网络中关于Spring security整合cas的方案有很多例,对于Springboot security整合cas方案则比较少,且有些仿制下来运行也有些错误,所以博主在此篇详细的分析cas原 ...
- Springboot security cas整合方案-实践篇
承接前文Springboot security cas整合方案-原理篇,请在理解原理的情况下再查看实践篇 maven环境 <dependency> <groupId>org.s ...
- Spring-shiro源码陶冶-DelegatingFilterProxy和ShiroFilterFactoryBean
阅读源码有助于陶冶情操,本文旨在简单的分析shiro在Spring中的使用 简单介绍 Shiro是一个强大易用的Java安全框架,提供了认证.授权.加密和会话管理等功能 web.xml配置Shiro环 ...
- 修改CAS源码是的基于DB的认证方式配置更灵活
最近在做CAS配置的时候,遇到了数据源不提供密码等数据的情况下,怎样实现密码输入认证呢? 第一步:新建Java项目,根据假面算法生成CAS加密工具 出于保密需要不提供自定义的加密工具,在您的实际项目中 ...
- Spring-shiro源码陶冶-DefaultFilter
阅读源码有助于陶冶情操,本文旨在简单的分析shiro在Spring中的使用 简单介绍 Shiro是一个强大易用的Java安全框架,提供了认证.授权.加密和会话管理等功能 Apache Shiro自带的 ...
- SpringBoot自动配置源码调试
之前对SpringBoot的自动配置原理进行了较为详细的介绍(https://www.cnblogs.com/stm32stm32/p/10560933.html),接下来就对自动配置进行源码调试,探 ...
- 调试CAS源码步骤
1.先安装gradle2.eclipse安装gradle(sts)插件3.克隆cas源码 这一块需要很长时间4.gradle build 会遇到安装node.js 的模块 不存在的问题. 按提示解决就 ...
随机推荐
- 基于C#的数据库文件管理助手
我们经常会遇到这样的问题,在数据库中的文件存放的是web格式或者是绝对路径,以及使用的是百度上传或者其他上传组件,造成了很多异步上传的冗余文件,如果客户需要我们导出企业官网中的产品图片,我们该如何处理 ...
- c++中的overload、overwrite、override
作为初学者,本文只从语法和简单的使用角度对overload.overwrite.override进行了区分,不曾涉及原理,记录下来以供查阅. 1.verload(重载) 1.1 基本要求: c++中的 ...
- github网站介绍、并使用git命令管理github(详细描述)
本章学习: 1)熟悉github网站 2)通过git命令远程管理github, 3)git命令使用ssh key密钥无需输入账号密码 1.首先我们来熟悉github网站 1.1 注册github 登录 ...
- java IO流、集合类部分小知识点总结
在Java中,以下三个类经常用于处理数据流,下面介绍一下三个类的不同之处以及各自的用法. InputStream : 是所有字节输入流的超类,一般使用它的子类:FileInputStream等,它能输 ...
- php的底层原理
PHP说简单,但是要精通也不是一件简单的事.我们除了会使用之外,还得知道它底层的工作原理. PHP是一种适用于web开发的动态语言.具体点说,就是一个用C语言实现包含大量组件的软件框架.更狭义点看,可 ...
- 阿里巴巴Java开发手册评
2016年底的时候阿里巴巴公开了其在内部使用的Java编程规范.随后进行了几次版本修订,目前的版本为v1.0.2版.下载地址可以在其官方社区-云栖社区https://yq.aliyun.com/art ...
- 从CUMT校园导航出现的问题看CSS布局设计(一) CSS盒模型
先说说做的这个校园导航系统值得一提的内容: 1. 二级菜单栏 .iframe内嵌窗口(样式设计.用hover做效果) 2. 高德地图API (自定义底图样式.弹跳点.信息窗体.线路导航) 3. DO ...
- ngRx 官方示例分析 - 1. 介绍
ngRx 的官方示例演示了在具体的场景中,如何使用 ngRx 管理应用的状态. 示例介绍 示例允许用户通过查询 google 的 book API 来查询图书,并保存自己的精选书籍列表. 菜单有两 ...
- 小白的Python之路 day5 logging模块
logging模块的特点及用法 一.概述 很多程序都有记录日志的需求,并且日志中包含的信息即有正常的程序访问日志,还可能有错误.警告等信息输出,python的logging模块提供了标准的日志接口,你 ...
- J.U.C atomic AtomicInteger解析
很多情况下我们只是需要简单的,高效,线程安全的递增递减方法.注意,这里有三个条件:简单,意味着程序员尽可能少的底层或者实现起来比较简单:高效,意味着耗用资源要少,程序处理速度要快: 线程安全也非常重要 ...