3

我们有基于 Web 的表单登录身份验证j_securtiy_check。我们想通过编程登录身份验证来改变它。让 servlet 验证传递给它的用户名和密码的正确方法是什么?servlet 显然是不受保护的。

我们一直在试验这个 server.xml 领域:

<Realm  className="org.apache.catalina.realm.DataSourceRealm"
    dataSourceName="UserDatabase"
    userTable="app_user" userNameCol="login_name" userCredCol="password_value"
    userRoleTable="user_perm" roleNameCol="permission_name"
    allRolesMode="authOnly" digest="MD5"
/>

原因是我们有一个 java webstart 客户端,它将登录信息发送到未受保护的 loginServlet。此 servlet 当前针对 JOSSO 单点登录服务进行身份验证,但我希望删除它并为初学者使用简单的 tomcat7 身份验证。然后最终迁移到 OpenAM。如果我可以以编程方式生成 JSSESSIONIDSSO 值并将其填充到 cookie 中。

这是我找到的一些代码。这是调用身份验证的正确方法吗?

ApplicationContextFacade acf = (ApplicationContextFacade) this.getServletContext();

Field privateField = ApplicationContextFacade.class.getDeclaredField("context");  
privateField.setAccessible(true);  
ApplicationContext appContext = (ApplicationContext) privateField.get(acf);  
Field privateField2 = ApplicationContext.class.getDeclaredField("context");  
privateField2.setAccessible(true);  
StandardContext stdContext = (StandardContext) privateField2.get(appContext);  
Realm realm = stdContext.getRealm();  

Principal principal = realm.authenticate(loginBean.getUsername(), loginBean.getPassword());  
if (principal == null)
{
   return 0;
}
GenericPrincipal genericPrincipal = (GenericPrincipal) principal;

System.out.println ("genericPrincipal=" + genericPrincipal.toString());
4

4 回答 4

5

如果您已经在使用 Servlet 3.0 或更新版本,对于程序化身份验证,请login()使用HttpServletRequest.

if (request.getUserPrincipal() == null) {
    request.getSession(); // create session before logging in
    request.login(username, password);
}

Servlet API 为您提供了以编程方式访问容器管理的安全性的方法login()和方法。logout()

于 2014-02-26T13:14:04.940 回答
1

我认为在 Java webstart 客户端应用程序中,当您需要进行身份验证时,您只需使用任何 HTTP 客户端使用 POST 方法将用户名、密码发送到您的 LoginServer。在 loginServlet 中,您使用 request.login ( userName, password ) 然后以任何格式(XML、JSON)返回身份验证结果。在客户端,您也必须从响应标头中解析身份验证结果( POST 结果)和 JSESSIONID cookie。对于后续请求,您可能必须发送之前解析的 JSESSIONID。

于 2013-12-10T22:36:31.387 回答
1

我想跟进这件事。

真的没有一个简单的答案。

最后的代码使用纯反射来尝试在领域内调用 authenticate 方法。问题在于,这实际上取决于附加的领域。

例如 JOSSO (org.josso.tc55.agent.jaas.CatalinaJAASRealm) 没有这个方法。相反,它有一个叫做 createPrincipal(String username, Subject subject) 的东西。他们建议的执行此操作的过程(至少对于 josso 1.5)是使用如下代码:

            impl = getIdentityProvider(endpoint);
            String assertion =   impl.assertIdentityWithSimpleAuthentication(username,password);
            sessionID = impl.resolveAuthenticationAssertion(assertion);

如果您使用 OpenAM(这是我试图迁移到的)作为您的单点登录提供程序而不是 JOSSO,那将是完全不同的。我目前的想法是使用他们直接从 webstart 客户端提供的 RESTful 服务。

我对这个想法的第一个问题 - 试图找到一个我可以从 webstart java 客户端使用的 API 1)没有巨大的 jar 文件大小,2)适用于 tomee+ CXF 版本 2.6.4。(我对此知之甚少,“是的,只需使用 CXF 3.0 客户端 jar,因为它们可以与 tomee+ 的 CXF 版本一起正常工作......”)

无论如何,如果您使用 Tomcat7 的罐装数据源机制来设置领域,那么这里的代码“应该”工作。

            Class c = Class.forName("org.apache.catalina.core.ApplicationContextFacade");
            Object o = this.getServletContext();
            System.out.println ("servletContext is really:" + o.getClass().getCanonicalName());

            Field privateField = o.getClass().getDeclaredField("context");  
            privateField.setAccessible(true);  
            Object appContext =  privateField.get(o);  
            Field privateField2 = appContext.getClass().getDeclaredField("context");  
            privateField2.setAccessible(true);  
            Object stdContext =  privateField2.get(appContext);
            Method getRealm = stdContext.getClass().getMethod("getRealm");
            Object realm = getRealm.invoke(stdContext);

            Principal principal = null;
            try
            {
                Method authenticate = realm.getClass().getMethod("authenticate");  
                principal = (Principal)authenticate.invoke(realm, loginBean.getUsername(), loginBean.getPassword());
                if (principal == null)
                {
                    return 0;
                }
            }
            catch (Exception e2)
            {
                // The authenticate method doesn't exist within the configured server.xml realm
                e2.printStackTrace();
            }

同样,这是如果您发现自己试图从未受保护的 servlet 中验证用户的情况。

-丹尼斯

于 2013-12-13T15:58:06.443 回答
0

我注意到这不再是最新的。最终的解决方案是使用 OpenAM 提供的 Java SDK。

这是起点: http: //openam.forgerock.org/openam-documentation/openam-doc-source/doc/dev-guide/index/chap-jdk.html

1) 将此 SDK 附带的所有 jar 文件添加到您的 Web 应用程序。2)更改您的 servlet(或重型客户端)以具有以下代码:

    private void addLoginCallbackMessage(LoginCredentialsBean loginBean, Callback [] callbacks)
        throws UnsupportedCallbackException
{
    int i = 0;
    try
    {
        for (i = 0; i < callbacks.length; i++)
        {
            if (callbacks[i] instanceof TextOutputCallback)
            {
                handleTextOutputCallback((TextOutputCallback) callbacks[i]);
            }
            else if (callbacks[i] instanceof NameCallback)
            {
                handleNameCallback(loginBean.getUsername(), (NameCallback) callbacks[i]);
            }
            else if (callbacks[i] instanceof PasswordCallback)
            {
                handlePasswordCallback(loginBean.getPassword(), (PasswordCallback) callbacks[i]);
            }
            else if (callbacks[i] instanceof TextInputCallback)
            {
                handleTextInputCallback((TextInputCallback) callbacks[i]);
            }
            else if (callbacks[i] instanceof ChoiceCallback)
            {
                handleChoiceCallback((ChoiceCallback) callbacks[i]);
            }
        }
    }
    catch (IOException e)
    {
        e.printStackTrace();
        throw new UnsupportedCallbackException(callbacks[i], e.getMessage());
    }
}

private void handleTextOutputCallback(TextOutputCallback toc)
{
    System.out.println("Got TextOutputCallback");
    // display the message according to the specified type

    switch (toc.getMessageType())
    {
    case TextOutputCallback.INFORMATION:
        System.out.println(toc.getMessage());
        break;
    case TextOutputCallback.ERROR:
        System.out.println("ERROR: " + toc.getMessage());
        break;
    case TextOutputCallback.WARNING:
        System.out.println("WARNING: " + toc.getMessage());
        break;
    default:
        System.out.println("Unsupported message type: " +
                toc.getMessageType());
    }
}

private void handleNameCallback(String name, NameCallback nc)
        throws IOException
{
    nc.setName(name);
}

private void handleTextInputCallback(TextInputCallback tic)
        throws IOException
{
    // not supported for server side
    // prompt for text input
}

private void handlePasswordCallback(String password, PasswordCallback pc)
        throws IOException
{
    // prompt the user for sensitive information

    pc.setPassword(password.toCharArray());
}

private void handleChoiceCallback(ChoiceCallback cc)
        throws IOException
{
    // not supported for server side

    // ignore the provided defaultValue
    /*        
    System.out.print(cc.getPrompt());

    String [] strChoices = cc.getChoices();
    for (int j = 0; j < strChoices.length; j++)
    {
        System.out.print("choice[" + j + "] : " + strChoices[j]);
    }
    System.out.flush();
    cc.setSelectedIndex(Integer.parseInt((new BufferedReader
            (new InputStreamReader(System.in))).readLine()));
    */
}


private void doLogin ()
{
    // ... lots of other logic here

    // TODO: Make this into modules with this one being for OpenAM
    if (_useOpenAM)
    {
        String orgName = "/";
        String moduleName = "DataStore";
        String locale = "en_US";

        AuthContext lc = new AuthContext(orgName);
        AuthContext.IndexType indexType = AuthContext.IndexType.MODULE_INSTANCE;
        lc.login(indexType, moduleName, locale);

        boolean succeed = false;
        Callback [] callbacks = null;

        // get information requested from module
        while (lc.hasMoreRequirements())
        {
            callbacks = lc.getRequirements();
            if (callbacks != null)
            {
                addLoginCallbackMessage(loginBean, callbacks);
                lc.submitRequirements(callbacks);
            }
        }

        if (lc.getStatus() == AuthContext.Status.SUCCESS)
        {
            try
            {
                System.out.println("Login succeeded.");
                openAMSessionId = lc.getAuthIdentifier();
                System.out.println("lc.getAuthIdentifier()=" + openAMSessionId);
                System.out.println("lc.getSuccessURL()=" + lc.getSuccessURL());
                System.out.println("lc.getSSOToken().getAuthLevel()=" + lc.getSSOToken().getAuthLevel());
                System.out.println("lc.getSSOToken().getAuthType()=" + lc.getSSOToken().getAuthType());
                System.out.println("lc.getSSOToken().getHostName()=" + lc.getSSOToken().getHostName());
                System.out.println("lc.getSSOToken().getIdleTime()=" + lc.getSSOToken().getIdleTime());
                System.out.println("lc.getSSOToken().getMaxIdleTime()=" + lc.getSSOToken().getMaxIdleTime());
                System.out.println("lc.getSSOToken().getMaxSessionTime()=" + lc.getSSOToken().getMaxSessionTime());
                System.out.println("lc.getSSOToken().getTimeLeft()=" + lc.getSSOToken().getTimeLeft());
                System.out.println("lc.getSSOToken().getIPAddress()=" + lc.getSSOToken().getIPAddress());
                System.out.println("lc.getSSOToken().getTokenID()=" + lc.getSSOToken().getTokenID().toString());
                System.out.println("lc.getSSOToken().getPrincipal()=" + lc.getSSOToken().getPrincipal().toString());
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }

            succeed = true;
        }
        else if (lc.getStatus() == AuthContext.Status.FAILED)
        {
            System.out.println("Login failed.");
        }
        else
        {
            System.out.println("Unknown status: " + lc.getStatus());
        }

        System.out.println( "OpenAM login success=" + succeed);
    }
}

上面代码中最重要的是变量 openAMSessionId 。最终获得了新的 OpenAM 单点登录会话 ID,您可以将其传递给所有受保护的客户端应用程序,这样用户就不会在登录时受到挑战。

我希望这有帮助。

-dklotz

于 2014-01-14T21:21:30.190 回答