【问题标题】:Smartcard terminal removal : SCARD_E_NO_SERVICE CardException智能卡终端移除:SCARD_E_NO_SERVICE CardException
【发布时间】:2013-05-31 01:58:31
【问题描述】:

我正在开发一个使用smartcardio 来处理智能卡的Java 应用程序。 必须可以让一个人移除其 USB 读卡器,然后重新插入,而无需重新启动小程序。

我正在使用terminals() 和waitForChange() 方法来检测终端更改,它在Linux、MacOS 和Win7 上运行良好。

但在 Windows 8(和仅限 Windows 8)上,在删除最后一个终端后,这些方法会抛出 SCARD_E_NO_SERVICE CardException,并且不会检测到更多更改。

我不确定它在说什么“服务”。但我认为这是在我调用TerminalFactory.getDefault() 以拥有TerminalFactory 单例时在我的线程中启动的。而且我认为这个单例可能有办法管理底层服务,这就是被破坏的地方。

有人知道如何在 Windows 8 上使用smartcardio 管理终端断开连接吗?

【问题讨论】:

    标签: java windows-8 smartcard pcsc


    【解决方案1】:

    这篇文章已经很老了,但它对我解决 Windows 8 上描述的问题很有用。

    JR Utily 的解决方案不能完全发挥作用:如果读卡器被拔掉然后再次插入,CardTerminal 实例上会出现错误。

    所以我添加了一些代码来清除终端列表,如下面的代码所示。

            Class pcscterminal = Class.forName("sun.security.smartcardio.PCSCTerminals");
            Field contextId = pcscterminal.getDeclaredField("contextId");
            contextId.setAccessible(true);
    
            if(contextId.getLong(pcscterminal) != 0L)
            {
                // First get a new context value
                Class pcsc = Class.forName("sun.security.smartcardio.PCSC");
                Method SCardEstablishContext = pcsc.getDeclaredMethod(
                                                   "SCardEstablishContext",
                                                   new Class[] {Integer.TYPE }
                                               );
                SCardEstablishContext.setAccessible(true);
    
                Field SCARD_SCOPE_USER = pcsc.getDeclaredField("SCARD_SCOPE_USER");
                SCARD_SCOPE_USER.setAccessible(true);
    
                long newId = ((Long)SCardEstablishContext.invoke(pcsc, 
                        new Object[] { SCARD_SCOPE_USER.getInt(pcsc) }
                ));
                contextId.setLong(pcscterminal, newId);
    
    
                // Then clear the terminals in cache
                TerminalFactory factory = TerminalFactory.getDefault();
                CardTerminals terminals = factory.terminals();
                Field fieldTerminals = pcscterminal.getDeclaredField("terminals");
                fieldTerminals.setAccessible(true);
                Class classMap = Class.forName("java.util.Map");
                Method clearMap = classMap.getDeclaredMethod("clear");
    
                clearMap.invoke(fieldTerminals.get(terminals));
            }
    

    【讨论】:

    • 谢谢!。我必须指出,PCSCTerminal Map 字段“stateMap”也必须被清除。
    • 我不再从事需要这个的项目,但我会告诉他们这个更好的解决方案:)
    • 我在 Windows 10 上尝试了该解决方案,但我仍然有问题。应用您的示例后,终端“回来了”。看起来可用,读取卡元数据等等......但是当我尝试登录时,我看到了以下内容。当终端断开连接时,调用 SunPKCS11.uninitToken()。终端返回后,调用 SunPKCS11.initToken() 但 slotInfo.flags 不包含 CKF_TOKEN_PRESENT 并且 init 创建一个轮询器来等待它。结果是……登录失败。我确定我什么都没看到,但我不知道是什么......
    【解决方案2】:

    我找到了一种方法,但它使用的是反射代码。我宁愿找到一种更清洁的方法,但似乎没有官方的 API 来管理智能卡上下文。所有课程都是私有的。

    sun.security.smartcardio.PCSCTerminals (http://www.docjar.com/html/api/sun/security/smartcardio/PCSCTerminals.java.html) 的 initContext() 方法可防止新线程在第一个线程被初始化后获得新的上下文:该方法被调用,但上下文被视为单例并且不重新-初始化。

    通过private 与java.lang.reflect 在此相关的所有内容中传递,可以强制创建新上下文并将其新ID 存储为“官方”contextId。这应该在实例化新的TerminalFactory 之前完成。

        // ...
        Class pcscterminal = Class.forName("sun.security.smartcardio.PCSCTerminals");
        Field contextId = pcscterminal.getDeclaredField("contextId");
        contextId.setAccessible(true);
    
        if(contextId.getLong(pcscterminal) != 0L)
        {
            Class pcsc = Class.forName("sun.security.smartcardio.PCSC");
            Method SCardEstablishContext = pcsc.getDeclaredMethod(
                                               "SCardEstablishContext",
                                               new Class[] {Integer.TYPE }
                                           );
            SCardEstablishContext.setAccessible(true);
    
            Field SCARD_SCOPE_USER = pcsc.getDeclaredField("SCARD_SCOPE_USER");
            SCARD_SCOPE_USER.setAccessible(true);
    
            long newId = ((Long)SCardEstablishContext.invoke(pcsc, 
                  new Object[] { Integer.valueOf(SCARD_SCOPE_USER.getInt(pcsc)) }
                  )).longValue();
            contextId.setLong(pcscterminal, newId);
        }
        // ...
    

    【讨论】:

      【解决方案3】:

      (这只是一个评论,但我没有足够的代表来发布 cmets。)

      它所指的服务是Windows智能卡服务,也称为智能卡资源管理器。如果您打开 Services MMC 控制台,您将在那里看到它的启动类型设置为 Manual (Trigger Start)。在 Windows 8 中,此服务已更改为仅在将智能卡读卡器连接到系统时运行(以节省资源),并且在删除最后一个读卡器时自动停止该服务。停止服务会使所有未完成的句柄失效。

      原生的Windows解决方案是调用SCardAccessStartedEvent并使用它返回的句柄等待服务启动,然后再使用SCardEstablishContext再次连接到资源管理器。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-11-22
        • 2017-10-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多