单点

单点登陆


单点登陆相关知识
理解Cookie和Session机制
单点登陆时序图
在这里插入图片描述

cas-clicet-core-3.1.10.jar(cas客户端)源码分析
项目背景:

  • 基于springboot集成cas客户端
  • 没有使用springboot自带集成方式,使用传统原始的集成方式
  • 由于cas服务端版本比较低,客户端使用的版本比较低

一、先了解如何集成,如何使用
1、cas配置参数实体@Configuration

@Getter
@Setter
public  class CasConfiguration {
    //cas登录路径
    @Value("${cas.casServerUrlPrefix}${cas.casServerLoginUrl}")
    private String casServerLoginUrl;


    @Value("${cas.casServerUrlPrefix}${cas.casServerLogoutUrl}")
    private String casServerLogoutUrl;

    //cas客户端服务器
    @Value("${cas.clientServerName}${cas.clientService}")
    private String clientService;

    //登录成功地址
    @Value("${cas.clientServerName}${cas.clientService}${cas.clientLoginSuccessUrl}")
    private String clientLoginSuccessUrl;

    //白名单
    @Value("${cas.whiteList}")
    private String whiteList;

    //cas服务器
    @Value("${cas.casServerUrlPrefix}")
    private String casServerUrlPrefix;

    //cas客户端服务器根目录
    @Value("${cas.clientServerName}")
    private String clientServerName;

}

2、动态参数配置在application.yml中

cas:
  #cas服务器地址
  casServerUrlPrefix: http://111.198.186.80:18080/cas
  #本地地址
  clientServerName: http://127.0.0.1:9090
    #登录路径
  casServerLoginUrl: /login
  #注销路径
  casServerLogoutUrl: /logout
  #项目名称,上下文
  clientService: /project-test
  #成功后返回路径
  clientLoginSuccessUrl: /vue/index.html
  #白名单
  whiteList: /login/**.**/css/**

3、配置多个过滤器

/*
  * @Description:cas客户端过滤器配置
  * @Author: wangwei
  * @Date:2020/3/27 10:46
  */
@Configuration
public class CasFilter {
    @Autowired
    CasConfiguration casConfiguration;


    /*
      * @Description:退出登录过滤器,需要放在最前面
      * @Param:[]
      * @Return: org.springframework.boot.web.servlet.FilterRegistrationBean
      * @Throws:
      * @Author: wangwei
      * @Date:2020/3/31 15:44
      */
    @Bean
    public FilterRegistrationBean CasSingleSignOutFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        //配置拦截器参数map
        Map<String, String> map = new HashMap<>(16);
        SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
        filterRegistrationBean.setFilter(singleSignOutFilter);
        map.put("casServerUrlPrefix", casConfiguration.getCasServerUrlPrefix());
        filterRegistrationBean.setInitParameters(map);
        String url = "/*";
        filterRegistrationBean.addUrlPatterns(url);
        filterRegistrationBean.setName("CasSingleSignOutFilter");
        filterRegistrationBean.setOrder(1);
        return filterRegistrationBean;
    }


    //配置 SingleSignOutHttpSessionListener
    @Bean
    public ServletListenerRegistrationBean<org.jasig.cas.client.session.SingleSignOutHttpSessionListener> casListener() {
        return new ServletListenerRegistrationBean<>(
                new org.jasig.cas.client.session.SingleSignOutHttpSessionListener());
    }
    /*
      * @Description:CAS认证filter casServerLoginUrl参数:表示CAS Server登录URL,后面追加appResId参数,表明应用类型(公文系统暂时使用GONGWEN,备案系统使用BHXT)。
		             service参数:表示在通过CAS Server认证后的返回页面。 localLoginUrl参数:本地登录URL。 renew参数:请不要修改。
		             whiteList参数:不进行认证检查的URI,使用分号进行分割。如果以/为结尾,则表示该路径下的所有URI均不进行认证检查。
      * @Param:[]
      * @Return: org.springframework.boot.web.servlet.FilterRegistrationBean
      * @Throws:
      * @Author: wangwei
      * @Date:2020/3/27 11:10
      */
    @Bean
    public FilterRegistrationBean CasAuthenticationFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        //配置拦截器参数map
        Map<String, String> map = new HashMap<>(16);
        WhiteListJwtAndAuthenticationFilter casAuthenticationFilter = new WhiteListJwtAndAuthenticationFilter();
        filterRegistrationBean.setFilter(casAuthenticationFilter);
        map.put("casServerLoginUrl", casConfiguration.getCasServerLoginUrl());
        map.put("service", casConfiguration.getClientLoginSuccessUrl());
        map.put("localLoginUrl", casConfiguration.getClientLoginSuccessUrl());
        map.put("renew", "false");
        map.put("whiteList", casConfiguration.getWhiteList());
        filterRegistrationBean.setInitParameters(map);
        String url = "/*";
        filterRegistrationBean.addUrlPatterns(url);
        filterRegistrationBean.setName("casAuthenticationFilter");
        filterRegistrationBean.setOrder(2);
        return filterRegistrationBean;
    }


    /*
      * @Description:CAS验证filter serverName参数:应用根路径。 CAS Http请求Wrapper filter:在通过CAS认证或验证通过后,将user id赋值到request中remoteUser中
      * @Param:[]
      * @Return: org.springframework.boot.web.servlet.FilterRegistrationBean
      * @Throws:
      * @Author: wangwei
      * @Date:2020/3/27 11:10
      */
    @Bean
    public FilterRegistrationBean CasValidationFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        //配置拦截器参数map
        Map<String, String> map = new HashMap<>(16);
        CustomCas30ProxyReceivingTicketValidationFilter casValidationFilter = new CustomCas30ProxyReceivingTicketValidationFilter();
        filterRegistrationBean.setFilter(casValidationFilter);
        map.put("casServerUrlPrefix", casConfiguration.getCasServerUrlPrefix());
        map.put("serverName", casConfiguration.getClientServerName());
        filterRegistrationBean.setInitParameters(map);
        String url = "/*";
        filterRegistrationBean.addUrlPatterns(url);
        filterRegistrationBean.setName("casValidationFilter");
        filterRegistrationBean.setOrder(3);
        return filterRegistrationBean;
    }


    @Bean
    public FilterRegistrationBean CasHttpServletRequestFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        //配置拦截器参数map
        HttpServletRequestWrapperFilter casHttpServletRequestFilter = new HttpServletRequestWrapperFilter();
        filterRegistrationBean.setFilter(casHttpServletRequestFilter);
        String url = "/*";
        filterRegistrationBean.addUrlPatterns(url);
        filterRegistrationBean.setName("casHttpServletRequestFilter");
        filterRegistrationBean.setOrder(4);
        return filterRegistrationBean;
    }

}

4、CustomCas30ProxyReceivingTicketValidationFilter过滤器

public class CustomCas30ProxyReceivingTicketValidationFilter extends Cas10TicketValidationFilter {

    /*
     * @Description:校验成功后设置sesion
     * @Param:[request, response, assertion]
     * @Return: void
     * @Throws:
     * @Author: wangwei
     * @Date:2020/3/29 10:40
     */
    @Override
    protected void onSuccessfulValidation(HttpServletRequest request, HttpServletResponse response, Assertion assertion) {
        String dcpLoginInfo = (String) assertion.getPrincipal().getName();
        javax.servlet.http.HttpSession session=request.getSession(false);
        if(session!=null){
            session.setAttribute("systemUser",dcpLoginInfo);
        }
    }


}

5、WhiteListJwtAndAuthenticationFilter 复写认证过滤器,添加了白名单,跳过cas认证的条件

public class WhiteListJwtAndAuthenticationFilter extends AbstractCasFilter {

    private String casServerLoginUrl;
    private boolean renew = false;
    private boolean gateway = false;
    private List<String> whiteList;
    private GatewayResolver gatewayStorage = new DefaultGatewayResolverImpl();

    public WhiteListJwtAndAuthenticationFilter() {
    }

    @Override
    protected void initInternal(FilterConfig filterConfig) throws ServletException {
        if (!this.isIgnoreInitConfiguration()) {
            super.initInternal(filterConfig);
            this.setCasServerLoginUrl(this.getPropertyFromInitParams(filterConfig, "casServerLoginUrl", (String)null));
            this.log.trace("Loaded CasServerLoginUrl parameter: " + this.casServerLoginUrl);
            this.setRenew(this.parseBoolean(this.getPropertyFromInitParams(filterConfig, "renew", "false")));
            this.log.trace("Loaded renew parameter: " + this.renew);
            this.setGateway(this.parseBoolean(this.getPropertyFromInitParams(filterConfig, "gateway", "false")));
            this.log.trace("Loaded gateway parameter: " + this.gateway);
            this.setWhiteList(this.parseStringList(this.getPropertyFromInitParams(filterConfig, "whiteList", "")));
            String gatewayStorageClass = this.getPropertyFromInitParams(filterConfig, "gatewayStorageClass", (String)null);
            if (gatewayStorageClass != null) {
                try {
                    this.gatewayStorage = (GatewayResolver)Class.forName(gatewayStorageClass).newInstance();
                } catch (Exception var4) {
                    this.log.error(var4, var4);
                    throw new ServletException(var4);
                }
            }
        }

    }

    private List<String> parseStringList(String whiteListStr) {
        String[] whiteListArray = whiteListStr.split(",");
        if (whiteListArray != null && whiteListArray.length > 0) {
            List<String> whiteList = new ArrayList(whiteListArray.length);
            String[] arr$ = whiteListArray;
            int len$ = whiteListArray.length;

            for(int i$ = 0; i$ < len$; ++i$) {
                String s = arr$[i$];
                whiteList.add(s);
            }

            return whiteList;
        } else {
            return null;
        }
    }

    /*
      * @Description:白名单路径匹配
      * @Author: wangwei
      * @Date:2020/5/14 17:11
      */
    private boolean isSkipCheck(String uri, String contextPath) {
        PathMatcher pathMatcherToUse = new AntPathMatcher();
        if (!ObjectUtils.isEmpty(this.whiteList)) {
            for(int i = 0;  i< whiteList.size(); i++) {
                String pattern = whiteList.get(i);
                if (pathMatcherToUse.match(pattern, uri)) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public void init() {
        super.init();
        CommonUtils.assertNotNull(this.casServerLoginUrl, "casServerLoginUrl cannot be null.");
    }

    @Override
    public final void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        HttpServletResponse response = (HttpServletResponse)servletResponse;
        HttpSession session = request.getSession(false);
        Assertion assertion = session != null ? (Assertion)session.getAttribute("_const_cas_assertion_") : null;
        if (assertion != null) {
            filterChain.doFilter(request, response);
        } else {
            String contextPath = request.getContextPath();
            String uri = request.getRequestURI().substring(contextPath.length(),request.getRequestURI().length());
            //获取参数
            String headerFlag = request.getHeader("flagCas");
            String urlFlag = request.getParameter("flagCas");
            //判断是否为白名单
            if (this.isSkipCheck(uri, contextPath)) {
                filterChain.doFilter(request, response);
            //根据参数判断是否需要cas认证
            } else if("no".equals(headerFlag)||"no".equals(urlFlag)){
                filterChain.doFilter(request, response);
            }else {
                String serviceUrl = this.constructServiceUrl(request, response);
                String ticket = CommonUtils.safeGetParameter(request, this.getArtifactParameterName());
                boolean wasGatewayed = this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);
                if (!CommonUtils.isNotBlank(ticket) && !wasGatewayed) {
                    this.log.debug("no ticket and no assertion found");
                    String modifiedServiceUrl;
                    if (this.gateway) {
                        this.log.debug("setting gateway attribute in session");
                        modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request, serviceUrl);
                    } else {
                        modifiedServiceUrl = serviceUrl;
                    }

                    if (this.log.isDebugEnabled()) {
                        this.log.debug("Constructed service url: " + modifiedServiceUrl);
                    }

                    String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl, this.getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway);
                    if (this.log.isDebugEnabled()) {
                        this.log.debug("redirecting to \"" + urlToRedirectTo + "\"");
                    }

                    response.sendRedirect(urlToRedirectTo);
                } else {
                    filterChain.doFilter(request, response);
                }
            }
        }
    }

    public final void setRenew(boolean renew) {
        this.renew = renew;
    }

    public final void setGateway(boolean gateway) {
        this.gateway = gateway;
    }

    public final void setCasServerLoginUrl(String casServerLoginUrl) {
        this.casServerLoginUrl = casServerLoginUrl;
    }

    public final void setGatewayStorage(GatewayResolver gatewayStorage) {
        this.gatewayStorage = gatewayStorage;
    }

    public void setWhiteList(List<String> whiteList) {
        this.whiteList = whiteList;
    }
}

二、结合实现,以及流程图,分析客户端源码,请参考CasFilter文件

1、WhiteListJwtAndAuthenticationFilter过滤器(这是复写了jar包中AuthenticationFilter过滤器,主要的改造的是添加了白名单,添加了跳过cas单点认证方式)

 @Override
    public final void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        HttpServletResponse response = (HttpServletResponse)servletResponse;
        HttpSession session = request.getSession(false);
        //判断有没有session
        Assertion assertion = session != null ? (Assertion)session.getAttribute("_const_cas_assertion_") : null;
         //session有数据说明本地已经登录了,直接跳转路径
        if (assertion != null) {
            filterChain.doFilter(request, response);
        } else {
         /*
         否则用户没有登录
         1、判断是不是白名单路径,如果是白名单路径放行
         2、判断是否关闭单点登录验证,这样用户就可以使用自己的登录认证方式,这样就既可以使用单点登录,也可以使用本地登录
         3、如果上面两个条件都不满足
                3.1判断用户是户是否有token值,如果有放行(有token值说明用户已经经过cas服务认证登录了,并且返回了token值)
                3.2如果没有token值重定向cas认证登录
          */
            String contextPath = request.getContextPath();
            String uri = request.getRequestURI().substring(contextPath.length(),request.getRequestURI().length());
            //获取参数
            String headerFlag = request.getHeader("flagCas");
            String urlFlag = request.getParameter("flagCas");
            //判断是否为白名单
            if (this.isSkipCheck(uri, contextPath)) {
                filterChain.doFilter(request, response);
            //根据参数判断是否需要cas认证
            } else if("no".equals(headerFlag)||"no".equals(urlFlag)){
                filterChain.doFilter(request, response);
            }else {
                String serviceUrl = this.constructServiceUrl(request, response);
                String ticket = CommonUtils.safeGetParameter(request, this.getArtifactParameterName());
                boolean wasGatewayed = this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);
                if (!CommonUtils.isNotBlank(ticket) && !wasGatewayed) {
                    this.log.debug("no ticket and no assertion found");
                    String modifiedServiceUrl;
                    if (this.gateway) {
                        this.log.debug("setting gateway attribute in session");
                        modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request, serviceUrl);
                    } else {
                        modifiedServiceUrl = serviceUrl;
                    }

                    if (this.log.isDebugEnabled()) {
                        this.log.debug("Constructed service url: " + modifiedServiceUrl);
                    }

                    String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl, this.getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway);
                    if (this.log.isDebugEnabled()) {
                        this.log.debug("redirecting to \"" + urlToRedirectTo + "\"");
                    }

                    response.sendRedirect(urlToRedirectTo);
                } else {
                    filterChain.doFilter(request, response);
                }
            }
        }
    }

2、Cas10TicketValidationFilter过滤继承的AbstractTicketValidationFilter过滤器(登录成功了返回token,验证token是否有效)

 public final void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        if (this.preFilter(servletRequest, servletResponse, filterChain)) {
            HttpServletRequest request = (HttpServletRequest)servletRequest;
            HttpServletResponse response = (HttpServletResponse)servletResponse;
            String ticket = CommonUtils.safeGetParameter(request, this.getArtifactParameterName());
            /*
            重点:***********************************************************
            这个拦截器只拦截带有token值的路径,这样就实现了只在用户登录成功后返回token值验证token值有效后,不会每一个请求都验证token的有效性,这样安全性得到了保证,也不会频繁访问cas服务端验证有效性
            重点:***********************************************************
            */
            if (CommonUtils.isNotBlank(ticket)) {
                if (this.log.isDebugEnabled()) {
                    this.log.debug("Attempting to validate ticket: " + ticket);
                }

                try {
                    Assertion assertion = this.ticketValidator.validate(ticket, this.constructServiceUrl(request, response));
                    if (this.log.isDebugEnabled()) {
                        this.log.debug("Successfully authenticated user: " + assertion.getPrincipal().getName());
                    }

                    request.setAttribute("_const_cas_assertion_", assertion);
                    if (this.useSession) {
                        request.getSession().setAttribute("_const_cas_assertion_", assertion);
                    }

                    this.onSuccessfulValidation(request, response, assertion);
                } catch (TicketValidationException var8) {
                    response.setStatus(403);
                    this.log.warn(var8, var8);
                    this.onFailedValidation(request, response);
                    if (this.exceptionOnValidationFailure) {
                        throw new ServletException(var8);
                    }
                }

                if (this.redirectAfterValidation) {
                    this.log.debug("Redirecting after successful ticket validation.");
                    response.sendRedirect(response.encodeRedirectURL(this.constructServiceUrl(request, response)));
                    return;
                }
            }

            filterChain.doFilter(request, response);
        }
    }

3、SingleSignOutFilter,单点登录注销过滤器

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        String artifact;
        if ("POST".equals(request.getMethod())) {
            artifact = CommonUtils.safeGetParameter(request, "logoutRequest");
            if (CommonUtils.isNotBlank(artifact)) {
                if (log.isTraceEnabled()) {
                    log.trace("Logout request=[" + artifact + "]");
                }

                String sessionIdentifier = XmlUtils.getTextForElement(artifact, "SessionIndex");
                if (CommonUtils.isNotBlank(sessionIdentifier)) {
                    HttpSession session = SESSION_MAPPING_STORAGE.removeSessionByMappingId(sessionIdentifier);
                    if (session != null) {
                        String sessionID = session.getId();
                        if (log.isDebugEnabled()) {
                            log.debug("Invalidating session [" + sessionID + "] for ST [" + sessionIdentifier + "]");
                        }

                        try {
                            session.invalidate();
                        } catch (IllegalStateException var10) {
                            log.debug(var10, var10);
                        }
                    }

                    return;
                }
            }
        } else {
            artifact = CommonUtils.safeGetParameter(request, this.artifactParameterName);
            if (CommonUtils.isNotBlank(artifact)) {
                HttpSession session = request.getSession(true);
                if (log.isDebugEnabled()) {
                    log.debug("Storing session identifier for " + session.getId());
                }

                try {
                    SESSION_MAPPING_STORAGE.removeBySessionById(session.getId());
                } catch (Exception var11) {
                }

                SESSION_MAPPING_STORAGE.addSessionById(artifact, session);
            } else {
                log.debug("No Artifact Provided; no action taking place.");
            }
        }

        filterChain.doFilter(servletRequest, servletResponse);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值