我如何以编程方式从像j_security_check这样的servlet中调用authenticate

时间:2013-12-10 21:18:18

标签: tomcat authentication servlets programmatically-created j-security-check

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

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

<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 个答案:

答案 0 :(得分:5)

如果您已经使用Servlet 3.0或更高版本,则对于编程身份验证,请使用login()的{​​{1}}方法。

HttpServletRequest

Servlet API为您提供了login()logout()方法,用于对容器管理的安全性进行编程访问。

答案 1 :(得分:1)

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

答案 2 :(得分: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中验证用户。

丹尼斯

答案 3 :(得分: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