-
Notifications
You must be signed in to change notification settings - Fork 4
RESTful API for JavaEE
PowerAuth RESTful Integration documentation has been moved to: https://developers.wultra.com/docs/develop/powerauth-restful-integration/RESTful-API-for-JavaEE
Please use the new developer portal to access documentation.
This tutorial shows the way mobile API developers who build their applications with JAX-RS integrate with PowerAuth Server.
- Running PowerAuth Server with available SOAP interface.
- Knowledge of Java EE applications based on JAX-RS.
- Software: IDE - Spring Tool Suite, Java EE Application Server (Pivotal Server, Tomcat, ...)
To add PowerAuth support in your RESTful API, add Maven dependency for PowerAuth RESTful Security module in your pom.xml
file:
<dependency>
<groupId>io.getlime.security</groupId>
<artifactId>powerauth-restful-security-javaee</artifactId>
<version>${powerauth.version}</version>
</dependency>
To read about PowerAuth project Maven modules, visit Maven modules documentation.
This step is technically required only in case your server uses end-to-end encryption, but performing it anyway will not cause any harm. First, make sure you include Bouncy Castle libraries in your dependencies:
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-ext-jdk15on</artifactId>
<version>1.55</version>
</dependency>
Then, you can then register Bouncy Castle provider in your Application
class (or an equivalent class in case you use Jersey or some similar technology):
@ApplicationPath("/")
public class JavaEEApplication extends Application {
public JavaEEApplication() {
super();
// Register BC provider
Security.addProvider(new BouncyCastleProvider());
// Tell PowerAuth components to use BC provider
PowerAuthConfiguration.INSTANCE.setKeyConvertor(CryptoProviderUtilFactory.getCryptoProviderUtils());
}
@Override
public Set<Class<?>> getClasses() {
// ... see more information below
return resources;
}
}
In order to connect to the correct PowerAuth Server, you need to add a producer that configures SOAP service endpoint and default application configuration.
@Dependent
public class PowerAuthBeanFactory {
@Produces
public PowerAuthServiceClient buildClient() {
try {
return new PowerAuthServiceClient("http://localhost:8080/powerauth-java-server/soap");
} catch (AxisFault axisFault) {
return null;
}
}
@Produces
public PowerAuthApplicationConfiguration buildApplicationConfiguration() {
return new DefaultApplicationConfiguration();
}
}
// TODO: Describe SOAP client WS-Security configuration
Note: For SOAP interface, PowerAuth Server uses WS-Security, UsernameToken
validation (plain text password). The RESTful interface is secured using Basic HTTP Authentication (pre-emptive).
In order to automatically use resources, exception resolvers and filters, you need to register them in your application. For plain JAX-RS application, this is how to do it:
@ApplicationPath("/")
public class JavaEEApplication extends Application {
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> resources = new HashSet<>();
// Your resources
// ...
// ...
// PowerAuth Controllers
resources.add(ActivationController.class);
resources.add(SecureVaultController.class);
// PowerAuth Exception Resolvers
resources.add(PowerAuthActivationExceptionResolver.class);
resources.add(PowerAuthAuthenticationExceptionResolver.class);
resources.add(PowerAuthSecureVaultExceptionResolver.class);
// PowerAuth Filters
resources.add(PowerAuthRequestFilter.class);
return resources;
}
}
Note that Jersey uses ResourceConfig
subclass for a similar purpose...
(optional)
PowerAuth uses the concept of application ID
and application secret
. While applicationId
attribute is transmitted with requests in X-PowerAuth-Authorization
header, applicationSecret
is shared implicitly between client and server and is a part of the actual signature value. Applications are a first class citizen in PowerAuth protocol. Intermediate application, however, may influence which applications are accepted by implementing following configuration.
public class ApplicationConfiguration implements PowerAuthApplicationConfiguration {
@Override
public boolean isAllowedApplicationKey(String applicationKey) {
return true; // default implementation
}
@Override
public Map<String, Object> statusServiceCustomObject() {
return null; // default implementation
}
}
You can then return instance of this class in the producer method mentioned above, instead of DefaultApplicationConfiguration
instance.
In order to validate request signatures, you need to:
- inject a
HttpServletRequest
instance using the@Context
annotation - inject a
PowerAuthAuthenticationProvider
instance - add
@HeaderParam(value = PowerAuthSignatureHttpHeader.HEADER_NAME) String authHeader
in resource methods
Then, you can process the header and request using the authentication provider.
Here is the source code example:
@Path("pa/signature")
@Produces(MediaType.APPLICATION_JSON)
public class AuthenticationController {
@Context
private HttpServletRequest request;
@Inject
private PowerAuthAuthenticationProvider authenticationProvider;
@POST
@Path("validate")
@Consumes("*/*")
@Produces(MediaType.APPLICATION_JSON)
public PowerAuthApiResponse<String> login(String body, @HeaderParam(value = PowerAuthSignatureHttpHeader.HEADER_NAME) String authHeader) throws PowerAuthAuthenticationException {
// ##EXAMPLE: Here, we could store the authentication in the session like this:
// ##EXAMPLE: SecurityContextHolder.getContext().setAuthentication(apiAuthentication);
// ##EXAMPLE: ... or you can grab a user ID like this and use it for querying back-end:
// ##EXAMPLE: String userId = apiAuthentication.getUserId();
PowerAuthApiAuthentication auth = authenticationProvider.validateRequestSignature(
request,
"/pa/signature/validate",
authHeader
);
if (auth != null && auth.getUserId() != null) {
return new PowerAuthApiResponse<>("OK", "Hooray! User: " + auth.getUserId());
} else {
return new PowerAuthApiResponse<>("ERROR", "Authentication failed.");
}
}
}
This sample resource implementation illustrates how to use simple token based authentication. In case the authentication is not successful, the PowerAuthApiAuthentication
object is null.
Please note that token based authentication should be used only for endpoints with lower sensitivity, such as simplified account information for widgets or smart watch, that are also not prone to replay attack.
@Path("secure/account")
@Produces(MediaType.APPLICATION_JSON)
public class AuthenticationController {
@Inject
private PowerAuthAuthenticationProvider authenticationProvider;
@Inject
private CustomService service;
@POST
@Path("widget/balance")
@Consumes("*/*")
@Produces(MediaType.APPLICATION_JSON)
public PowerAuthApiResponse<String> getBalance(@HeaderParam(value = PowerAuthTokenHttpHeader.HEADER_NAME) String tokenHeader) throws PowerAuthAuthenticationException {
PowerAuthApiAuthentication auth = authenticationProvider.validateToken(tokenHeader);
if (apiAuthentication == null) {
throw new PowerAuthAuthenticationException();
} else {
String userId = apiAuthentication.getUserId();
String balance = service.getBalanceForUser(userId);
return new PowerAuthAPIResponse<String>("OK", balance);
}
}
}
You can use end-to-end encryption to add an additional encryption layer on top of the basic HTTPS encryption to protect the request body contents better.
End-to-end encryption provided by PowerAuth uses POST
method for all data transport and it requires predefined request / response structure. See a specialized chapter on End-To-End Encryption to learn more about how end-to-end encryption works under the hood.
To use non-personalized (application specific) encryption, use following pattern:
@Path("pa/custom/activation")
@Produces(MediaType.APPLICATION_JSON)
public class EncryptedController {
@Inject
private EncryptorFactory encryptorFactory;
@POST
@Path("create")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public PowerAuthApiResponse<NonPersonalizedEncryptedPayloadModel> createNewActivation( PowerAuthApiRequest<NonPersonalizedEncryptedPayloadModel> encryptedRequest) throws PowerAuthActivationException {
try {
// Prepare an encryptor
final PowerAuthNonPersonalizedEncryptor encryptor = encryptorFactory.buildNonPersonalizedEncryptor(encryptedRequest);
if (encryptor == null) {
throw new EncryptionException("Unable to initialize encryptor.");
}
// Decrypt the request object
OriginalRequest request = encryptor.decrypt(object, OriginalRequest.class);
if (request == null) {
throw new EncryptionException("Unable to decrypt request object.");
}
// ... do your business logic with OriginalRequest instance
// Create original response object
OriginalResponse response = new OriginalResponse();
response.setAttribute1("attribute1");
response.setAttribute2("attribute2");
response.setAttribute3("attribute3");
// Encrypt response object
final PowerAuthApiResponse<NonPersonalizedEncryptedPayloadModel> encryptedResponse = encryptor.encrypt(response);
if (encryptedResponse == null) {
throw new EncryptionException("Unable to encrypt response object.");
}
// Return response
return encryptedResponse;
} catch (IOException e) {
throw new PowerAuthActivationException();
}
}
}
If you need any assistance, do not hesitate to drop us a line at [email protected].
Deployment Tutorials
Integration Tutorials