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);
    }
    }
  1. CasAuthenticationProvider的必要属性含义
  • authenticationUserDetailsService-权限获取对象
  • ticketValidator-ticket校验器,其中需要设置cas服务端的校验地址前缀casServerUrlPrefix
  • key-设置唯一标识
  1. CasAuthenticationProvider校验过程中如果ticket为空或者ticket校验失败都会由AbstractAuthenticationProcessingFilter类抓取并将错误信息写入到页面中,从而关于ticket的异常信息都会显示至前端页面
  2. CasAuthenticationProvider校验成功后会生成CasAuthenticationToken,且设置authenticatedtrue并保存至spring的安全上下文中,这在FilterSecurityInterceptorFilter类会有所作用

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);
}

小结

  1. CasAuthenticationFilter的放行策略:非登录请求;非代理接收请求;非ticket请求

  2. 对登录请求的成功处理是直接跳转至指定的页面,可通过SimpleUrlAuthenticationSuccessHandler#setDefaultTargetUrl(String url)设置;

    对非登录请求比如token请求的操作将保存校验通过的Authentication对象至SecurityContextHolder.getContext()上下文中再放行

  3. CasAuthenticationProvider校验过程中如果ticket为空或者ticket校验失败都会由AbstractAuthenticationProcessingFilter类抓取并将错误信息写入到页面中,从而关于ticket的异常信息都会显示至前端页面

    温馨提示:cas服务端登录成功后的service路径不要为login请求,避免token没拿到就被拦截从而输出错误页面

  4. 其中对ticket进行校验的是CasAuthenticationProvider对象,包括ticket校验以及权限获取

Springboot security cas源码陶冶-CasAuthenticationFilter的更多相关文章

  1. Springboot security cas源码陶冶-ExceptionTranslationFilter

    拦截关键的两个异常,对异常进行处理.主要应用异常则跳转至cas服务端登录页面 ExceptionTranslationFilter#doFilter-逻辑入口 具体操作逻辑如下 public void ...

  2. Springboot security cas源码陶冶-FilterSecurityInterceptor

    前言:用户登录信息校验成功后,都会获得当前用户所拥有的全部权限,所以对访问的路径当前用户有无权限则需要拦截验证一发 Spring security过滤器的执行顺序 首先我们需要验证为啥FilterSe ...

  3. Springboot security cas整合方案-原理篇

    前言:网络中关于Spring security整合cas的方案有很多例,对于Springboot security整合cas方案则比较少,且有些仿制下来运行也有些错误,所以博主在此篇详细的分析cas原 ...

  4. Springboot security cas整合方案-实践篇

    承接前文Springboot security cas整合方案-原理篇,请在理解原理的情况下再查看实践篇 maven环境 <dependency> <groupId>org.s ...

  5. Spring-shiro源码陶冶-DelegatingFilterProxy和ShiroFilterFactoryBean

    阅读源码有助于陶冶情操,本文旨在简单的分析shiro在Spring中的使用 简单介绍 Shiro是一个强大易用的Java安全框架,提供了认证.授权.加密和会话管理等功能 web.xml配置Shiro环 ...

  6. 修改CAS源码是的基于DB的认证方式配置更灵活

    最近在做CAS配置的时候,遇到了数据源不提供密码等数据的情况下,怎样实现密码输入认证呢? 第一步:新建Java项目,根据假面算法生成CAS加密工具 出于保密需要不提供自定义的加密工具,在您的实际项目中 ...

  7. Spring-shiro源码陶冶-DefaultFilter

    阅读源码有助于陶冶情操,本文旨在简单的分析shiro在Spring中的使用 简单介绍 Shiro是一个强大易用的Java安全框架,提供了认证.授权.加密和会话管理等功能 Apache Shiro自带的 ...

  8. SpringBoot自动配置源码调试

    之前对SpringBoot的自动配置原理进行了较为详细的介绍(https://www.cnblogs.com/stm32stm32/p/10560933.html),接下来就对自动配置进行源码调试,探 ...

  9. 调试CAS源码步骤

    1.先安装gradle2.eclipse安装gradle(sts)插件3.克隆cas源码 这一块需要很长时间4.gradle build 会遇到安装node.js 的模块 不存在的问题. 按提示解决就 ...

随机推荐

  1. DFS+打表

    N皇后问题 Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u Submit Status ...

  2. 整数n的全排列

    第一道用搜索码的.得纪念一下 #include <iostream> #include <cstdio> #include <cstring> #include & ...

  3. 基于.netcore 开发的轻量Rpc框架

    Rpc原理详解 博客上已经有人解释的很详细了,我就不在解释了.传送门 项目简介 项目是依赖于.net core2.0版本,内部都是依靠IOC来实现的,方便做自定义扩展.底层的通信是采用socket,s ...

  4. javascript 思维导图 总结

    项目接近尾声,闲暇时间对JavaScript的总结,包含数组的一些知识(创建.访问.关联数组,数组API,以及二维数组).js的内置对象.面向对象概念和特征.以及部分ES5特性. 大纲如图: 如需可下 ...

  5. OBS studio最新版配置鉴权推流

    这两天在看百度的LSS音视频直播服务的sdk..sdk看了一圈,基本上只能操作个流什么的,查看流列表,域名之类的.按照百度这块的描述自己去实现这个显得不是那么明智我感觉.其次就是百度LSS的教程用的O ...

  6. JDK、JRE、JVM详解

    JDK.JRE.JVM JDK包含JRE,而JRE包含JVM JDK(Java Development Kit)是针对Java开发员的产品,是整个Java的核心,包括了Java运行环境JRE.Java ...

  7. href

    <a href="#"></a>点击浏览器会跳转地址栏加了#:<a href=""></a>点击会打开当前网页所 ...

  8. 真正从零开始,TensorFlow详细安装入门图文教程!

    本文转载地址:https://www.leiphone.com/news/201606/ORlQ7uK3TIW8xVGF.html AI这个概念好像突然就火起来了,年初大比分战胜李世石的AlphaGo ...

  9. Java数据持久层框架 MyBatis之API学习二(入门)

    对于MyBatis的学习而言,最好去MyBatis的官方文档:http://www.mybatis.org/mybatis-3/zh/index.html 对于语言的学习而言,马上上手去编程,多多练习 ...

  10. 详解python中的__init__与__new__方法

    一.__init__和__new__方法执行的顺序? 在面向对象中介绍了关于对象创建的过程,我们知道__new__方法先于__init__方法执行. 二.__new__方法是什么? 首先,我们先来看下 ...