针对数据库的JAX-WS身份validation

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

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

最好使用哪种身份validation机制来确保misc客户端可以使用它?

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

以下是如何在Glassfish上实现此目的的一些细节

使用JBoss中的JBoss WS ,它更容易。

你使用什么JAX-WS实现以及在哪个容器中?

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

  • 使用HTTP请求标头中的用户名和密码参数进行身份validation
  • 使用HTTP基本身份validation进行身份验

请注意,我们的Web服务的所有流量都通过SSL安全连接进行路由。 因此,无法嗅探密码。 当然,也可以选择带有摘要的HTTP身份validation – 请参阅这个有趣的网站以获取更多相关信息。

但回到我们的例子:

//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 - eg 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; } 

使用我们预定义的“用户名”和“密码”参数的第一次身份validation对于使用SOAP-UI的集成测试人员特别有用(虽然我不能完全确定是否也无法使用HTTP基本身份validation来使用SOAP-UI )。 然后,第二次身份validation尝试使用HTTP基本身份validation提供的参数。

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

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

handler.xml看起来像:

    AuthenticationHandler mywebservice.handler.AuthenticationHandler    

如您所见,处理程序指向AuthenticationHandler,它拦截对Web Service端点的每次调用。 这是身份validation处理程序:

 public class AuthenticationHandler implements SOAPHandler { /** * 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 getHeaders() { return null; } } 

在AuthenticationHandler中,我们调用了authenticate()方法,如上所述。 请注意,如果身份validation出现问题,我们会创建一个名为“Failure”的手动SOAP故障。

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