Wire-level security and users/roles security are related as follows. Under users/roles security, a client furnishes an identification such as a username or even a digital certificate together with a security credential that vouches for the identification (for instance, a password or a signature on the digital certificate from a certificate authority). To avoid hijacking, the identification and the credential should be sent from the client to the server through a secure channel, for instance, over an HTTPS connection. Wire-level security is thus the foundation upon which users/roles security should be implemented, and HTTPS is an ideal way to provide wire-level security for web-based systems such as web services.
Users/roles security is a two-phase process (see Figure 6-8). In the first and required phase, the user provides an identification and a credential that vouches for the identification. A successful user authentication phase results in an authenticated subject. In the optional second phase, role authorization, the access permissions of the authenticated subject can be refined as needed. For example, in a software development organization there might be a distinction between a senior engineer and a starting programmer, in that the former can access resources (for instance, sensitive records in a database) that the latter cannot access. This distinction could be implemented with different authorization roles.
At what level should users/roles security be enforced? Enforcement at the application level does not scale easily, in that every web service (or website) would require code, perhaps consolidated into a library, dedicated to security; a web service still would need to link to such library code. The preferred approach is to hand over the security concerns to the service container—to Tomcat or Jetty. This is container-managed security, which is considered best practice. Tomcat’s implementation of container-managed security, like its management of wire-level security, is unobtrusive at the service level: no changes are required in the web service code to enable users/roles security. Once again, the configuration document web.xml is the key.
The RESTful predictions2 service once again can be augmented with container-managed security—and without any change to the code. The revised web.xml document is displayed in Example 6-9.
Example 6-9. The revised web.xml to support both HTTPS and users/roles security
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
web
-
app
>
<
servlet
>
<
servlet
-
name
>
predictor
</
servlet
-
name
>
<
servlet
-
class
>
predictions2
.
PredictionsServlet
</
servlet
-
class
>
</
servlet
>
<
security
-
role
>
![]()
<
role
-
name
>
bigshot
</
role
-
name
>
![]()
<!--
other
roles
as
needed
-->
</
security
-
role
>
<
security
-
constraint
>
<
web
-
resource
-
collection
>
<
url
-
pattern
>/*</
url
-
pattern
>
</
web
-
resource
-
collection
>
<
auth
-
constraint
>
![]()
<
role
-
name
>
bigshot
</
role
-
name
>
</
auth
-
constraint
>
<
user
-
data
-
constraint
>
<
transport
-
guarantee
>
CONFIDENTIAL
</
transport
-
guarantee
>
</
user
-
data
-
constraint
>
</
security
-
constraint
>
<
login
-
config
>
![]()
<
auth
-
method
>
BASIC
</
auth
-
method
>
![]()
</
login
-
config
>
<
servlet
-
mapping
>
<
servlet
-
name
>
predictor
</
servlet
-
name
>
<
url
-
pattern
>/*</
url
-
pattern
>
</
servlet
-
mapping
>
</
web
-
app
>
The numbered lines in the revised web.xml need clarification.
security-role
, which is an authorization role, and
line 2 sets the role’s name to bigshot
. On the Tomcat side, a data store must contain the same
role name, with details to follow.
security-constraint
element, introduced earlier, now contains two
specific constraints: the user-data-constraint
, which enforces HTTPS transport, from the earlier
example; and the new auth-constraint
(line 3), which is an authorization rather than an
authentication constraint in the context of
users/roles security. The authorization constraint specifies that access to the predictions2
resource, the service and its operations, is restricted to a client authorized as a bigshot
.
The login-config
element (line 4) designates BASIC
as the
user authentication method (line 5). HTTP 1.1 supports four authentication types: BASIC, FORM,
DIGEST, and CLIENT-CERT. These four types were designed with websites in mind but are adaptable
to web services as well. Here is a summary of the differences:
The very simplicity of the BASIC type is attractive for clients against RESTful services, especially if BASIC authentication is combined with HTTPS transport, which then provides the required username/password encryption.
The revised web.xml document specifies the type of HTTP authentication in use, BASIC, as well as the
authorization role, bigshot
, required of the client that accesses the predictions2 service. The next question is how
Tomcat puts this security information to use, in other words, how Tomcat’s container-managed security
works under the hood. Tomcat implements container-managed security with realms, which are akin to
groups in Unix-type operating systems. In simplest form, a realm is a collection
of usernames and passwords together the authorization roles, if any, associated with the usernames. The purpose of a realm is
to coordinate various security resources in support of a single policy on access control. On the service side, security
information needs to be saved in a data store such as a relational database system; Tomcat realms provide the details
about how the security information is to be saved and accessed.
Tomcat7 comes with six standard plug-ins, all of which have Realm
in their names.
Developers are free to develop additional Realm
plug-ins.
Here are the six native Tomcat plug-ins with a short description of each:
DataSource
, which in turn is available through a JNDI (Java Naming and Directory Interface) lookup service.
UserDatabaseRealm
and remains as an option for backward compatibility.
Under any of these choices, it is the Tomcat container rather than the application that becomes the security provider. With respect
to the options, the path of
least resistance leads to the default, the UserDatabaseRealm
. Here is the data store, the XML file
tomcat-users.xml.
The five elements commented out act as Tomcat’s tutorial about how the file is to be used. My additions are lines
1 and 2. Line 1 declares the security role used in line 2, which specifies a username and an associated
password:
<
tomcat
-
users
>
<
role
rolename
=
"bigshot"
/>
![]()
<
user
username
=
"moe"
password
=
"MoeMoeMoe"
roles
=
"bigshot"
/>
![]()
</
tomcat
-
users
>
With the UserDatabaseRealm
now configured, the security process can be summarized as follows:
user
entry in the file tomcat-users.xml must
include bigshot
among the roles
.
On the service side, Tomcat is responsible for conducting the user authentication and role authorization.
The burden
now shifts to the client, which must properly format, within an HTTPS request, the username
and password information. On the service side, the required changes are limited to the
web service’s configuration file, web.xml, and to the Tomcat UserDatabaseRealm
file,
tomcat-users.xml. No code in the predictions2 service needs to change.
The PredictionsHttpsClientAA
(see Example 6-10) adds users/roles security
on the client side to the earlier HTTPS client against the predictions2 service. The changes
are quite small.
Example 6-10. The PredictionsHttpsClientAA
client against the predictions2 service
import
java.net.URL
;
import
javax.net.ssl.HttpsURLConnection
;
import
javax.net.ssl.SSLContext
;
import
java.security.KeyStore
;
import
javax.net.ssl.TrustManagerFactory
;
import
javax.net.ssl.X509TrustManager
;
import
javax.net.ssl.HostnameVerifier
;
import
javax.net.ssl.SSLSession
;
import
java.security.cert.X509Certificate
;
import
java.security.SecureRandom
;
import
java.io.FileInputStream
;
import
java.io.InputStream
;
import
java.io.OutputStream
;
import
java.io.ByteArrayOutputStream
;
import
org.apache.commons.codec.binary.Base64
;
public
class
PredictionsHttpsClientAA
{
private
static
final
String
endpoint
=
"https://localhost:8443/predictions2"
;
private
static
final
String
truststore
=
"test.keystore"
;
public
static
void
main
(
String
[
]
args
)
{
new
PredictionsHttpsClientAA
().
runTests
();
}
private
void
runTests
()
{
try
{
SSLContext
sslCtx
=
SSLContext
.
getInstance
(
"TLS"
);
char
[
]
password
=
"qubits"
.
toCharArray
();
KeyStore
ks
=
KeyStore
.
getInstance
(
"JKS"
);
FileInputStream
fis
=
new
FileInputStream
(
truststore
);
ks
.
load
(
fis
,
password
);
TrustManagerFactory
tmf
=
TrustManagerFactory
.
getInstance
(
"SunX509"
);
tmf
.
init
(
ks
);
// same as keystore
sslCtx
.
init
(
null
,
// not needed, not challenged
tmf
.
getTrustManagers
(),
new
SecureRandom
());
HttpsURLConnection
.
setDefaultSSLSocketFactory
(
sslCtx
.
getSocketFactory
());
// Proof of concept tests.
String
uname
=
"moe"
;
String
passwd
=
"MoeMoeMoe"
;
getTest
(
uname
,
passwd
);
postTest
(
uname
,
passwd
);
getTestAll
(
uname
,
passwd
);
// confirm POST test
deleteTest
(
uname
,
passwd
,
"31"
);
getTestAll
(
uname
,
passwd
);
// confirm DELETE test
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
e
);
}
}
private
HttpsURLConnection
getConnection
(
URL
url
,
String
verb
,
String
uname
,
String
passwd
)
{
try
{
HttpsURLConnection
conn
=
(
HttpsURLConnection
)
url
.
openConnection
();
conn
.
setDoInput
(
true
);
conn
.
setDoOutput
(
true
);
conn
.
setRequestMethod
(
verb
);
// authentication (although header name is Authorization)
String
userpass
=
uname
+
":"
+
passwd
;
String
basicAuth
=
"Basic "
+
new
String
(
new
Base64
().
encode
(
userpass
.
getBytes
()));
conn
.
setRequestProperty
(
"Authorization"
,
basicAuth
);
conn
.
setHostnameVerifier
(
new
HostnameVerifier
()
{
public
boolean
verify
(
String
host
,
SSLSession
session
)
{
return
host
.
equals
(
"localhost"
);
// for development
}
});
return
conn
;
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
e
);
}
}
private
void
getTest
(
String
uname
,
String
passwd
)
{
getTestAll
(
uname
,
passwd
);
getTestOne
(
uname
,
passwd
,
"31"
);
}
private
void
getTestAll
(
String
uname
,
String
passwd
)
{
try
{
URL
url
=
new
URL
(
endpoint
);
HttpsURLConnection
conn
=
getConnection
(
url
,
"GET"
,
uname
,
passwd
);
conn
.
connect
();
readResponse
(
"GET all request:\n"
,
conn
);
conn
.
disconnect
();
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
e
);
}
}
private
void
getTestOne
(
String
uname
,
String
passwd
,
String
id
)
{
try
{
URL
url
=
new
URL
(
endpoint
+
"?id="
+
id
);
HttpsURLConnection
conn
=
getConnection
(
url
,
"GET"
,
uname
,
passwd
);
conn
.
connect
();
readResponse
(
"GET request for "
+
id
+
":\n"
,
conn
);
conn
.
disconnect
();
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
e
);
}
}
private
void
postTest
(
String
uname
,
String
passwd
)
{
try
{
URL
url
=
new
URL
(
endpoint
);
HttpsURLConnection
conn
=
getConnection
(
url
,
"POST"
,
uname
,
passwd
);
conn
.
connect
();
writeBody
(
conn
);
readResponse
(
"POST request:\n"
,
conn
);
conn
.
disconnect
();
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
e
);
}
}
private
void
deleteTest
(
String
uname
,
String
passwd
,
String
id
)
{
try
{
URL
url
=
new
URL
(
endpoint
+
"?id="
+
id
);
HttpsURLConnection
conn
=
getConnection
(
url
,
"DELETE"
,
uname
,
passwd
);
conn
.
connect
();
readResponse
(
"DELETE request:\n"
,
conn
);
conn
.
disconnect
();
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
e
);
}
}
private
void
writeBody
(
HttpsURLConnection
conn
)
{
try
{
String
pairs
=
"who=Freddy&what=Avoid Friday nights if possible."
;
OutputStream
out
=
conn
.
getOutputStream
();
out
.
write
(
pairs
.
getBytes
());
out
.
flush
();
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
e
);
}
}
private
void
readResponse
(
String
msg
,
HttpsURLConnection
conn
)
{
try
{
byte
[
]
buffer
=
new
byte
[
4096
];
InputStream
in
=
conn
.
getInputStream
();
ByteArrayOutputStream
out
=
new
ByteArrayOutputStream
();
int
n
=
0
;
while
((
n
=
in
.
read
(
buffer
))
!=
-
1
)
out
.
write
(
buffer
,
0
,
n
);
in
.
close
();
System
.
out
.
println
(
new
String
(
out
.
toByteArray
()));
// stringify and print
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
e
);
}
}
}
The getConnection
method has three newlines:
String
userpass
=
uname
+
":"
+
passwd
;
![]()
String
basicAuth
=
"Basic "
+
new
String
(
new
Base64
().
encode
(
userpass
.
getBytes
()));
![]()
conn
.
setRequestProperty
(
"Authorization"
,
basicAuth
);
A userpass
string is created as a key/value pair, with the colon, :
, as the separator,
from the parameters uname
and passwd
(line 1). The userpass
is then encoded in base64
and has Basic
prepended (line 2). The result is inserted into the HTTPS headers, with
Authorization
as the key. For moe
as the username and MoeMoeMoe
as the password, the
resulting header is:
Authorization:
Basic
bW9lOk1vZU1vZU1vZQ
==
This setup follows HTTP 1.1 guidelines and meets Tomcat expectations about how the authentication/authorization information is to be formatted in the HTTPS request. As usual, a client against a RESTful service needs to stay close to the HTTP/HTTPS metal.
The curl utility is an alternative to a full-blown RESTful client written in Java or some other language. For the predictions2 service accessible through HTTPS and with user-authentication/role authorization in play, this curl command sends a GET request:
%
curl
--
verbose
--
insecure
--
user
moe:
MoeMoeMoe
\
https:
//localhost:8443/predictions2
The --insecure
flag means that curl goes through handshake process but does not verify
the digital certificates sent from the server; the verification would require that curl
be pointed to the appropriate truststore file. In any case, the output from a sample run, edited
slightly for readability, is shown in Example 6-11.
Example 6-11. The output from a curl request over HTTPS
*
About
to
connect
()
to
localhost
port
8443
(
#
0
)
*
Trying
::
1
...
connected
*
Connected
to
localhost
(::
1
)
port
8443
(
#
0
)
*
successfully
set
certificate
verify
locations:
*
CAfile:
none
CApath:
/
etc
/
ssl
/
certs
*
SSLv3
,
TLS
handshake
,
Client
hello
(
1
):
*
SSLv3
,
TLS
handshake
,
Server
hello
(
2
):
*
SSLv3
,
TLS
handshake
,
CERT
(
11
):
*
SSLv3
,
TLS
handshake
,
Server
key
exchange
(
12
):
*
SSLv3
,
TLS
handshake
,
Server
finished
(
14
):
*
SSLv3
,
TLS
handshake
,
Client
key
exchange
(
16
):
*
SSLv3
,
TLS
change
cipher
,
Client
hello
(
1
):
*
SSLv3
,
TLS
handshake
,
Finished
(
20
):
*
SSLv3
,
TLS
change
cipher
,
Client
hello
(
1
):
*
SSLv3
,
TLS
handshake
,
Finished
(
20
):
*
SSL
connection
using
EDH
-
RSA
-
DES
-
CBC3
-
SHA
*
Server
certificate:
...
*
SSL
certificate
verify
result:
self
signed
certificate
(
18
),
![]()
continuing
anyway
.
*
Server
auth
using
Basic
with
user
'
moe
'
>
GET
/
predictions2
HTTP
/
1.1
>
Authorization:
Basic
bW9lOk1vZU1vZU1vZQ
==
>
User
-
Agent:
curl
/
7.19
.
7
(
x86_64
-
pc
-
linux
-
gnu
)
libcurl
/
7.19
.
7
OpenSSL
/
0.9
.
8
k
zlib
/
1.2
.
3.3
libidn
/
1.15
>
Host:
localhost:
8443
>
Accept:
*/*
>
<
HTTP
/
1.1
200
OK
<
Server:
Apache
-
Coyote
/
1.1
<
Cache
-
Control:
private
<
Expires:
Wed
,
31
Dec
1969
18
:
00
:
00
CST
<
Transfer
-
Encoding:
chunked
...
<
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
java
version
=
"1.6.0_21"
class
=
"java.beans.XMLDecoder"
>
...
In the curl output, the character >
introduces text lines sent from curl to the server, whereas
the character <
introduces text lines from from the server to curl. The lines that begin
with a star, *
, trace the TLS handshake process. Although curl recognizes (line 1) that the
self-signed certificate from the server is worthless as a security credential, curl continues the
process, again because of the
--insecure
flag, by sending a GET request over HTTPS to the predictions2 service; the
service responds with a list of the predictions.
Tomcat supports HTTPS transport and users/roles security for SOAP-based services as well. A SOAP-based client built atop wsimport-generated artifacts can use a slightly higher level API than its REST-style counterpart to insert the required security credentials into an HTTPS request. This section uses a minimal SOAP-based service to focus on security in the client against the service.
The SOAP-based TempConvert service (see Example 6-12) has two operations: f2c converts temperatures from fahrenheit to centigrade and c2f converts them from centigrade to fahrenheit. With respect to security, the web.xml for this service is essentially the same as for the RESTful and secure predictions2 service.
Example 6-12. The SOAP-based TempConvert service
package
tc
;
import
javax.jws.WebService
;
import
javax.jws.WebMethod
;
@WebService
public
class
TempConvert
{
@WebMethod
public
float
c2f
(
float
t
)
{
return
32.0f
+
(
t
*
9.0f
/
5.0f
);
}
@WebMethod
public
float
f2c
(
float
t
)
{
return
(
5.0f
/
9.0f
)
*
(
t
-
32.0f
);
}
}
However, the web.xml for the SOAP-based service needs to reference the Metro
WSServlet
(line 2), which acts as the intermediary between the servlet container and the service (see
Example 6-13); the
additional configuration file, sun-jaxws.xml, is likewise required.
Example 6-13. The web.xml document for the SOAP-based TempConvert service
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
web
-
app
>
<
listener
>
<
listener
-
class
>
com
.
sun
.
xml
.
ws
.
transport
.
http
.
servlet
.
WSServletContextListener
![]()
</
listener
-
class
>
</
listener
>
<
servlet
>
<
servlet
-
name
>
wsservlet
</
servlet
-
name
>
<
servlet
-
class
>
com
.
sun
.
xml
.
ws
.
transport
.
http
.
servlet
.
WSServlet
![]()
</
servlet
-
class
>
</
servlet
>
<
security
-
role
>
<
role
-
name
>
bigshot
</
role
-
name
>
<!--
other
roles
as
needed
-->
</
security
-
role
>
<
security
-
constraint
>
<
web
-
resource
-
collection
>
<
url
-
pattern
>/*</
url
-
pattern
>
</
web
-
resource
-
collection
>
<
auth
-
constraint
>
<
role
-
name
>
bigshot
</
role
-
name
>
</
auth
-
constraint
>
<
user
-
data
-
constraint
>
<
transport
-
guarantee
>
CONFIDENTIAL
</
transport
-
guarantee
>
</
user
-
data
-
constraint
>
</
security
-
constraint
>
<
login
-
config
>
<
auth
-
method
>
BASIC
</
auth
-
method
>
</
login
-
config
>
<
servlet
-
mapping
>
<
servlet
-
name
>
wsservlet
</
servlet
-
name
>
<
url
-
pattern
>/*</
url
-
pattern
>
</
servlet
-
mapping
>
</
web
-
app
>
Lines 1 and 2 are the only changes to the web.xml used in the earlier predictions2 service.
With the web.xml and sun-jaxws.xml in place, the TempConvert
service can be
deployed in the usual way:
%
ant
deploy
-
Dwar
.
name
=
tc
How should the wsimport-generated artifacts be generated for a service accessible only through HTTPS? The attempt:
%
wsimport
-
p
tcClient
-
keep
https:
//localhost:8443/tc?wsdl
generates a sun.security.validator.ValidatorException
precisely because wsimport is unable to
conduct the HTTPS handshake: the utility does not have access to a truststore against which the
server’s digital certificate(s) can be verified.
The service is HTTPS-secured and, therefore, so is the service’s dynamically
generated WSDL. The wsgen utility provides a workaround. The command:
%
wsgen
-
cp
.
tc
.
TempConvert
-
wsdl
generates the TempConvertService.wsdl file and the TempConvertService_schema1.xsd file. The wsimport utility can now be targeted at the WSDL:
%
wsmport
-
p
tcClient
-
keep
TempConvertService
.
wsdl
The only drawback is that the service’s URL is not in the class TempConvertService
because the WSDL used is not generated dynamically. The TempConvertClient
(see Example 6-14)
shows how to overcome this drawback.
Example 6-14. The TempConvertClient
against the SOAP-based TempConvert service
import
tcClient.TempConvertService
;
import
tcClient.TempConvert
;
import
javax.xml.ws.BindingProvider
;
import
java.util.Map
;
import
javax.net.ssl.HostnameVerifier
;
import
javax.net.ssl.SSLSession
;
import
javax.net.ssl.SSLContext
;
import
javax.net.ssl.TrustManager
;
import
javax.net.ssl.X509TrustManager
;
import
javax.net.ssl.HttpsURLConnection
;
import
java.security.cert.Certificate
;
import
java.security.cert.X509Certificate
;
public
class
TempConvertClient
{
private
static
final
String
endpoint
=
"https://localhost:8443/tc"
;
// Make the client "trusting" and handle the hostname verification.
static
{
![]()
HttpsURLConnection
.
setDefaultHostnameVerifier
(
new
HostnameVerifier
()
{
public
boolean
verify
(
String
name
,
SSLSession
session
)
{
return
true
;
// allow everything
}
});
try
{
TrustManager
[
]
trustMgr
=
new
TrustManager
[
]
{
new
X509TrustManager
()
{
public
X509Certificate
[
]
getAcceptedIssuers
()
{
return
null
;
}
public
void
checkClientTrusted
(
X509Certificate
[
]
cs
,
String
t
)
{
}
public
void
checkServerTrusted
(
X509Certificate
[
]
cs
,
String
t
)
{
}
}
};
SSLContext
sslCtx
=
SSLContext
.
getInstance
(
"TLS"
);
sslCtx
.
init
(
null
,
trustMgr
,
null
);
HttpsURLConnection
.
setDefaultSSLSocketFactory
(
sslCtx
.
getSocketFactory
());
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
e
);
}
}
public
static
void
main
(
String
args
[
])
{
if
(
args
.
length
<
2
)
{
System
.
err
.
println
(
"Usage: TempConvertClient <uname> <passwd>"
);
return
;
}
String
uname
=
args
[
0
];
String
passwd
=
args
[
1
];
TempConvertService
service
=
new
TempConvertService
();
TempConvert
port
=
service
.
getTempConvertPort
();
BindingProvider
prov
=
(
BindingProvider
)
port
;
![]()
prov
.
getRequestContext
().
put
(
BindingProvider
.
ENDPOINT_ADDRESS_PROPERTY
,
endpoint
);
![]()
prov
.
getRequestContext
().
put
(
BindingProvider
.
USERNAME_PROPERTY
,
uname
);
![]()
prov
.
getRequestContext
().
put
(
BindingProvider
.
PASSWORD_PROPERTY
,
passwd
);
![]()
System
.
out
.
println
(
"f2c(-40.1) = "
+
port
.
f2C
(-
40.1f
));
System
.
out
.
println
(
"c2f(-40.1) = "
+
port
.
c2F
(-
40.1f
));
System
.
out
.
println
(
"f2c(+98.7) = "
+
port
.
f2C
(+
98.7f
));
}
}
The TempConvertClient
uses a static
block (line 1) to make itself into a trusting client
that does not check the server’s digital certificate during the HTTPS handshake; the static
block also instructs the HostnameVerifier
to allow client access to any host,
including localhost. The static
block isolates the transport-level security
so that the focus can be kept on the users/roles security. By the way, the static
block exploits the fact that
a JAX-WS client uses, under the hood, the HttpsURLConnection
of earlier RESTful examples.
To gain access to the transport level, in particular to the headers in the HTTPS request, the
TempConvertClient
casts the port
reference to a BindingProvider
(line 2). The endpoint
then is set (line 3) to the correct URL because the wsimport-generated classes do not have
a usable URL. The username and password, entered as command-line arguments,
are likewise placed in the HTTPS headers (lines 4 and 5). This SOAP-based client need not bother with creating a
single string out of the username and password or with encoding these in base64. Instead, the client
uses the intuitive:
BindingProvider
.
USERNAME_PROPERTY
BindingProvider
.
PASSWORD_PROPERTY
keys and sets the value for each. After the setup, the client makes three calls against the SOAP-based service. The output is:
f2c
(-
40.1
)
=
-
40.055557
c2f
(-
40.1
)
=
-
40.18
f2c
(+
98.7
)
=
37.055557
A downside of BASIC authentication is that a client’s password must be stored, as is, on the server side so that the received password can be compared against the stored password. The DIGEST option requires only that the hash value of the password be stored on the server. The setup for the DIGEST option is trickier than for the BASIC option, however. Yet the BASIC option can be tweaked so that it behaves just like the DIGEST option. This section illustrates.
Tomcat comes with a digest utility: digest.sh for Unixy systems and digest.bat for Windows. The command:
%
digest
.
sh
-
a
SHA
MoeMoeMoe
generates a 20-byte hash value, in hex, using the SHA-1 algorithm. Here is the value:
0
f9e52090a322d7f788db2ae6b603e8efbd7fbd1
In the TOMCAT_HOME/conf/tomcat-users.xml file, this value replaces the password for moe
(line 1):
<?
xml
version
=
'
1.0
'
encoding
=
'
utf
-
8
'
?>
<
tomcat
-
users
>
<
role
rolename
=
"bigshot"
/>
<
user
username
=
"moe"
password
=
"0f9e52090a322d7f788db2ae6b603e8efbd7fbd1"
![]()
roles
=
"bigshot"
/>
</
tomcat
-
users
>
The file is otherwise unchanged.
The digest utility is implemented with the RealmBase.Digest
method, which can be used in a Java client. The
revised client against the TempConvertService
, named TempConvertClient2
(see Example 6-15), illustrates.
Example 6-15. The revised TempConvertClient2
import
tcClient.TempConvertService
;
import
tcClient.TempConvert
;
...
import
org.apache.catalina.realm.RealmBase
;
![]()
public
class
TempConvertClient2
{
private
static
final
String
endpoint
=
"https://localhost:8443/tc"
;
static
{
...
}
public
static
void
main
(
String
args
[
])
{
if
(
args
.
length
<
2
)
{
System
.
err
.
println
(
"Usage: TempConvertClient <uname> <passwd>"
);
return
;
}
String
uname
=
args
[
0
];
String
passwd
=
args
[
1
];
String
passwdHash
=
RealmBase
.
Digest
(
passwd
,
// password
![]()
"SHA"
,
// algorithm
null
);
// default encoding: utf-8
TempConvertService
service
=
new
TempConvertService
();
TempConvert
port
=
service
.
getTempConvertPort
();
BindingProvider
prov
=
(
BindingProvider
)
port
;
prov
.
getRequestContext
().
put
(
BindingProvider
.
ENDPOINT_ADDRESS_PROPERTY
,
endpoint
);
prov
.
getRequestContext
().
put
(
BindingProvider
.
USERNAME_PROPERTY
,
uname
);
prov
.
getRequestContext
().
put
(
BindingProvider
.
PASSWORD_PROPERTY
,
passwdHash
);
![]()
...
}
}
Most of the code in the TempConvertClient2
client is the same as that in the original.
The import
in line 1 is the first difference: the Tomcat libraries include the RealmBase
class
whose Digest
method is of interest (line 2). The Digest
method generates the hash value for the
sample password, in this case MoeMoeMoe
, which is given as a command-line argument. The hash value
instead of the actual password then is placed in the HTTPS headers (line 3). On the service side,
the send hash value is compared against the hash value stored in the revised tomcat-users.xml. The
ZIP with the sample code includes runClient.xml, an Ant script to compile and execute the
TempConvertClient2
. A sample invocation with output is:
%
ant
-
f
runClient
.
xml
-
Darg1
=
moe
-
Darg2
=
MoeMoeMoe
Buildfile:
run
.
xml
compile:
run:
[
java
]
f2c
(-
40.1
)
=
-
40.055557
[
java
]
c2f
(-
40.1
)
=
-
40.18
[
java
]
f2c
(+
98.7
)
=
37.055557