JAX-WS针对数据库的身份验证

5

我正在实现一个JAX-WS Web服务,将被外部Java和PHP客户端使用。

客户端必须使用存储在每个客户端数据库中的用户名和密码进行身份验证。

什么身份验证机制最好用于确保各种客户端都可以使用它?

3个回答

9
对于我们的Web服务身份验证,我们采用双重方法,以确保具有不同前提条件的客户端能够进行身份验证。
  • 在HTTP请求头中使用用户名和密码参数进行身份验证
  • 使用HTTP基本身份验证进行身份验证。
请注意,所有对我们的Web服务的流量都通过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的集成测试人员特别有用(尽管我不确定是否可以使用HTTP基本身份验证使SOAP-UI正常工作)。第二次身份验证尝试使用由HTTP基本身份验证提供的参数。

为了拦截每个对Web服务的调用,我们在每个端点上定义一个处理程序:

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

处理器的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,它拦截了对Web服务端点的每个调用。下面是Authentication Handler:

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()方法。请注意,如果身份验证出现问题,我们会手动创建一个名为“Failure”的SOAP故障。

这真的是一个详细而好的答案。我不知道关于为JAX-WS定义处理程序链的概念。太糟糕了,似乎没有办法手动调用容器实现的领域 - 因此,如果您使用基于表单的身份验证,您也必须以不同的方式进行操作。 - cljk

4
基本的WS-Security可以在Java和PHP客户端中使用(以及其他插入JAAS提供数据库后端的客户端)。如何实现这种功能取决于您的容器。使用@RolesAllowed注释来控制调用用户必须拥有哪些角色,对Web服务方法进行注释。所有J2EE容器都会提供一些机制来指定用户应该经过哪个JAAS领域进行身份验证。例如,在Glassfish中,您可以使用管理控制台来管理领域、用户和组。然后在您的application.xml中指定领域和组到角色映射。 这里是如何在Glassfish上实现此操作的详细信息。
在JBoss中使用JBoss WS更加简单。
您正在使用哪个JAX-WS实现和哪个容器?

1
请检查链接是否失效,如果有兴趣的话,请告诉我 :) - Daniel Szalay

1

有没有一种与当前容器无关的方法?我想定义哪个类负责授权。该类可以调用数据库或在其他地方存储密码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接