cas+shiro遇到的一个问题:统一注销失败,session的注销跟创建事件都能捕获到,原因是:

最近遇到一个非常麻烦的问题,cas+shiro 统一注销的失败,cas正常注销掉,但是应用里面的用户信息没有被注销掉

跟踪问题:

  首先怀疑SingleSignOutHttpSessionListener监听器没有正常工作没有把应用的session注销掉,这里可以跟大家讲一下cas 客户端及服务器的注销原理

    1,客户端发送一个注销请求到cas server,跟踪casorg.jasig.cas.CentralAuthenticationServiceImpl类的destroyTicketGrantingTicket注销方法,

服务端注销代码

@Audit(
            action="TICKET_GRANTING_TICKET_DESTROYED",
            actionResolverName="DESTROY_TICKET_GRANTING_TICKET_RESOLVER",
            resourceResolverName="DESTROY_TICKET_GRANTING_TICKET_RESOURCE_RESOLVER")
    @Profiled(tag = "DESTROY_TICKET_GRANTING_TICKET", logFailuresSeparately = false)
    @Transactional(readOnly = false)
    @Override
    public List<LogoutRequest> destroyTicketGrantingTicket(final String ticketGrantingTicketId) {
        Assert.notNull(ticketGrantingTicketId);

        logger.debug("Removing ticket [{}] from registry.", ticketGrantingTicketId);
        final TicketGrantingTicket ticket = this.ticketRegistry.getTicket(ticketGrantingTicketId,
                TicketGrantingTicket.class);

        if (ticket == null) {
            logger.debug("TicketGrantingTicket [{}] cannot be found in the ticket registry.", ticketGrantingTicketId);
            return Collections.emptyList();
        }

        logger.debug("Ticket found. Processing logout requests and then deleting the ticket...");    //在这里cas server会去根据客户端带过来的ticket找到所有在cas server服务注册过的cas client server,让后对这些客户端服务发送一个http请求,客户端接受请求,删除//本身的ticket及注销session,cas server发送请求看下一段代码
        final List<LogoutRequest> logoutRequests = logoutManager.performLogout(ticket);
        this.ticketRegistry.deleteTicket(ticketGrantingTicketId);

        return logoutRequests;
    } @Override
public List<LogoutRequest> performLogout(final TicketGrantingTicket ticket) {
        final Map<String, Service> services;
        // synchronize the retrieval of the services and their cleaning for the TGT
        // to avoid concurrent logout mess ups
        synchronized (ticket) {
            services = ticket.getServices();
            ticket.removeAllServices();
        }
        ticket.markTicketExpired();

        final List<LogoutRequest> logoutRequests = new ArrayList<LogoutRequest>();
        // if SLO is not disabled
        if (!disableSingleSignOut) {
            // through all services

        //循环遍历客户端的server
            for (final String ticketId : services.keySet()) {
                final Service service = services.get(ticketId);
                // it‘s a SingleLogoutService, else ignore
                if (service instanceof SingleLogoutService) {
                    final SingleLogoutService singleLogoutService = (SingleLogoutService) service;
                    // the logout has not performed already
                    if (!singleLogoutService.isLoggedOutAlready()) {
                        final LogoutRequest logoutRequest = new LogoutRequest(ticketId, singleLogoutService);
                        // always add the logout request
                        logoutRequests.add(logoutRequest);
                        final RegisteredService registeredService = servicesManager.findServiceBy(service);
                        // the service is no more defined, or the logout type is not defined or is back channel
                        if (registeredService == null || registeredService.getLogoutType() == null
                                || registeredService.getLogoutType() == LogoutType.BACK_CHANNEL) {
                            // perform back channel logout                                //向客户端发送一个请求,
                            if (performBackChannelLogout(logoutRequest)) {
                                logoutRequest.setStatus(LogoutRequestStatus.SUCCESS);
                            } else {
                                logoutRequest.setStatus(LogoutRequestStatus.FAILURE);
                                LOGGER.warn("Logout message not sent to [{}]; Continuing processing...",
                                        singleLogoutService.getId());
                            }
                        }
                    }
                }
            }
        }

        return logoutRequests;
    }

下面在来看一下客户端接收的代码,客户段单点注销必须配置SingleSignOutFilter,前文cas server发送一个注销请求回来的时候会被接收处理

public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
        final HttpServletRequest request = (HttpServletRequest) servletRequest;
        //重点的两个IF判断,同时也是逻辑处理,第一是判断是不是第一次登录的时候会进入方法往cas SessionMappingStorage的添加ticket,sessionId键值对
        if (handler.isTokenRequest(request)) {
            LOG.warn("第{}次进来映射,sessionId={}", index++, request.getSession().getId());
            handler.recordSession(request);      //判断是否是注销请求,如果是注销请求进入逻辑,注销session同时删除SessionMappingStorage的键值对(就是这里实现了统一注销)
        } else if (handler.isLogoutRequest(request)) {
            LOG.warn("第{}进来删除映射,sessionId={}", removeIndex++, request.getSession().getId());
            handler.destroySession(request);
            // Do not continue up filter chain
            return;
        } else {
            log.trace("Ignoring URI " + request.getRequestURI());
        }

        filterChain.doFilter(servletRequest, servletResponse);
    }

回到原来的主题,通过日志打印检查SingleSignOutHttpSessionListener有正常监听到session的注销行动,也完成了我想要的操作,此路不通了..

  第二个猜想SingleSignOutHttpSessionListener监听到完成我想要的操作了,以及前文的注销handler.destroySession(request);所注销的session与我保存到键值对里面的不是同一个session

  通过打印日志:发现一个问题

[WARN ] 2016-01-27 21:18:17,423 --> com.chenrd.shiro.SingleSignOutFilter.doFilter(SingleSignOutFilter.java:63): 第6次进来映射,sessionId=7EC4C5CFCEBA41C47254F4DCC2497EAA
[DEBUG] 2016-01-27 21:18:17,424 --> org.jasig.cas.client.session.SingleSignOutHandler.recordSession(SingleSignOutHandler.java:118): Recording session for token ST-248-U4CpR4jgP0qkrVQP4Q9N-cas01.example.org

[DEBUG] 2016-01-27 21:18:39,051 --> org.jasig.cas.client.util.CommonUtils.safeGetParameter(CommonUtils.java:291): safeGetParameter called on a POST HttpServletRequest for LogoutRequest. Cannot complete check safely. Reverting to standard behavior for this Parameter
[WARN ] 2016-01-27 21:18:59,655 --> com.chenrd.shiro.ShiroCheckFilter.doFilter(ShiroCheckFilter.java:67): BB1616D432877F2D4998F5FD88BEA7B6

上面只是两个重要日志,一个是登录成功进行映射的日志,下面的是注销时的日志

上面说的发现的问题就是,我的一次登录操作里面尽然多次输出了上面两个操作,说面session被多次的注销及创建(两个日志不对等),那么第二个猜想就成立,没有注销掉正确的session

那么问题很明显了,下面就是说明一下为什么会发送这样的情况,通过跟踪代码,找到答案:原来我的项目中,使用了web service,由于我的业务需要,在登录的时候会有web service 请求来访问项目,web service请求同样也是http请求过来,其中没有带上cas登录的身份信息,SingleSignOutFilter会进入注销逻辑去注销session。

/jaxws/services/**=anon我的shiro配置了web service不进行身份验证也是无效的,因为SingleSignOutFilter过滤器的优先级在shiro过滤前面,所以才会发送这个问题。

  

时间: 2024-10-28 04:18:54

cas+shiro遇到的一个问题:统一注销失败,session的注销跟创建事件都能捕获到,原因是:的相关文章

cas+shiro实现不时时的去请求cas进行身份验证

cas+shiro不进行时时去cas验证身份信息,需要用shiro在当前系统有一份完整的认证机构. 那么有一个问题,什么时候去cas校验信息,目前的配置方式: cas系统设置默认的浏览器session存活时间,当前系统的session存活时间为30分钟,那么当当前系统身份验证失败是,去cas校验. 这里涉及到一个非常重要的节点,就是shiro框架内部是怎么进行cas校验的呢,请看代码: org.apache.shiro.web.filter.AccessControlFilterd还是所有默认验

Apache shiro(3)—cas + shiro配置说明

这篇博客我们还是继续讲如何使用,其他的我自己还需要一些时间来消化.这次因为项目的需要,将登录从shiro中抽出来交给cas来验证.Shiro,只负责权限的验证.shiro在1.2版本之后加入了对cas的支持.我们先从cas入手-- SSO的概念 单点登录( Single Sign-On , 简称 SSO )是目前比较流行的服务于企业业务整合的解决方案之一, SSO就是一次登录,就可以访问多个互相信任的应用.而cas只是这种解决方案的一种实现. 这么说吧:大家应该都去过游乐场,通常会卖通票.就是买

shiro实现APP、web统一登录认证和权限管理

先说下背景,项目包含一个管理系统(web)和门户网站(web),还有一个手机APP(包括Android和IOS),三个系统共用一个后端,在后端使用shiro进行登录认证和权限控制.好的,那么问题来了web和APP都可以用shiro认证吗?两者有什么区别?如果可以,解决方案是什么?看着大家焦急的小眼神,接下来挨个解决上面的问题. web和APP可以用shiro统一登录认证吗? 可以.假如web和APP都使用密码登录的话,那没的说肯定是可以的,因为对于shiro(在此不会介绍shiro详细知识,只介

shiro实现app和web统一登录

(转自:http://www.cnblogs.com/sunshine-2015/p/5515429.html) 先说下背景,项目包含一个管理系统(web)和门户网站(web),还有一个手机APP(包括Android和IOS),三个系统共用一个后端,在后端使用shiro进行登录认证和权限控制.好的,那么问题来了web和APP都可以用shiro认证吗?两者有什么区别?如果可以,解决方案是什么?看着大家焦急的小眼神,接下来挨个解决上面的问题. web和APP可以用shiro统一登录认证吗? 可以.假

OC中动态创建可变数组的问题.有一个数组,数组中有13个元素,先将该数组进行分组,每3个元素为一组,分为若干组,最后用一个数组统一管理这些分组.(要动态创建数组).两种方法

<span style="font-size:24px;">//////第一种方法 // NSMutableArray *arr = [NSMutableArray array]; // for (int i = 0; i < 13; i ++) { // [arr addObject:[NSString stringWithFormat:@"lanou%d",i + 1]]; // } // NSLog(@"%@",arr);

利用atimicInteger cas的特性实现一个锁

利用atimicInteger cas的特性实现一个锁 主要是使用的是 atomicIntegerAPI 的compareAndSet()方法,让线程不在阻塞,获取不到直接失败. 我们先定义一个异常类 public class GetLockException extends Exception { public GetLockException() { super(); } public GetLockException(String message) { super(message); }

JSTL c:If 标签 test条件后追加一个空格导致判断失败

上联:no zuo no die why you try 下联:no try no high give me five 横批: let it go. 坑爹bug: 错误写法: <c:if test="${4 > 0 } ">true</c:if> (最后一个大括号后多出一个空格,代码啥都不会输出) 正确写法: <c:if test="${4 > 0 }">true</c:if> 正确写法 输出结果: tru

一个IPv6地址绑定失败的问题

引子 双栈环境中,其中一个IPv6地址绑定失败,日志中没有打印具体的错误信息. 问题原因   在环境中,bond6设备中,虽然协议层处于UP状态,但是bond设备中所有的网口都是down,所以其IPv6地址处于tentative状态. bond6设备信息如下所示: # ip addr show dev bond632: bond6: <NO-CARRIER,BROADCAST,MULTICAST,MASTER,UP> mtu 1500 qdisc noqueue state DOWN    

cas单点注销失败Error Sending message to url endpoint

最近在做cas单点登录时,由于是单点登录.必然会涉及到单点注销,然而在做单点注销时由于对cas注销机制不了解加之测试条件所致,所有测试都是在本机下完成(机器性能较低,没用虚拟机):导致折腾了很久.网上说的原因也各式各样,大部分原因集中在: 1:你的CAS服务器将cookie设置成了浏览器有效,那么表示如果浏览器不关闭,则一直有效. 在WEB-INF/spring-configuration/ticketGrantingTicketCookieGenerator.xml中设置cookie有效期,默