Skip to content

RESTful API for Spring

Petr Dvořák edited this page Sep 23, 2017 · 16 revisions

This tutorial shows the way mobile API developers who build their applications on top of Spring framework can integrate with PowerAuth Server.

Prerequisites for the tutorial

  • 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, ...)

Add a Maven dependency

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>

Register Bouncy Castle Provider

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 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);
    }

}

Configure PowerAuth SOAP Service

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

}

Setting Up Credentials

(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).

Register PowerAuth Components

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 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.

Register a PowerAuth Application Configuration

(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
    }

}

Set Up Spring Security

(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);
    }

}

Verify signatures

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 PowerAuthAuthenticationException is automatically raised that is handled alongside other application exceptions, for example via @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")
    public @ResponseBody PowerAuthAPIResponse<String> login(PowerAuthApiAuthentication apiAuthentication) {

        String userId = apiAuthentication.getUserId(); // use userId if needed ...
        SecurityContextHolder.getContext().setAuthentication((Authentication) apiAuthentication);
        return new PowerAuthAPIResponse<String>("OK", "User " + userId);

    }

}

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 = "X-PowerAuth-Authorization", 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");
        }

    }

}

Use End-To-End Encryption

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 specialized chapter on end-to-end encryption to learn more about how end-to-end encryption works under the hood.

Non-Personalized End-To-End Encryption

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();
        }

    }

}

Deployment Tutorials

Integration Tutorials

Clone this wiki locally