CAS单点登陆源码解析之【客户端】

cas 3.5.3服务器搭建+spring boot集成+shiro模拟登陆(不修改现有shiro认证架构)。由于咱们属于供应商,因此有些客户那里会须要接对方的CAS,因此没有使用shiro和cas的直接集成模式,若是是这种状况,能够参考:https://blog.csdn.net/catoop/article/details/50534006。html

Cas Client主要有四个核心过滤器:java

l  AuthenticationFilterspring

l  TicketValidationFilterapi

l  HttpServletRequestWrapperFilter服务器

l  AssertionThreadLocalFiltersession

他们的做用解释以下:架构

AuthenticationFilter

AuthenticationFilter用来拦截全部的请求,用以判断用户是否须要经过Cas Server进行认证,若是须要则将跳转到Cas Server的登陆页面。若是不须要进行登陆认证,则请求会继续往下执行。oracle

     AuthenticationFilter有两个用户必须指定的参数,一个是用来指定Cas Server登陆地址的casServerLoginUrl,另外一个是用来指定认证成功后须要跳转地址的serverNameservice。service和serverName只须要指定一个就能够了。当二者都指定了,参数service将具备更高的优先级,即将以service指定的参数值为准。service和serverName的区别在于service指定的是一个肯定的URL,认证成功后就会确切的跳转到service指定的URL;而serverName则是用来指定主机名,其格式为{protocol}:{hostName}:{port},如:https://localhost:8443,当指定的是serverName时,AuthenticationFilter将会把它附加上当前请求的URI,以及对应的查询参数来构造一个肯定的URL,如指定serverName为“http://localhost”,而当前请求的URI为“/app”,查询参数为“a=b&b=c”,则对应认证成功后的跳转地址将为“http://localhost/app?a=b&b=c”。app

除了上述必须指定的参数外,AuthenticationFilter还能够指定以下可选参数:oop

l  renew:当指定renew为true时,在请Cas Server时将带上参数“renew=true”,默认为false。

l  gateway:指定gateway为true时,在请求Cas Server时将带上参数“gateway=true”,默认为false。

l  artifactParameterName:指定ticket对应的请求参数名称,默认为ticket。

l  serviceParameterName:指定service对应的请求参数名称,默认为service。

 

TicketValidationFilter

在请求经过AuthenticationFilter的认证以后,若是请求中携带了参数ticket则将会由TicketValidationFilter来对携带的ticket进行校验。TicketValidationFilter只是对验证ticket的这一类Filter的统称,其并不对应Cas Client中的一个具体类型。Cas Client中有多种验证ticket的Filter,都继承自AbstractTicketValidationFilter,它们的验证逻辑都是一致的,都有AbstractTicketValidationFilter实现,所不一样的是使用的TicketValidator不同。默认是Cas10TicketValidationFilter。

可选参数包括:

l  redirectAfterValidation :表示是否验证经过后从新跳转到该URL,可是不带参数ticket,默认为true。

l  useSession :在验证ticket成功后会生成一个Assertion对象,若是useSession为true,则会将该对象存放到Session中。若是为false,则要求每次请求都须要携带ticket进行验证,显然useSession为false跟redirectAfterValidation为true是冲突的。默认为true。

l  exceptionOnValidationFailure :表示ticket验证失败后是否须要抛出异常,默认为true。

l  renew:当值为true时将发送“renew=true”到Cas Server,默认为false。

 

HttpServletRequestWrapperFilter

HttpServletRequestWrapperFilter用于将每个请求对应的HttpServletRequest封装为其内部定义的CasHttpServletRequestWrapper,该封装类将利用以前保存在Session或request中的Assertion对象重写HttpServletRequest的getUserPrincipal()、getRemoteUser()和isUserInRole()方法。这样在咱们的应用中就能够很是方便的从HttpServletRequest中获取到用户的相关信息。

 

AssertionThreadLocalFilter

AssertionThreadLocalFilter是为了方便用户在应用的其它地方获取Assertion对象,其会将当前的Assertion对象存放到当前的线程变量中,那么之后用户在程序的任何地方均可以从线程变量中获取当前Assertion,无需再从Session或request中进行解析。该线程变量是由AssertionHolder持有的,咱们在获取当前的Assertion时也只须要经过AssertionHolder的getAssertion()方法获取便可,如:

   Assertion assertion = AssertionHolder.getAssertion();

 

cas client/shiro过滤器顺序,集成cas的状况下,cas客户端老是第一个过滤器。
org.jasig.cas.client.validation.AbstractTicketValidationFilter#doFilter
    org.jasig.cas.client.util.CommonUtils#safeGetParameter
org.jasig.cas.client.authentication.AuthenticationFilter#doFilter --在这里修改源码,拿到assertion、且为空后须要判断token是否存在且有效,若是存在且有效,说明已经登陆过,则直接返回
    org.jasig.cas.client.util.CommonUtils#constructRedirectUrl 
    javax.servlet.http.HttpServletResponse#sendRedirect --此时请求去了CAS

最后进入shiro过滤器UserFilter.isAccessAllowed

 

由于在userfilter里面标准的cas登陆后,是能够经过UserPrincipal拿到当前的用户信息的,可是当咱们是集群模式的时候由于直接在AuthenticationFilter#doFilter中拦截返回了,且没有明确设置,天然就拿不到了,servletrequest中没有提供设置UserPrincipal的入口,cas的org.jasig.cas.client.validation.Cas20ServiceTicketValidator#parseResponseFromServer是建立了,可是org.jasig.cas.client.validation.Cas20ServiceTicketValidator#customParseResponse的实现体是空的。以下:

protected final Assertion parseResponseFromServer(final String response) throws TicketValidationException {
        final String error = XmlUtils.getTextForElement(response,
                "authenticationFailure");

        if (CommonUtils.isNotBlank(error)) {
            throw new TicketValidationException(error);
        }

        final String principal = XmlUtils.getTextForElement(response, "user");
        final String proxyGrantingTicketIou = XmlUtils.getTextForElement(response, "proxyGrantingTicket");
        final String proxyGrantingTicket = this.proxyGrantingTicketStorage != null ? this.proxyGrantingTicketStorage.retrieve(proxyGrantingTicketIou) : null;

        if (CommonUtils.isEmpty(principal)) {
            throw new TicketValidationException("No principal was found in the response from the CAS server.");
        }

        final Assertion assertion;
        final Map<String,Object> attributes = extractCustomAttributes(response);
        if (CommonUtils.isNotBlank(proxyGrantingTicket)) {
            final AttributePrincipal attributePrincipal = new AttributePrincipalImpl(principal, attributes, proxyGrantingTicket, this.proxyRetriever);
            assertion = new AssertionImpl(attributePrincipal);
        } else {
            assertion = new AssertionImpl(new AttributePrincipalImpl(principal, attributes));
        }

        customParseResponse(response, assertion);

        return assertion;
    }

stackoverflow也没搜到。以下:

https://stackoverflow.com/questions/25885747/when-and-where-java-security-set-userprincipal

https://docs.oracle.com/javase/7/docs/jre/api/security/jaas/spec/com/sun/security/auth/UserPrincipal.html

此时若是设置UserPrincipal须要的话,须要本身经过HttpServletRequestWrapperFilter重写一遍,以下:

    public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
        final AttributePrincipal principal = retrievePrincipalFromSessionOrRequest(servletRequest);

        filterChain.doFilter(new CasHttpServletRequestWrapper((HttpServletRequest) servletRequest, principal), servletResponse);
    }

    protected AttributePrincipal retrievePrincipalFromSessionOrRequest(final ServletRequest servletRequest) {
        final HttpServletRequest request = (HttpServletRequest) servletRequest;
        final HttpSession session = request.getSession(false);
        final Assertion assertion = (Assertion) (session == null ? request.getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION) : session.getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION));

        return assertion == null ? null : assertion.getPrincipal();
    }

这样的话,shiro UserFilter里面就能够经过

AttributePrincipal principal = (AttributePrincipal)((HttpServletRequest) request).getUserPrincipal();

拿到登陆用户的信息了。

相关文章
相关标签/搜索