-
Notifications
You must be signed in to change notification settings - Fork 4
RESTful API for Spring
PowerAuth RESTful Integration documentation has been moved to: https://developers.wultra.com/docs/develop/powerauth-restful-integration/RESTful-API-for-Spring
Please use the new developer portal to access documentation.
This tutorial shows the way mobile API developers who build their applications on top of Spring framework can integrate with PowerAuth Server.
- Running PowerAuth Server with available SOAP interface.
- Knowledge of Java EE applications based on Spring Framework.
- 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-spring</artifactId>
<version>${powerauth.version}</version>
</dependency>
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>${bouncycastle.version}</version>
</dependency>
Then, you can then register Bouncy Castle provider in your SpringBootServletInitializer
(or an equivalent class in case you do not use Spring Boot):
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
// Register BC provider
Security.addProvider(new BouncyCastleProvider());
// Tell PowerAuth components to use BC provider
PowerAuthConfiguration.INSTANCE.setKeyConvertor(CryptoProviderUtilFactory.getCryptoProviderUtils());
return application.sources(PowerAuthApiJavaApplication.class);
}
}
In order to connect to the correct PowerAuth Server, you need to add following configuration:
@Configuration
@ComponentScan(basePackages = {"io.getlime.security.powerauth"})
public class PowerAuthWebServiceConfiguration {
@Value("${powerauth.service.url}")
private String powerAuthServiceUrl;
@Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath("io.getlime.powerauth.soap");
return marshaller;
}
@Bean
public PowerAuthServiceClient powerAuthClient(Jaxb2Marshaller marshaller) {
PowerAuthServiceClient client = new PowerAuthServiceClient();
client.setDefaultUri(powerAuthServiceUrl);
client.setMarshaller(marshaller);
client.setUnmarshaller(marshaller);
return client;
}
}
(optional) In case PowerAuth Server uses a restricted access flag in the server configuration, you need to configure credentials for the WS-Security so that your client can connect to the SOAP service - modify your PowerAuthWebServiceConfiguration
to include Wss4jSecurityInterceptor
bean, like so:
@Value("${powerauth.service.security.clientToken}")
private String clientToken;
@Value("${powerauth.service.security.clientSecret}")
private String clientSecret;
@Bean
public Wss4jSecurityInterceptor securityInterceptor() {
Wss4jSecurityInterceptor wss4jSecurityInterceptor = new Wss4jSecurityInterceptor();
wss4jSecurityInterceptor.setSecurementActions("UsernameToken");
wss4jSecurityInterceptor.setSecurementUsername(clientToken);
wss4jSecurityInterceptor.setSecurementPassword(clientSecret);
wss4jSecurityInterceptor.setSecurementPasswordType(WSConstants.PW_TEXT);
return wss4jSecurityInterceptor;
}
// ...
@Bean
public PowerAuthServiceClient powerAuthClient(Jaxb2Marshaller marshaller) {
PowerAuthServiceClient client = new PowerAuthServiceClient();
client.setDefaultUri(powerAuthServiceUrl);
client.setMarshaller(marshaller);
client.setUnmarshaller(marshaller);
// ****
// HERE ==> Add interceptors for the security
// ****
ClientInterceptor interceptor = securityInterceptor();
client.setInterceptors(new ClientInterceptor[] { interceptor });
return client;
}
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).
As a part of the PowerAuth integration setup, you need to register following components by registering appropriate @Beans
and by adding these components to the Spring life-cycle in your WebMvcConfigurerAdapter
:
@Configuration
public class WebApplicationConfig extends WebMvcConfigurerAdapter {
@Bean
public PowerAuthWebArgumentResolver powerAuthWebArgumentResolver() {
return new PowerAuthWebArgumentResolver();
}
@Bean
public PowerAuthAnnotationInterceptor powerAuthInterceptor() {
return new PowerAuthAnnotationInterceptor();
}
@Bean
public FilterRegistrationBean powerAuthFilterRegistration () {
FilterRegistrationBean<PowerAuthRequestFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new PowerAuthRequestFilter());
registrationBean.setMatchAfter(true);
return registrationBean;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(powerAuthInterceptor());
super.addInterceptors(registry);
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(powerAuthWebArgumentResolver());
super.addArgumentResolvers(argumentResolvers);
}
}
PowerAuthWebArgumentResolver
bean is responsible for auto-injecting PowerAuth authentication objects into the controller handler methods (see example at "Verify signatures" chapter). You need to add it to argument resolver list.
PowerAuthInterceptor
bean is responsible for the @PowerAuth
annotation handling (see example at "Verify signatures" chapter). You need to add it to the interceptor registry.
And finally, the FilterRegistrationBean
(with the PowerAuthRequestFilter
filter) is a technical component that passes the HTTP request body as an attribute of HttpServletRequest
, so that it can be used for signature validation.
(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.
@Configuration
public class ApplicationConfiguration implements PowerAuthApplicationConfiguration {
@Override
public boolean isAllowedApplicationKey(String applicationKey) {
return true; // suggested default implementation
}
@Override
public Map<String, Object> statusServiceCustomObject() {
return null; // suggested default implementation
}
}
(optional)
Create a security configuration class SecurityConfig
extending WebSecurityConfigurerAdapter
. The configuration we will use:
- disable default Basic HTTP authentication
- disables CSRF (we don''t need it for REST)
- register your authentication entry point (if someone tries to visit our API without prior authentication, show error)
- secures all REST endpoints with
/secured/
prefix
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private PowerAuthApiAuthenticationEntryPoint apiAuthenticationEntryPoint;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/secured/**").fullyAuthenticated();
http.httpBasic().disable();
http.csrf().disable();
http.exceptionHandling().authenticationEntryPoint(apiAuthenticationEntryPoint);
}
}
This sample @Controller
implementation illustrates how to use @PowerAuth
annotation to verify that the request signature matches what is expected - in this case, to establish an authenticated session. In case the authentication is not successful, the PowerAuthApiAuthentication
object is null
. You may check for the null
value and raise PowerAuthAuthenticationException
that is handled alongside other application exceptions via default @ControllerAdvice
.
Note: Controllers that establish a session must not be on a context that is protected by Spring Security (for example "/secured/", in our example), otherwise context could never be reached and session will never be established.
@Controller
@RequestMapping(value = "session")
public class AuthenticationController {
@RequestMapping(value = "login", method = RequestMethod.POST)
@PowerAuth(resourceId = "/session/login")
@ResponseBody
public MyApiResponse login(PowerAuthApiAuthentication auth) {
if (auth != null) {
// use userId if needed ...
String userId = auth.getUserId();
// create authenticated session
SecurityContextHolder.getContext().setAuthentication((Authentication) auth);
// return OK response, ... or
return new MyApiResponse(Status.OK, userId);
} else {
// handle authentication failure
throw new PowerAuthAuthenticationException();
}
}
}
In case you need a more low-level access to the signature verification, you can verify the signature manually using the PowerAuthAuthenticationProvider
like this:
@Controller
@RequestMapping(value = "session")
public class AuthenticationController {
@Autowired
private PowerAuthAuthenticationProvider authenticationProvider;
@RequestMapping(value = "login", method = RequestMethod.POST)
public @ResponseBody PowerAuthAPIResponse<String> login(
@RequestHeader(value = PowerAuthSignatureHttpHeader.HEADER_NAME, required = true) String signatureHeader,
HttpServletRequest servletRequest) throws Exception {
PowerAuthApiAuthentication apiAuthentication = authenticationProvider.validateRequestSignature(
"POST",
"Any data".getBytes("UTF-8"),
"/session/login",
signatureHeader
);
if (apiAuthentication != null && apiAuthentication.getUserId() != null) {
SecurityContextHolder.getContext().setAuthentication((Authentication) apiAuthentication);
return new PowerAuthAPIResponse<String>("OK", "User " + userId);
} else {
throw new PowerAuthAuthenticationException("USER_NOT_AUTHENTICATED");
}
}
}
This sample @Controller
implementation illustrates how to use @PowerAuthToken
annotation to verify simple token based authentication headers. 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.
@Controller
@RequestMapping(value = "secure/account")
public class AuthenticationController {
@Autowired
private CustomService service;
@RequestMapping(value = "widget/balance", method = RequestMethod.GET)
@PowerAuthToken
public @ResponseBody PowerAuthAPIResponse<String> getBalance(PowerAuthApiAuthentication apiAuthentication) throws PowerAuthAuthenticationException {
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:
@Controller
@RequestMapping(value = "encrypted")
public class EncryptedController {
private EncryptorFactory encryptorFactory;
@Autowired
public void setEncryptorFactory(EncryptorFactory encryptorFactory) {
this.encryptorFactory = encryptorFactory;
}
@RequestMapping(value = "hello", method = RequestMethod.POST)
public @ResponseBody PowerAuthApiResponse<NonPersonalizedEncryptedPayloadModel> createNewActivation(@RequestBody 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