2008-12-18 68 views
4

我正在实现将由外部Java和PHP客户端使用的JAX-WS webservice。针对数据库的JAX-WS身份验证

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

什么认证机制最好用来确保misc客户端可以使用它?

回答

3

基本的WS-Security可以与Java和PHP客户端(以及其他)一起插入JAAS以提供数据库后端。如何实现这种取决于你的容器。使用@RolesAllowed批注注释您的Web服务方法,以控制主叫用户必须拥有的角色。所有J2EE容器都将提供一些机制来指定哪些J​​AAS领域用户应该进行身份验证。例如,在Glassfish中,您可以使用管理控制台来管理领域,用户和组。然后在application.xml中指定领域和组到角色映射。

Here是如何实现这一Glassfish上

随着JBoss的JBoss WS一些细节,那就更简单了。

您正在使用哪种JAX-WS实现以及哪个容器?

+1

请检查链接,因为他们已经死了。我会感兴趣:) – 2011-09-23 11:59:20

1

有没有独立于当前容器的方式?我想定义哪个类负责授权。该类可以调用数据库或在其他地方使用密码。

8

对于我们的Web服务身份验证,我们正在采取双重方法,以确保具有不同先决条件的客户端能够进行身份验证。

  • 身份验证使用用户名和密码参数在HTTP请求报头使用HTTP基本认证
  • 验证。

请注意,所有到我们Web服务的流量均通过SSL安全连接进行路由。因此,嗅探密码是不可能的。当然,也可以选择带摘要的HTTP身份验证 - 请参阅this interesting site了解更多信息。

但是,回到我们的例子:

//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 { 

的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,它截取对Web Service端点的每个调用。这里的认证处理程序:

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错误。

+0

这真的是一个详细和良好的答案。我不知道为JAX-WS定义处理程序链的概念。太糟糕了,似乎没有办法手动调用实现的Realm容器 - 所以如果您使用基于表单的身份验证,也必须以不同的方式执行此操作。 – cljk 2013-06-08 12:20:41

相关问题