Как мне программно вызвать аутентификацию из сервлета, как это сделал бы j_security_check

У нас есть аутентификация для входа в веб-форму с j_securtiy_check. Мы хотели бы изменить его с помощью программной аутентификации при входе. Каков правильный способ аутентификации сервлетом имени пользователя и пароля, переданных ему? Сервлет явно незащищен.

Мы экспериментировали с этой областью 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. Этот сервлет в настоящее время аутентифицируется в службе единого входа 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());

person D-Klotz    schedule 10.12.2013    source источник


Ответы (4)


Если вы уже используете Servlet 3.0 или новее, для программной аутентификации используйте login() метод HttpServletRequest.

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

Servlet API предоставляет вам login() и logout() для программного доступа к безопасности, управляемой контейнером.

person Rustam    schedule 26.02.2014

Я думаю, что в клиентском приложении Java webstart, когда вам нужно запросить аутентификацию, вы просто используете любой HTTP-клиент для отправки имени пользователя и пароля на ваш LoginServer с использованием метода POST. В loginServlet вы используете request.login (userName, password), а затем возвращаете результат аутентификации в любом формате (XML, JSON). На стороне клиента вы также должны проанализировать результат аутентификации (результат POST) и файл cookie JSESSIONID из заголовка ответа. Для последующих запросов вам может потребоваться отправить JSESSIONID, который вы проанализировали ранее.

person Loc    schedule 10.12.2013
comment
Оказывается, мое затмение все еще было настроено для сервера 2.X. У меня не было доступа к HttpServletRequest 3.0, где доступен логин (пользователь, пароль). Теперь, когда я попробовал это, он терпит неудачу с менее чем полезной ошибкой: Вызвано: javax.servlet.ServletException: Ошибка входа в org.apache.catalina.authenticator.AuthenticatorBase.doLogin(AuthenticatorBase.java:820) в org.apache. catalina.authenticator.AuthenticatorBase.login(AuthenticatorBase.java:800) по адресу org.apache.catalina.connector.Request.login(Request.java:2621). Я не думаю, что OpenAm поддерживает это. - person D-Klotz; 17.12.2013
comment
Вы можете использовать другой способ (не использовать request.login Servlet 3.0+): отправьте запрос аутентификации в действие /j_security_check с параметром имени пользователя j_username и именем параметра пароля j_password. Контейнер будет обрабатывать вашу аутентификацию (это похоже на request.login). Обратите внимание, что это решение для входа в контейнер. - person Loc; 17.12.2013
comment
docs.oracle.com/javaee/6/tutorial/doc/glxce. html - ссылка j_security_check - person Loc; 17.12.2013

Я хотел проследить за этим.

На самом деле нет простого ответа.

Код в конце использует чистое отражение, чтобы попытаться вызвать метод аутентификации в области. Проблема в том, что это действительно зависит от присоединенной области.

JOSSO (org.josso.tc55.agent.jaas.CatalinaJAASRealm), например, не имеет этого метода. Вместо этого у него есть нечто, называемое createPrincipal (имя пользователя String, тема темы). Предлагаемый ими процесс для этого (по крайней мере, для josso 1.5) заключается в использовании такого кода:

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

Если вы используете OpenAM (это то, на что я пытаюсь перейти) в качестве поставщика единого входа вместо JOSSO, все будет совершенно по-другому. Текущая идея, которую я собираюсь использовать, состоит в том, чтобы использовать службу RESTful, которую они предоставляют непосредственно из клиента веб-запуска.

Моя первая проблема с этой идеей - это попытка найти API, который я могу использовать из java-клиента webstart, который 1) не имеет огромного размера файла jar, 2) работает с tomee+ CXF версии 2.6.4. (Я недостаточно знаю об этом, чтобы сказать: «Да, просто используйте клиентские jar-файлы CXF 3.0, поскольку они будут нормально работать с версией CXF от tomee+...»)

Во всяком случае, вот код, который «должен» работать, если вы используете готовые механизмы источника данных 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();
            }

Опять же, это если вы пытаетесь аутентифицировать пользователя из незащищенного сервлета.

-Деннис

person D-Klotz    schedule 13.12.2013

Я заметил, что это уже не актуально. Окончательным решением было использование Java SDK, предоставляемого OpenAM.

Это отправная точка: http://openam.forgerock.org/openam-documentation/openam-doc-source/doc/dev-guide/index/chap-jdk.html

1) добавьте все jar-файлы, которые поставляются с этим SDK, в ваше веб-приложение. 2) Измените свой сервлет (или тяжелый клиент), чтобы он имел следующий код:

    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, который вы можете передать всем своим защищенным клиентским приложениям, чтобы у пользователя не возникало проблем при входе в систему.

Надеюсь, это поможет.

-dklotz

person D-Klotz    schedule 14.01.2014