9

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

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

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

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

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

4

3 回答 3

18

这篇文章已经很老了,但它对我解决 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));
        }
于 2014-10-20T16:09:18.470 回答
4

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

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

通过privatein 周围的所有内容 with 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);
    }
    // ...
于 2013-06-07T15:35:32.927 回答
3

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

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

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

于 2013-06-20T08:46:26.113 回答