JAX-WS аутентификация по базе данных - PullRequest
5 голосов
/ 18 декабря 2008

Я реализую веб-сервис JAX-WS, который будет использоваться внешними клиентами Java и PHP.

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

Какой механизм аутентификации лучше использовать, чтобы убедиться, что клиенты могут использовать его?

Ответы [ 3 ]

9 голосов
/ 31 января 2012

Для нашей аутентификации через веб-сервис мы используем двоякий подход, чтобы удостовериться, что клиенты с разными предпосылками могут проходить аутентификацию.

  • Аутентификация с использованием параметра имени пользователя и пароля в заголовке HTTP-запроса
  • Аутентификация с использованием базовой HTTP-аутентификации.

Обратите внимание, что весь трафик на наш веб-сервис направляется через защищенное соединение SSL. Таким образом, прослушивание паролей невозможно. Конечно, можно также выбрать HTTP-аутентификацию с дайджестом - см. этот интересный сайт для получения дополнительной информации об этом.

Но вернемся к нашему примеру:

//First, try authenticating against two predefined parameters in the HTTP 
//Request Header: 'Username' and 'Password'.

public static String authenticate(MessageContext mctx) {

     String s = "Login failed. Please provide a valid 'Username' and 'Password' in the HTTP header.";

    // Get username and password from the HTTP Header
    Map httpHeaders = (Map) mctx.get(MessageContext.HTTP_REQUEST_HEADERS);
    String username = null;
    String password = null;

    List userList = (List) httpHeaders.get("Username");
    List passList = (List) httpHeaders.get("Password");

    // first try our username/password header authentication
    if (CollectionUtils.isNotEmpty(userList)
            && CollectionUtils.isNotEmpty(passList)) {
        username = userList.get(0).toString();
        password = passList.get(0).toString();
    }

    // No username found - try HTTP basic authentication
    if (username == null) {
        List auth = (List) httpHeaders.get("Authorization");
        if (CollectionUtils.isNotEmpty(auth)) {
            String[] authArray = authorizeBasic(auth.get(0).toString());
            if (authArray != null) {
                username = authArray[0];
                password = authArray[1];
            }
        }
    }

    if (username != null && password != null) {

        try {
            // Perform the authentication - e.g. against credentials from a DB, Realm or other
            return authenticate(username, password);
        } catch (Exception e) {
            LOG.error(e);
            return s;
        }

    }
    return s;
}


/**
 * return username and password for basic authentication
 * 
 * @param authorizeString
 * @return
 */
public static String[] authorizeBasic(String authorizeString) {

    if (authorizeString != null) {
        StringTokenizer st = new StringTokenizer(authorizeString);
        if (st.hasMoreTokens()) {
            String basic = st.nextToken();
            if (basic.equalsIgnoreCase("Basic")) {
                String credentials = st.nextToken();
                String userPass = new String(
                        Base64.decodeBase64(credentials.getBytes()));
                String[] userPassArray = userPass.split(":");
                if (userPassArray != null && userPassArray.length == 2) {
                    String userId = userPassArray[0];
                    String userPassword = userPassArray[1];
                    return new String[] { userId, userPassword };
                }

            }
        }
    }

    return null;

}

Первая проверка подлинности с использованием наших предопределенных параметров «Имя пользователя» и «Пароль» особенно полезна для наших тестировщиков интеграции, которые используют SOAP-UI (Хотя я не совсем уверен, что нельзя получить к работать SOAP-UI с HTTP Basic Authentication тоже). Затем при второй попытке аутентификации используются параметры, предоставляемые базовой аутентификацией HTTP.

Чтобы перехватывать каждый вызов веб-службы, мы определяем обработчик для каждой конечной точки:

@HandlerChain(file = "../../../../../handlers.xml")
@SchemaValidation(handler = SchemaValidationErrorHandler.class)
public class DeliveryEndpointImpl implements DeliveryEndpoint {

Файл handler.xml выглядит следующим образом:

<handler-chains xmlns="http://java.sun.com/xml/ns/javaee" 
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
                xsi:schemaLocation="http://java.sun.com/xml/ns/javaee">

     <handler-chain>
        <handler>
            <handler-name>AuthenticationHandler</handler-name>
            <handler-class>mywebservice.handler.AuthenticationHandler</handler-class>
        </handler>
    </handler-chain>
</handler-chains>

Как видите, обработчик указывает на AuthenticationHandler, который перехватывает каждый вызов конечной точки веб-службы. Вот обработчик аутентификации:

public class AuthenticationHandler implements SOAPHandler<SOAPMessageContext> {

    /**
     * Logger
     */
    public static final Log log = LogFactory
            .getLog(AuthenticationHandler.class);

    /**
     * The method is used to handle all incoming messages and to authenticate
     * the user
     * 
     * @param context
     *            The message context which is used to retrieve the username and
     *            the password
     * @return True if the method was successfully handled and if the request
     *         may be forwarded to the respective handling methods. False if the
     *         request may not be further processed.
     */
    @Override
    public boolean handleMessage(SOAPMessageContext context) {

        // Only inbound messages must be authenticated
        boolean isOutbound = (Boolean) context
                .get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);

        if (!isOutbound) {
            // Authenticate the call
            String s = EbsUtils.authenticate(context);
            if (s != null) {
                log.info("Call to Web Service operation failed due to wrong user credentials. Error details: "
                        + s);

                // Return a fault with an access denied error code (101)
                generateSOAPErrMessage(
                        context.getMessage(),
                        ServiceErrorCodes.ACCESS_DENIED,
                        ServiceErrorCodes
                                .getErrorCodeDescription(ServiceErrorCodes.ACCESS_DENIED),
                        s);

                return false;
            }

        }

        return true;
    }

    /**
     * Generate a SOAP error message
     * 
     * @param msg
     *            The SOAP message
     * @param code
     *            The error code
     * @param reason
     *            The reason for the error
     */
    private void generateSOAPErrMessage(SOAPMessage msg, String code,
            String reason, String detail) {
        try {
            SOAPBody soapBody = msg.getSOAPPart().getEnvelope().getBody();
            SOAPFault soapFault = soapBody.addFault();
            soapFault.setFaultCode(code);
            soapFault.setFaultString(reason);

            // Manually crate a failure element in order to guarentee that this
            // authentication handler returns the same type of soap fault as the
            // rest
            // of the application
            QName failureElement = new QName(
                    "http://yournamespacehere.com", "Failure", "ns3");
            QName codeElement = new QName("Code");
            QName reasonElement = new QName("Reason");
            QName detailElement = new QName("Detail");

            soapFault.addDetail().addDetailEntry(failureElement)
                    .addChildElement(codeElement).addTextNode(code)
                    .getParentElement().addChildElement(reasonElement)
                    .addTextNode(reason).getParentElement()
                    .addChildElement(detailElement).addTextNode(detail);

            throw new SOAPFaultException(soapFault);
        } catch (SOAPException e) {
        }
    }

    /**
     * Handles faults
     */
    @Override
    public boolean handleFault(SOAPMessageContext context) {
        // do nothing
        return false;
    }

    /**
     * Close - not used
     */
    @Override
    public void close(MessageContext context) {
        // do nothing

    }

    /**
     * Get headers - not used
     */
    @Override
    public Set<QName> getHeaders() {
        return null;
    }

}

В AuthenticationHandler мы вызываем метод authenticate (), определенный выше. Обратите внимание, что мы создаем ошибку SOAP вручную, которая называется «Отказ», на случай, если с аутентификацией что-то пойдет не так.

4 голосов
/ 20 января 2009

Basic WS-Security будет работать как с Java, так и с PHP-клиентами (среди прочих), подключенными к JAAS, для обеспечения базы данных. Как реализовать этот вид зависит от вашего контейнера. Аннотируйте методы веб-службы с помощью аннотации @RolesAllowed, чтобы контролировать, какие роли должен выполнять вызывающий пользователь. Все контейнеры J2EE будут предоставлять некоторый механизм для указания того, против каких пользователей области JAAS должна проходить аутентификация. Например, в Glassfish вы можете использовать консоль администратора для управления сферами, пользователями и группами. В своем application.xml вы затем указываете область и сопоставления группы и роли.

Здесь приведены некоторые подробности того, как этого добиться на Glassfish

С JBoss WS в JBoss это еще проще.

Какую реализацию JAX-WS вы используете и в каком контейнере?

1 голос
/ 24 июня 2009

Есть ли способ, независимый от текущего контейнера? Я хотел бы определить, какой класс отвечает за авторизацию. Этот класс может вызывать базу данных или иметь пароль в другом месте.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...