Java Web服务客户端基本身份验证

55

我在Glassfish上创建了一个JAX-WS Web服务,需要基本的HTTP身份验证。

现在我想为该Web服务创建一个独立的Java应用程序客户端,但我不知道如何传递用户名和密码。

它可以使用Eclipse的Web服务浏览器,并且通过检查wire(数据流)我发现了以下内容:

POST /SnaProvisioning/SnaProvisioningV1_0 HTTP/1.1
Host: localhost:8080
Content-Type: text/xml; charset=utf-8
Content-Length: 311
Accept: application/soap+xml, application/dime, multipart/related, text/*
User-Agent: IBM Web Services Explorer
Cache-Control: no-cache
Pragma: no-cache
SOAPAction: ""
Authorization: Basic Z2VybWFuOmdlcm1hbg==
Connection: close

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:q0="http://ngin.ericsson.com/sna/types/v1.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soapenv:Body>
    <q0:listServiceScripts/>
  </soapenv:Body>
</soapenv:Envelope>

如何使用Java代码在“Authorization”头部传递用户名和密码?它是否进行了哈希处理或类似的操作?算法是什么?

没有涉及安全性的情况下,我有一个可工作的独立Java客户端:

SnaProvisioning myPort = new SnaProvisioning_Service().getSnaProvisioningV10Port();
myPort.listServiceScripts();
10个回答

90

JAX-WS 实现基本身份验证的方法是

Service s = new Service();
Port port = s.getPort();

BindingProvider prov = (BindingProvider)port;
prov.getRequestContext().put(BindingProvider.USERNAME_PROPERTY, "myusername");
prov.getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, "mypassword");

port.call();

1
我也感谢!要是没有你,我可能需要很长时间才能弄明白这个。 - Geert Schuring
1
对于那些处于1.5级别的人,您需要为BindingProvider添加此依赖项: <!-- https://mvnrepository.com/artifact/javax.xml/webservices-api --> <dependency> <groupId>javax.xml</groupId> <artifactId>webservices-api</artifactId> <version>1.5</version> </dependency> - boly38
1
非常好的回答。非常感谢。 - Dan Ortega

51

事实证明,有一种简单的、标准的方法可以实现我想要的目标:

import java.net.Authenticator;
import java.net.PasswordAuthentication;

Authenticator myAuth = new Authenticator() 
{
    @Override
    protected PasswordAuthentication getPasswordAuthentication()
    {
        return new PasswordAuthentication("german", "german".toCharArray());
    }
};

Authenticator.setDefault(myAuth);

没有自定义的"sun"类或外部依赖项,也没有手动编码任何内容。

我知道基本安全性不够安全,但我们也正在使用HTTPS。


19
请注意,Authenticator#setDefault 方法不是线程安全的。如果你的服务消费者需要访问多个端点,则这不是一个好的解决方案。 - Thomas Upton
Authenticator对于任何类型的安全协议都非常有用,但是JAX-WS提供凭据的方式不需要使用此方法。 - Jonathan Barbero
1
很遗憾,如果您的用户名是Unicode而不是Latin1,这个解决方案将无法工作 :( - Hubbitus
3
如果您想以编程方式后期更改凭据,则可能会面临与此缺陷/问题相关的问题,参考链接。如果仅使用基本身份验证保护服务端口,则最简单的解决方案是(再次)使用Jonathan的答案。如果WSDL本身受到保护,则只有在另一个线程中描述的丑陋解决方法。 - Hein Blöd
@Humza,很抱歉我不能再帮你了,因为我已经有一段时间没有接触这个主题了。但是我认为这个被接受的答案应该可以解决问题,或者那个线程中的其他答案也可以尝试一下(?)。 - Hein Blöd
显示剩余2条评论

10

对于 Axis2 客户端,这可能会有所帮助。

...
serviceStub = new TestBeanServiceStub("<WEB SERVICE URL>"); // Set your value
HttpTransportProperties.Authenticator basicAuthenticator = new HttpTransportProperties.Authenticator();
List<String> authSchemes = new ArrayList<String>();
authSchemes.add(Authenticator.BASIC);
basicAuthenticator.setAuthSchemes(authSchemes); 
basicAuthenticator.setUsername("<UserName>"); // Set your value
basicAuthenticator.setPassword("<Password>"); // Set your value
basicAuthenticator.setPreemptiveAuthentication(true);
serviceStub._getServiceClient().getOptions().setProperty(org.apache.axis2.transport.http.HTTPConstants.AUTHENTICATE, basicAuthenticator);
serviceStub._getServiceClient().getOptions().setProperty(org.apache.axis2.transport.http.HTTPConstants.CHUNKED, "false");
...

我不得不将代码行 authSchemes.add(Authenticator.BASIC); 更改为 authSchemes.add(HttpTransportProperties.Authenticator.BASIC); 才能使其编译通过。 - Casey Perkins
嗨,Avil,我的Axis2客户端中没有Stub类。抱歉,我是个初学者。我正在使用商业供应商的服务,并且客户端由供应商提供。我已经进行了wsimport,但没有stub类。因此,我无法执行_getServiceClient().getOptions()这一部分。我需要在执行wsimport时添加任何其他属性吗?请帮帮我。感谢您的帮助。 - Saravana Kumar

8

关于基本认证的一些额外说明:它由一个头部组成,其中包含键/值对:

Authorization: Basic Z2VybWFuOmdlcm1hbg==

其中 "Authorization" 是头部的键, 头部的值是一个字符串("Basic"单词加上空格)与"Z2VybWFuOmdlcm1hbg=="连接在一起,这是以双点连接的base64编码的用户名和密码

String name = "username";
String password = "secret";
String authString = name + ":" + password;
String authStringEnc = new BASE64Encoder().encode(authString.getBytes());
...
objectXXX.header("Authorization", "Basic " + authStringEnc);

6
如果您的客户端使用JAX-WS实现(例如Metro Web Services),则以下代码展示了如何在HTTP头中传递用户名和密码:
 MyService port = new MyService();
 MyServiceWS service = port.getMyServicePort();

 Map<String, List<String>> credentials = new HashMap<String,List<String>>();

 credentials.put("username", Collections.singletonList("username"));
 credentials.put("password", Collections.singletonList("password"));

 ((BindingProvider)service).getRequestContext().put(MessageContext.HTTP_REQUEST_HEADERS, credentials);

然后对服务的后续调用将会得到认证。请注意,密码仅使用Base64进行编码,因此我建议您使用其他附加机制(如客户端证书)来增加安全性。


这可以用于手动添加任意标头并设置任意值(通过手动计算凭据的base64编码),但有更多标准选项。 - German

5
这是我的解决方案:

这对我有用:

 BindingProvider bp = (BindingProvider) port;
 Map<String, Object> map = bp.getRequestContext();
 map.put(BindingProvider.USERNAME_PROPERTY, "aspbbo");
 map.put(BindingProvider.PASSWORD_PROPERTY, "9FFFN6P");

4
Please use English - quirimmo

1

1
Web服务已经运行了相当长的时间,添加安全性只需要在单个类中添加一个注释(@RolesAllowed),我认为没有比这更简单的事情了,特别是当我看到CXF需要大量配置时。此外,仅因为客户端尚无法发送一个标头而更改框架是不值得的。 - German
@German。从你的问题中并不清楚你在客户端使用了任何框架。看起来你自己的答案是赢家。 - Alexander Pogrebnyak
是的,也许我使用的框架不够清晰,但事实上我们没有任何jar包或依赖项或任何指向特定实现的东西,但我想如果我们正在使用Glassfish,那么我们就是在使用Metro。 - German

0

以下代码对我有效

        Map<String, Object> requestContext = ((BindingProvider)port).getRequestContext();
        Map<String, List<String>> requestHeaders = new HashMap<String, List<String>>();
        String authString = "<Userid>" + ":" + "<Pwd>";
        String authorization =  Base64.getEncoder().encodeToString(authString.getBytes());
        requestHeaders.put("Authorization", Collections.singletonList("Basic " + authorization));
        requestContext.put(MessageContext.HTTP_REQUEST_HEADERS, requestHeaders);

0

如果您使用JAX-WS,以下代码适用于我:

    //Get Web service Port
    WSTestService wsService = new WSTestService();
    WSTest wsPort = wsService.getWSTestPort();

    // Add username and password for Basic Authentication
    Map<String, Object> reqContext = ((BindingProvider) 
         wsPort).getRequestContext();
    reqContext.put(BindingProvider.USERNAME_PROPERTY, "username");
        reqContext.put(BindingProvider.PASSWORD_PROPERTY, "password");

0
最简单的方法是在请求的header中包含用户名和密码。请参见下面的示例。
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:typ="http://xml.demo.com/types" xmlns:ser="http://xml.demo.com/location/services"
    xmlns:typ1="http://xml.demo.com/location/types">
    <soapenv:Header>
        <typ:requestHeader>
            <typ:timestamp>?</typ:timestamp>
            <typ:sourceSystemId>TEST</typ:sourceSystemId>
            <!--Optional: -->
            <typ:sourceSystemUserId>1</typ:sourceSystemUserId>
            <typ:sourceServerId>1</typ:sourceServerId>
            <typ:trackingId>HYD-12345</typ:trackingId>
        </typ:requestHeader>

        <wsse:Security soapenv:mustUnderstand="1"
            xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
            <wsse:UsernameToken wsu:Id="UsernameToken-emmprepaid"
                xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
                <wsse:Username>your-username</wsse:Username>
                <wsse:Password
                    Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">your-password</wsse:Password>
            </wsse:UsernameToken>
        </wsse:Security>
    </soapenv:Header>
    <soapenv:Body>
        <ser:getLocation>
            <!--Optional: -->
            <ser:GetLocation>
                <typ1:locationID>HYD-GoldenTulipsEstates</typ1:locationID>
            </ser:GetLocation>
        </ser:getLocation>
    </soapenv:Body>
</soapenv:Envelope>

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