[心缘地方]同学录
首页 | 功能说明 | 站长通知 | 最近更新 | 编码查看转换 | 代码下载 | 常见问题及讨论 | 《深入解析ASP核心技术》 | 王小鸭自动发工资条VBA版
登录系统:用户名: 密码: 如果要讨论问题,请先注册。

[整理]CAS单点登录,服务端和客户端验证流程,抓包分析,带代码简单分析

上一篇:[备忘]linux下jmx连不上,Connection refused to host 0.0.0.0
下一篇:[备忘]CAS的Session两秒挂掉?--解决退出后登录,页面刷新的问题

添加日期:2013/10/30 11:53:47 快速返回   返回列表 阅读49944次
首先,cas 服务端的执行流程是WEB-INF/login-webflow.xml定义的,可以大概看看。
我用的代码是cas-server-3.5.2-release.zip和cas-client-3.2.1-release.zip。

(1)直接访问server端login页面
请求:


GET /cas/login HTTP/1.1
...省略..



响应:


HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Pragma: no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control: no-cache
Cache-Control: no-store
Set-Cookie: JSESSIONID=2DA712066025406394C7C644AF16FD18; Path=/cas
Content-Type: text/html;charset=UTF-8
Content-Length: 1993
Date: Wed, 30 Oct 2013 01:53:49 GMT

内容就是登陆表单,省略



对于Server端流程来说,走的是


<decision-state id="ticketGrantingTicketExistsCheck">
    <if test="flowScope.ticketGrantingTicketId != null" then="hasServiceCheck" else="gatewayRequestCheck" />
</decision-state>


TGT不存在,走gatewayRequestCheck


<decision-state id="gatewayRequestCheck">
    <if test="requestParameters.gateway != '' and requestParameters.gateway != null and flowScope.service != null" then="gatewayServicesManagementCheck" else="serviceAuthorizationCheck" />
</decision-state>


走serviceAuthorizationCheck


<action-state id="serviceAuthorizationCheck">
<evaluate expression="serviceAuthorizationCheck"/>
<transition to="generateLoginTicket"/>
</action-state>


执行认证check,然后转到generateLoginTicket


<action-state id="generateLoginTicket">
<evaluate expression="generateLoginTicketAction.generate(flowRequestContext)" />
    <transition on="generated" to="viewLoginForm" />
</action-state>


生成登录小票,转到login表单


<view-state id="viewLoginForm" view="casLoginView" model="credentials">
<binder>
    <binding property="username" />
    <binding property="password" />
</binder>
<on-entry>
    <set name="viewScope.commandName" value="'credentials'" />
</on-entry>
    <transition on="submit" bind="true" validate="true" to="realSubmit">
    <evaluate expression="authenticationViaFormAction.doBind(flowRequestContext, flowScope.credentials)" />
</transition>
</view-state>


对应的jsp页面是WEB-INF\view\jsp\default\ui\casLoginView.jsp
--------------------------------------------------------------------------
(2)输入用户名、密码,登录
请求:


POST /cas/login;jsessionid=2DA712066025406394C7C644AF16FD18 HTTP/1.1
Content-Type: application/x-www-form-urlencoded
...部分省略...
Cookie: JSESSIONID=2DA712066025406394C7C644AF16FD18

username=admin&password=1&warn=true<=LT-3-Ahdqm03UKMR9vwuy7A5ZSCMa9zJkNi&execution=e1s1&_eventId=submit&x=53&y=55


其中的LT-3-Ahdqm03UKMR9vwuy7A5ZSCMa9zJkNi就是登录小票,
后台会比较“提交的值”和“后台保存的是否一致”。这个类似于验证码,没啥说的。


响应:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Pragma: no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control: no-cache
Cache-Control: no-store
Set-Cookie: CASPRIVACY=true; Path=/cas/; Secure
Set-Cookie: CASTGC=TGT-2-DmMvVs9Wy01ScbnMtgtLzgcyX5X14TGcM1f5iGuWIBrlDwTg4Q-cas01.example.org; Path=/cas/
Content-Type: text/html;charset=UTF-8
Content-Length: 1602
Date: Wed, 30 Oct 2013 01:54:03 GMT


后台设置了两个cookie,一个是CASPRIVACY=true,不知道干嘛用的
一个是CASTGC,这个就是TicketGrantingTicket Cookie,就是说,你想买通票,你还得先买门票进去的意思,tgc就是门票。
这样,客户端浏览器就拥有了TGC门票。下次再访问cas server时就会带着这个门票来了。

后台执行流程:
由上面viewLoginForm的定义,表单提交后,会执行authenticationViaFormAction.doBind方法,然后跳到realSubmit


<action-state id="realSubmit">
    <evaluate expression="authenticationViaFormAction.submit(flowRequestContext, flowScope.credentials, messageContext)" />
    <transition on="warn" to="warn" />
    <transition on="success" to="sendTicketGrantingTicket" />
    <transition on="error" to="generateLoginTicket" />
    <transition on="accountDisabled" to="casAccountDisabledView" />
    <transition on="mustChangePassword" to="casMustChangePassView" />
    <transition on="accountLocked" to="casAccountLockedView" />
    <transition on="badHours" to="casBadHoursView" />
    <transition on="badWorkstation" to="casBadWorkstationView" />
    <transition on="passwordExpired" to="casExpiredPassView" />
</action-state>


执行authenticationViaFormAction.submit方法,
看一下代码:


    public final String submit(final RequestContext context, final Credentials credentials, final MessageContext messageContext) throws Exception {
        // Validate login ticket
        final String authoritativeLoginTicket = WebUtils.getLoginTicketFromFlowScope(context);
        final String providedLoginTicket = WebUtils.getLoginTicketFromRequest(context);
        if (!authoritativeLoginTicket.equals(providedLoginTicket)) {
            this.logger.warn("Invalid login ticket " + providedLoginTicket);
            final String code = "INVALID_TICKET";
            messageContext.addMessage(
                new MessageBuilder().error().code(code).arg(providedLoginTicket).defaultText(code).build());
            return "error";
        }

        final String ticketGrantingTicketId = WebUtils.getTicketGrantingTicketId(context);
        final Service service = WebUtils.getService(context);
        if (StringUtils.hasText(context.getRequestParameters().get("renew")) && ticketGrantingTicketId != null && service != null) {

            try {
                final String serviceTicketId = this.centralAuthenticationService.grantServiceTicket(ticketGrantingTicketId, service, credentials);
                WebUtils.putServiceTicketInRequestScope(context, serviceTicketId);
                putWarnCookieIfRequestParameterPresent(context);
                return "warn";
            } catch (final TicketException e) {
                if (isCauseAuthenticationException(e)) {
                    populateErrorsInstance(e, messageContext);
                    return getAuthenticationExceptionEventId(e);
                }
                
                this.centralAuthenticationService.destroyTicketGrantingTicket(ticketGrantingTicketId);
                if (logger.isDebugEnabled()) {
                    logger.debug("Attempted to generate a ServiceTicket using renew=true with different credentials", e);
                }
            }
        }

        try {
            WebUtils.putTicketGrantingTicketInRequestScope(context, this.centralAuthenticationService.createTicketGrantingTicket(credentials));
            putWarnCookieIfRequestParameterPresent(context);
            return "success";
        } catch (final TicketException e) {
            populateErrorsInstance(e, messageContext);
            if (isCauseAuthenticationException(e))
                return getAuthenticationExceptionEventId(e);
            return "error";
        }
    }


service就是指子系统的URL,由于直接访问的server端login,所以URL中没有service参数,所以grantServiceTicket不会执行。
最后,会createTicketGrantingTicket,然后放到request中。
返回success,跳转到sendTicketGrantingTicket


<action-state id="sendTicketGrantingTicket">
<evaluate expression="sendTicketGrantingTicketAction" />
    <transition to="serviceCheck" />
</action-state>


这里是把TicketGrantingTicket写到Cookie。
然后跳到serviceCheck


<!--
    <decision-state id="serviceCheck">
        <if test="flowScope.service != null" then="generateServiceTicket" else="getSubSystemUrl" />
    </decision-state> -->
    <decision-state id="serviceCheck">
        <if test="flowScope.service != null" then="getSubSystemUrl" else="getSubSystemUrl" />
    </decision-state>
    
    <action-state id="generateServiceTicket">
        <evaluate expression="generateServiceTicketAction" />
        <transition on="success" to ="warn" />
        <transition on="error" to="generateLoginTicket" />
        <transition on="gateway" to="gatewayServicesManagementCheck" />
    </action-state>
    
    <action-state id="getSubSystemUrl">
        <evaluate expression="getSubSystemUrlAction.get(flowRequestContext, flowScope.credentials)" />
        <transition to ="viewGenericLoginSuccess" />
    </action-state>


这段被我改过,原来是:有service到generateServiceTicket,没有直接到viewGenericLoginSuccess
我改成了,都跳到getSubSystemUrl。这个action是我自己加的,目的是去数据库取得子系统URL,并取得用户对每个子系统的权限。
然后跳转到viewGenericLoginSuccess


<end-state id="viewGenericLoginSuccess" view="casLoginGenericSuccessView" />


viewGenericLoginSuccess,也就是登录成功页面,就是服务端WEB-INF\view\jsp\default\ui\casGenericSuccess.jsp这个页面。
我在这里显示了几个子系统的链接。

这里的view写的都是id,实际对应的文件在WEB-INF\classes\default_views.properties中定义。

--------------------------------------------------------------------------
(3)然后点击页面上的链接,访问子系统
请求:


GET /UserManage/ HTTP/1.1



响应:


HTTP/1.1 302 Moved Temporarily
Server: Apache-Coyote/1.1
Location: http://10.0.103.137:8080/cas/login?service=http%3A%2F%2F10.0.103.137%3A8080%2FUserManage%2F
Content-Length: 0
Date: Wed, 30 Oct 2013 01:54:22 GMT



由于子系统增加了CAS client的Filter。


<filter>
    <filter-name>CAS Authentication Filter</filter-name>
    <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
    <!-- CAS login 服务地址-->
    <init-param>
        <param-name>casServerLoginUrl</param-name>
        <param-value>http://10.0.103.137:8080/cas/login</param-value>
    </init-param>
    <!-- 客户端应用服务地址-->
    <init-param>
        <param-name>serverName</param-name>
        <param-value>http://10.0.103.137:8080</param-value>
    </init-param>
    <init-param>
        <param-name>renew</param-name>
        <param-value>false</param-value>
    </init-param>
    <init-param>
        <param-name>gateway</param-name>
        <param-value>false</param-value>
    </init-param>
</filter>



看一下AuthenticationFilter的代码:


   public final void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
        final HttpServletRequest request = (HttpServletRequest) servletRequest;
        final HttpServletResponse response = (HttpServletResponse) servletResponse;
        final HttpSession session = request.getSession(false);
        final Assertion assertion = session != null ? (Assertion) session.getAttribute(CONST_CAS_ASSERTION) : null;

        if (assertion != null) {
            filterChain.doFilter(request, response);
            return;
        }

        final String serviceUrl = constructServiceUrl(request, response);
        final String ticket = CommonUtils.safeGetParameter(request,getArtifactParameterName());
        final boolean wasGatewayed = this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);

        if (CommonUtils.isNotBlank(ticket) || wasGatewayed) {
            filterChain.doFilter(request, response);
            return;
        }

        final String modifiedServiceUrl;

        log.debug("no ticket and no assertion found");
        if (this.gateway) {
            log.debug("setting gateway attribute in session");
            modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request, serviceUrl);
        } else {
            modifiedServiceUrl = serviceUrl;
        }

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

        final String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl, getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway);

        if (log.isDebugEnabled()) {
            log.debug("redirecting to \"" + urlToRedirectTo + "\"");
        }

        response.sendRedirect(urlToRedirectTo);
    }


从Session取assertion,就是登录信息,如果有,就继续执行其他Filter。
然后看有没有ticket,也就是通票。有的话,继续执行其他Filter。
else就拼接URL,跳转。
http://10.0.103.137:8080/cas/login?service=http%3A%2F%2F10.0.103.137%3A8080%2FUserManage%2F

(4)跳转到server端了
请求:


GET /cas/login?service=http%3A%2F%2F10.0.103.137%3A8080%2FUserManage%2F HTTP/1.1
Cookie: CASTGC=TGT-2-DmMvVs9Wy01ScbnMtgtLzgcyX5X14TGcM1f5iGuWIBrlDwTg4Q-cas01.example.org; JSESSIONID=2DA712066025406394C7C644AF16FD18


可以看到,浏览器自动把CASTGC带过来了。

响应:


HTTP/1.1 302 Moved Temporarily
Server: Apache-Coyote/1.1
Pragma: no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control: no-cache
Cache-Control: no-store
Location: http://10.0.103.137:8080/UserManage/?ticket=ST-1-0dnlMstfctMYVcat5wfy-cas01.example.org
Content-Length: 0
Date: Wed, 30 Oct 2013 01:54:22 GMT


又跳回了子系统,但是带着ticket过来的

后台流程:


<decision-state id="ticketGrantingTicketExistsCheck">
    <if test="flowScope.ticketGrantingTicketId != null" then="hasServiceCheck" else="gatewayRequestCheck" />
</decision-state>


CASTGC有了,所以走hasServiceCheck


<decision-state id="hasServiceCheck">
    <if test="flowScope.service != null" then="renewRequestCheck" else="getSubSystemUrl" />
</decision-state>


service参数也有了,所以走renewRequestCheck


<decision-state id="renewRequestCheck">
    <if test="requestParameters.renew != '' and requestParameters.renew != null" then="serviceAuthorizationCheck" else="generateServiceTicket" />
</decision-state>


renew客户端配置的是false,所以请求里无此参数。走generateServiceTicket


<action-state id="generateServiceTicket">
    <evaluate expression="generateServiceTicketAction" />
    <transition on="success" to ="warn" />
    <transition on="error" to="generateLoginTicket" />
    <transition on="gateway" to="gatewayServicesManagementCheck" />
</action-state>



看下代码:


protected Event doExecute(final RequestContext context) {
        final Service service = WebUtils.getService(context);
        final String ticketGrantingTicket = WebUtils.getTicketGrantingTicketId(context);

        try {
            final String serviceTicketId = this.centralAuthenticationService
                .grantServiceTicket(ticketGrantingTicket,
                    service);
            WebUtils.putServiceTicketInRequestScope(context,
                serviceTicketId);
            return success();
        } catch (final TicketException e) {
            if (isGatewayPresent(context)) {
                return result("gateway");
            }
        }

        return error();
    }


根据ticketGrantingTicket和service,生成通票,并放到request中。成功跳到warn


<decision-state id="warn">
    <if test="flowScope.warnCookieValue" then="showWarningView" else="redirect" />
</decision-state>


跳转时,不显示提醒页面,所以直接走redirect.


<action-state id="redirect">
    <evaluate expression="flowScope.service.getResponse(requestScope.serviceTicketId)" result-type="org.jasig.cas.authentication.principal.Response" result="requestScope.response" />
    <transition to="postRedirectDecision" />
</action-state>

<decision-state id="postRedirectDecision">
    <if test="requestScope.response.responseType.name() == 'POST'" then="postView" else="redirectView" />
</decision-state>


这个看不太懂,反正最后执行redirectView了。


<end-state id="redirectView" view="externalRedirect:${requestScope.response.url}" />


执行跳转,哦了

(5)又回到客户端了
请求:


GET /UserManage/?ticket=ST-1-0dnlMstfctMYVcat5wfy-cas01.example.org HTTP/1.1



响应:


HTTP/1.1 302 Moved Temporarily
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=3A8260292C0430806E231B0E76907137; Path=/UserManage
Location: http://10.0.103.137:8080/UserManage/;jsessionid=3A8260292C0430806E231B0E76907137
Content-Length: 0
Date: Wed, 30 Oct 2013 01:54:24 GMT


这个有点莫名其妙,又跳转了。

这时,是客户端的CAS Validation Filter在作怪。


<filter>
    <filter-name>CAS Validation Filter</filter-name>
    <filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
    <init-param>
        <param-name>casServerUrlPrefix</param-name>
        <param-value>http://10.0.103.137:8080/cas/</param-value>
    </init-param>
    <!-- 客户端应用服务地址-->
    <init-param>
        <param-name>serverName</param-name>
        <param-value>http://10.0.103.137:8080</param-value>
    </init-param>
</filter>


它发现URL中有ticket了,马上就拿它去服务端check一下,看是否有效,是否过期什么的。
这里用的是Cas20ProxyReceivingTicketValidationFilter,它继承了AbstractTicketValidationFilter
看看它的doFilter代码:


    public final void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {

        if (!preFilter(servletRequest, servletResponse, filterChain)) {
            return;
        }

        final HttpServletRequest request = (HttpServletRequest) servletRequest;
        final HttpServletResponse response = (HttpServletResponse) servletResponse;
        final String ticket = CommonUtils.safeGetParameter(request, getArtifactParameterName());

        if (CommonUtils.isNotBlank(ticket)) {
            if (log.isDebugEnabled()) {
                log.debug("Attempting to validate ticket: " + ticket);
            }

            try {
                final Assertion assertion = this.ticketValidator.validate(ticket, constructServiceUrl(request, response));

                if (log.isDebugEnabled()) {
                    log.debug("Successfully authenticated user: " + assertion.getPrincipal().getName());
                }

                request.setAttribute(CONST_CAS_ASSERTION, assertion);

                if (this.useSession) {
                    request.getSession().setAttribute(CONST_CAS_ASSERTION, assertion);
                }
                onSuccessfulValidation(request, response, assertion);

                if (this.redirectAfterValidation) {
                    log. debug("Redirecting after successful ticket validation.");
                    response.sendRedirect(constructServiceUrl(request, response));
                    return;
                }
            } catch (final TicketValidationException e) {
                response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                log.warn(e, e);

                onFailedValidation(request, response);

                if (this.exceptionOnValidationFailure) {
                    throw new ServletException(e);
                }

                return;
            }
        }

        filterChain.doFilter(request, response);

    }


取ticket,然后
final Assertion assertion = this.ticketValidator.validate(ticket, constructServiceUrl(request, response))
看看,直接就返回了assertion,这就是登录者信息,包括用户名什么的。
然后放到request和session里了。

validate方法,实际是调用的AbstractUrlBasedTicketValidator的validate方法,看看


        final String validationUrl = constructValidationUrl(ticket, service);
        if (log.isDebugEnabled()) {
            log.debug("Constructing validation url: " + validationUrl);
        }

        try {
            log.debug("Retrieving response from server.");
            final String serverResponse = retrieveResponseFromServer(new URL(validationUrl), ticket);

            if (serverResponse == null) {
                throw new TicketValidationException("The CAS server returned no response.");
            }
            
            if (log.isDebugEnabled()) {
                log.debug("Server response: " + serverResponse);
            }

            return parseResponseFromServer(serverResponse);
        } catch (final MalformedURLException e) {
            throw new TicketValidationException(e);
        }


构建一个URL,然后读取响应。实际调用的AbstractCasProtocolUrlBasedTicketValidator的retrieveResponseFromServer方法


protected final String retrieveResponseFromServer(final URL validationUrl, final String ticket) {
        if (this.hostnameVerifier != null) {
            return CommonUtils.getResponseFromServer(validationUrl, this.hostnameVerifier, getEncoding());
        } else {
            return CommonUtils.getResponseFromServer(validationUrl, getEncoding());
        }
    }


不知道走的那个,但都是getResponseFromServer方法,看看先。


    public static String getResponseFromServer(final URL constructedUrl, final HostnameVerifier hostnameVerifier, final String encoding) {
        URLConnection conn = null;
        try {
            conn = constructedUrl.openConnection();
            if (conn instanceof HttpsURLConnection) {
                ((HttpsURLConnection)conn).setHostnameVerifier(hostnameVerifier);
            }
            final BufferedReader in;

            if (CommonUtils.isEmpty(encoding)) {
                in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            } else {
                in = new BufferedReader(new InputStreamReader(conn.getInputStream(), encoding));
            }

            String line;
            final StringBuilder stringBuffer = new StringBuilder(255);

            while ((line = in.readLine()) != null) {
                stringBuffer.append(line);
                stringBuffer.append("\n");
            }
            return stringBuffer.toString();
        } catch (final Exception e) {
            LOG.error(e.getMessage(), e);
            throw new RuntimeException(e);
        } finally {
            if (conn != null && conn instanceof HttpURLConnection) {
                ((HttpURLConnection)conn).disconnect();
            }
        }

    }


就是直接访问URL,读取数据。
-------------------------
代码看完,看抓包数据吧。
实际上,上面带着ticket跳回客户端后,Filter发起了一个访问

请求:


GET /cas/serviceValidate?ticket=ST-1-0dnlMstfctMYVcat5wfy-cas01.example.org&service=http%3A%2F%2F10.0.103.137%3A8080%2FUserManage%2F%3Bjsessionid%3D3A8260292C0430806E231B0E76907137 HTTP/1.1
User-Agent: Java/1.6.0_26


看,User-Agent是Java哦~~

响应:


HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=UTF-8
Content-Language: zh-CN
Content-Length: 177
Date: Wed, 30 Oct 2013 01:54:24 GMT

<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
    <cas:authenticationSuccess>
        <cas:user>admin</cas:user>
    </cas:authenticationSuccess>
</cas:serviceResponse>


可以看到,服务端给返回的xml数据,只有用户名。
实际上,想返回更多信息也是可以的,需要修改服务端配置,具体方法请搜索,大概是:cas 登录 返回更多信息

服务端/serviceValidate这个请求是


<servlet>
    <servlet-name>cas</servlet-name>
    <servlet-class>
      org.jasig.cas.web.init.SafeDispatcherServlet
    </servlet-class>
</servlet>


这个Servlet响应的,
在cas-servlet.xml中有这样一段:


 <bean
      id="handlerMappingC"
      class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="mappings">
      <props>
        <prop key="/logout">logoutController</prop>
        <prop key="/serviceValidate">serviceValidateController</prop>



所以,对应调到了ServiceValidateController的handleRequestInternal方法里
里面有一句:
final Assertion assertion = this.centralAuthenticationService.validateServiceTicket(serviceTicketId, service);
实际是调用CentralAuthenticationServiceImpl的validateServiceTicket方法

这个类有


    @NotNull
    private TicketRegistry ticketRegistry;

    /** New Ticket Registry for storing and retrieving services tickets. Can point to the same one as the ticketRegistry variable. */
    @NotNull
    private TicketRegistry serviceTicketRegistry;


实际上,ticket信息就是保存在这两个变量里的。

里面有这样的代码:


final ServiceTicket serviceTicket = (ServiceTicket) this.serviceTicketRegistry.getTicket(serviceTicketId, ServiceTicket.class);

final List<Authentication> chainedAuthenticationsList = serviceTicket.getGrantingTicket().getChainedAuthentications();
final Authentication authentication = chainedAuthenticationsList.get(chainedAuthenticationsList.size() - 1);
final Principal principal = authentication.getPrincipal();


所以,能拿到Ticket或grant ticket的话,就可以拿到当前用户信息。

后面看不动了,大概就是把用户信息写到响应里吧。

(6)跳转到之前访问的子系统URL
请求:


GET /UserManage/;jsessionid=3A8260292C0430806E231B0E76907137 HTTP/1.1
Cookie: JSESSIONID=3A8260292C0430806E231B0E76907137


实际上,到前一步,用户信息已经去到,认证过程已经完毕了,单点登录已经实现。
后面就是正常访问子系统了。

但,每次请求,AuthenticationFilter仍然会检查一下。
看一下AuthenticationFilter的代码:


   public final void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
        final HttpServletRequest request = (HttpServletRequest) servletRequest;
        final HttpServletResponse response = (HttpServletResponse) servletResponse;
        final HttpSession session = request.getSession(false);
        final Assertion assertion = session != null ? (Assertion) session.getAttribute(CONST_CAS_ASSERTION) : null;

        if (assertion != null) {
            filterChain.doFilter(request, response);
            return;
        }
.....


由于已经登录,assertion不为空,所以直接跳过了。对性能影响不大。

(7)执行logout时,实际是访问server端的logout
请求:


GET /cas/logout HTTP/1.1
Cookie: CASTGC=TGT-2-DmMvVs9Wy01ScbnMtgtLzgcyX5X14TGcM1f5iGuWIBrlDwTg4Q-cas01.example.org; JSESSIONID=0F5355FE89E099FF14E149CF9C55644C



响应:


HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Pragma: no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control: no-cache
Cache-Control: no-store
Set-Cookie: CASTGC=""; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/cas/
Set-Cookie: CASPRIVACY=""; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/cas/
Set-Cookie: JSESSIONID=6DA0ACAEBA3A801CB897E98CE5E92AFC; Path=/cas
Content-Type: text/html;charset=UTF-8
Content-Language: zh-CN
Content-Length: 728
Date: Wed, 30 Oct 2013 03:31:39 GMT



设置Cookie过期。Session总是新起一个。
然后内容里有:
<script>window.location="/cas/login";</script>

同时,会通知所有已登录的子系统,进行退出操作

请求:


POST /UserManage/ HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Cache-Control: no-cache
Pragma: no-cache
User-Agent: Java/1.6.0_26
Host: 10.0.103.137:8080
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-Length: 484

logoutRequest=%3Csamlp%3ALogoutRequest+xmlns%3Asamlp%3D%22urn%3Aoasis%3Anames%3Atc%3ASAML%3A2.0%3Aprotocol%22+ID%3D%22LR-2-0EQR24uNI0TlMr52CzUYMuf1fOAsQg1O447%22+Version%3D%222.0%22+IssueInstant%3D%222013-10-30T11%3A31%3A39Z%22%3E%3Csaml%3ANameID+xmlns%3Asaml%3D%22urn%3Aoasis%3Anames%3Atc%3ASAML%3A2.0%3Aassertion%22%3E%40NOT_USED%40%3C%2Fsaml%3ANameID%3E%3Csamlp%3ASessionIndex%3EST-2-nfx6OgRedoAMqowQayXr-cas01.example.org%3C%2Fsamlp%3ASessionIndex%3E%3C%2Fsamlp%3ALogoutRequest%3E



响应:


HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Length: 0
Date: Wed, 30 Oct 2013 03:31:39 GMT


响应是由客户端的SingleSignOutFilter做出的。


public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
        final HttpServletRequest request = (HttpServletRequest) servletRequest;

        if (handler.isTokenRequest(request)) {
            handler.recordSession(request);
        } else if (handler.isLogoutRequest(request)) {
            handler.destroySession(request);
            // Do not continue up filter chain
            return;
        } else {
            log.trace("Ignoring URI " + request.getRequestURI());
        }

        filterChain.doFilter(servletRequest, servletResponse);
    }



(8)奇怪的一点,退出后,在登录页面,马上进行登录,
提交后,服务端还会执行一次跳转。


HTTP/1.1 302 Moved Temporarily
Server: Apache-Coyote/1.1
Pragma: no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control: no-cache
Cache-Control: no-store
Set-Cookie: JSESSIONID=3911117B64346B50E24F36632C2419D7; Path=/cas
Location: http://10.0.103.137:8080/cas/login
Content-Length: 0
Date: Wed, 30 Oct 2013 03:37:07 GMT



非得重新生成一个session才干~~导致登录页面又刷了一下,这时再登录就OK了。
奇怪,奇怪~~
原因不明,调查中~~
----------------
我靠:
登录成功后,马上点退出,就有此问题
等几秒,再点退出,就无此问题。

原因是,登录成功后,会马上咔嚓掉session,默认是2秒后挂掉。
马上点退出,Session还没挂,所以不会重建Session,
所以,登录页面提交时,是带着这个Session的ID去的,而此时Session挂掉了,故引起页面刷新。

而等几秒再退出,会创建新session,故后面无问题。

更多信息请看
http://www.mytju.com/classcode/news_readNews.asp?newsID=504
 

评论 COMMENTS
guest1027988546
2013/12/17 11:56:18
写的很清楚,阅读CAs源码难得的帮助文档呀!
guest956590742
2016/1/27 16:34:31
cas 4 有设置同步和异步退出的。默认是异步的,所以退出后还有可能客户端没收到服务端的退出请求,没退出。设置同步就好了。
guest398875934
2017/7/10 16:11:52
楼主,可以把cas的这块的代码给我发一份不,2965837215@qq.com
guest398875934
2017/7/10 16:14:03
不发代码可以加个QQ不,想请教你,用评论这个有点慢,QQ号:2965837215

添加评论 Add new comment.
昵称 Name:
评论内容 Comment:
验证码(不区分大小写)
Validation Code:
(not case sensitive)
看不清?点这里换一张!(Change it here!)
 
评论由管理员查看后才能显示。the comment will be showed after it is checked by admin.
CopyRight © 心缘地方 2005-2999. All Rights Reserved