Quick Start; Spring Security 5 OAuth2 Login

Standard

Social logins using Oauth2 have become a industry standard. It revolutionized the way sites share data and has allowed users to quickly access new applications without having to create a new set of credentials. This article gives an example of why OAuth2 was invented as well as provides a working example of Spring Security 5 application integrated with Google.

The source code for this tutorial is available on Github.

But what did sites do before OAuth2?

Before OAuth2, sites did some pretty scary things to get users data from other applications. For example; requiring your login credentials to get your contacts from another application. The classic example of this is Yelp in 2008 where they would ask for your login credentials to gather your contacts from MSN, Yahoo, AOL, and Gmail. This was a huge security risk because it meant you were giving your password to Yelp, and they were promising they wouldn’t do anything bad to your account.

Example of Yelp before OAuth2

But how is OAuth2 going to solve this problem?

Have you ever created an account on a site using your Facebook, Google, or Microsoft account? These are examples of OAuth2 client providers. Using one of these providers we can register our application to allow user sign ins with their existing email accounts, while at the same time not compromising the user. Essentially we are offhanding all the authentication to the client provider.

With Spring Security 5 this process could not be more simple to implement. In this quickstart tutorial we will see how to quickly setup a spring boot app to use Spring Security 5 to authenticate users with Google.

Begin by creating a new Spring Boot project. Because we want this to have a restful interface I am including the “Spring Web” dependencies and of course the “Spring Security” dependency as this gives us the OAuth2 client libraries. I am using version 2.2.6.RELEASE for the example;

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!-- oauth2 -->
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-oauth2-client</artifactId>
	<version>5.3.0.RELEASE</version>
</dependency>
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-oauth2-jose</artifactId>
	<version>5.3.0.RELEASE</version>
</dependency>

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

I also include swagger dependencies. Swagger is a tool that lets you document your api endpoints. It comes with a nice user interface, and you will see us using this later on to test restful endpoints.

<dependency>
	<groupId>io.springfox</groupId>
	<artifactId>springfox-swagger2</artifactId>
	<version>2.9.2</version>
</dependency>
<dependency>
	<groupId>io.springfox</groupId>
	<artifactId>springfox-swagger-ui</artifactId>
	<version>2.9.2</version>
</dependency>

Creating an app in Google Cloud

Now that we have all our dependencies setup we must register our application within the provider (Google Cloud); Google OAuth2 implementation follows the OpenID Connect 1.0 specification which is an identity layer added to the protocol and allows clients to verify the identity of the end-user based on the authentication performed by the authorization server. It provides basic profile information about the end-user.

To authenticate users you must create a new application within the Google Cloud Platform. If you don’t have an account yet, quickly sign up for one. Then navigate to the “API & Services” -> “Credentials” section to generate “OAuth 2.0 Client Ids”. For this example I am making this an internal api, which only allowing users within my organization to authenticate. I have also set the authorized redirect URI of “http://localhost:5000/login/oauth2/code/google”.

Registering a new Oauth client within Google cloud

After creation you should receive an appId and secret key.

Using the newly generated clientId create a Oauth2LoginConfig class within our app.

@Configuration
public class OAuth2LoginConfig {

    @EnableWebSecurity
    public static class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests(authorize -> authorize.anyRequest().authenticated())
                    .oauth2Login(Customizer.withDefaults());
        }
    }

    @Bean
    public ClientRegistrationRepository clientRegistrationRepository() {
        return new InMemoryClientRegistrationRepository(this.googleClientRegistration());
    }

    @Bean
    public OAuth2AuthorizedClientService authorizedClientService(
            ClientRegistrationRepository clientRegistrationRepository) {
        return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
    }

    @Bean
    public OAuth2AuthorizedClientRepository authorizedClientRepository(
            OAuth2AuthorizedClientService authorizedClientService) {
        return new AuthenticatedPrincipalOAuth2AuthorizedClientRepository(authorizedClientService);
    }

    private ClientRegistration googleClientRegistration() {
        return CommonOAuth2Provider.GOOGLE.getBuilder("google")
                .clientId("INSERT_APP_ID")
                .clientSecret("INSERT_CLIENT_ID")
                .scope("email",
                        "profile",
                        "openid",
                        "https://www.googleapis.com/auth/user.addresses.read",
                        "https://www.googleapis.com/auth/user.phonenumbers.read",
                        "https://www.googleapis.com/auth/user.birthday.read",
                        "https://www.googleapis.com/auth/user.emails.read")
                .build();
    }
}

This class configures a does several things;

  • Creates a WebSecurityConfigurerAdapter which secures all restful endpoints.
  • Registers a ClientRegistrationRepository and Oauth2AuthorizedClientService both using there respective in memory services / respositories. This means that the app will store all user data in memory once logged in.
  • The highlight of this class is the google client registration at the bottom. Using the api key and api secret from our google cloud account update the missing variables. This DSL also configures scopes which we will request from Google. The user must approve the scopes to give us access to there profile information

This configuration uses a DSL setup, but you can easily configure the google client registration using a yml file as well.

What is DSL?

DSL stands for domain specific language or also referred to as a fluent interface based on the builder pattern. You can see this by how the chaining together configuration options for the “http security” and “client registration”.

Creating a secure api

At this point the application is ready to run. But we want to see the results of our authenication with Google. Create a simple SecureController and output the results of the OAuth2User within the spring application.

@RestController
public class SecureController {

    @ApiOperation(
            value = "Get the current logged-in user",
            notes = "This example only returns the user logged in from Google")
    @GetMapping("/")
    public HashMap<String, Object> index(@ApiIgnore @RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient,
                                         @ApiIgnore @AuthenticationPrincipal OAuth2User oauth2User) {
        HashMap<String, Object> results = new HashMap<>();
        results.put("username", oauth2User.getName());
        results.put("attributes", oauth2User.getAttributes());
        results.put("authorities", oauth2User.getAuthorities());
        results.put("clientScopes", authorizedClient.getClientRegistration().getScopes());
        results.put("clientName", authorizedClient.getClientRegistration().getClientName());
        return results;
    }
}

What is this RegisteredOAuth2AuthorizedClient and AuthenticationPrincipal?

The RegisteredOAuth2AuthorizedClient annotation resolves to a Oauth2AuthorizedClient which we have registered in our application using the OAuth2LoginConfig class above. In this case it will be Google, which it will resolve to our registered client that has our public and private key. The Oauth2AuthorizedClient also contains the access token, refresh token, and client registration with the user scopes we have requested.

The AuthenticationPrincipal holds our currently authenticated user within the application. It has attributes pretaining to the OpenId 1.0 specification such as name, profile, picture, email, birthdate, etc. which are all consider standard claims.

Swagger Config

Finally, create a SwaggerConfig class to register our rest controller location

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.sixthpoint.spring.security.oauth2login.controller"))
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())
                .build();
    }
}

Running the application

Launch the Spring Boot 2.0 app and go to http://localhost:8080/swagger-ui.html. You are then redirected to a Google for authentication.

After you have authenticated with your google account credentials, the next page will ask you to consent to your app having access to the Oauth Client registered in your Google Cloud account. Clicking allow will authorize our spring boot app to access the users email and basic profile information as identified in our scopes from the ClientRegistration.

Once consent has been granted you will be redirected back to the swagger-ui page. Execute the secure controller get request to see your authenticate user.

The application currently sets a cookie for an authorized user using a default session called JSESSIONID. This session is tied to our authenicated user within the application. If I were to restart the application the in-memory store is lost. Meaning I would have to log back into the application again.

Wrap Up

Today this article showed how to quickly get up and running with Spring Security 5 OAuth2. The app integrates with Google to allow for a secure authentication and consent of users with a google account. The applications API was secured using a session token which is generated using the Spring Security 5.3 OAuth2 libraries.

Source code can be found on Github.

Optimizing your application using Spring Boot 2.x and Amazon ElastiCache for Redis

Standard

Has your project gotten to the point when big data sets and or time consuming calculations begin to affect performance? Struggling to optimize your queries and need to cache some information to avoid continually hitting your database? Then caching could be your solution.

For this article I will demonstrate how to utilize Amazon ElastiCache for Redis to speed up areas of your application. The exmple application we will build uses Spring Boot 2.x and is available on Github.

What is Amazon ElastiCache for Redis?

ElastiCache for Redis is a super fast, in memory, key-value store database. It supports many different kinds of abstract data structures, such as strings, lists, maps, sets, sorted sets, hyperloglogs, bitmaps and spatial indexes. At its highest level, ElastiCache for Redis is an fully managed service for a standard Redis installation and uses all the standard Redis API’s. Which means the app we are building can rely not only on the Amazon ElastiCache flavor of Redis, but any other environment matching the version of your choosing.

Setting up ElastiCache for Redis

Begin by navigating to the ElasticCache dashboard, selecting Redis, and create a new cluster. You will be prompted to define a cache name, description, node type (server size), and number of replicates.

I filled in the name, description, and changed the node type to a smaller instance.

VPC & Security Groups

To be able to access your Redis cluster the instance(s) running our app must have be in the same Virtual Private Network (VPC) and contain the proper security groups. Your EC2 instances must allow the port of your redis cluster (6379) to be able to communicate. By default your Redis cluster is only accessible internally from the VPC selected. This is done purposely, as no internet gateway should be connected as it would defeat the purpose of a high efficiency, in-memory cache that Redis provides. 

Our app will only be able to access Redis once it is deployed to AWS.

If you wish to run the app locally, consider installing Redis using docker. The variables outlined in our application.properties file below can be modified to run locally.

Launching your Redis Cluster

Once your have properly configured your security groups and VPC, click “create”. ElastiCache will now provision and launch you new Redis cluster. When the status turns to available the cluster is ready to handle connections.

We need the primary endpoint for our new spring boot application.

Building our application

For this example application we will be using Spring Boot 2.x with the Spring-Data-Redis and Jedis (client library for redis). I first begin by importing them into my project

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>

These  libraries allow us to setup our caching config in Spring. A important concept to understand is that the Spring Framework provides its own abstraction for transparently adding caching. You do not only have to use Redis, the abstraction provides a list of providers; CouchbaseEhCache 2.xHazelcast, etc. As you will see adding caching to a service method is as simple as providing the appropriate annotation.

Now that we have included the required libraries in our pom file, Spring will try to autoconfigure a RedisCacheManager. I personally do not like magic behind the scenes so we are going to setup and configure our own annotation based RedisCacheManager. 

A RedisCacheManager is how we configure, and build a cacheManger for Spring to use. Notice that I have defined the redisHostName, redisPort, and redisPrefix for the Jedis (client library for redis) to use to connect to our cluster.

@Configuration
@EnableCaching
public class RedisConfig {

@Value("${redis.hostname}")
private String redisHostName;

@Value("${redis.port}")
private int redisPort;

@Value("${redis.prefix}")
private String redisPrefix;

@Bean
JedisConnectionFactory jedisConnectionFactory() {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(redisHostName, redisPort);
return new JedisConnectionFactory(redisStandaloneConfiguration);
}

@Bean(value = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}

@Primary
@Bean(name = "cacheManager") // Default cache manager is infinite
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
return RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(RedisCacheConfiguration.defaultCacheConfig().prefixKeysWith(redisPrefix)).build();
}

@Bean(name = "cacheManager1Hour")
public CacheManager cacheManager1Hour(RedisConnectionFactory redisConnectionFactory) {
Duration expiration = Duration.ofHours(1);
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig().prefixKeysWith(redisPrefix).entryTtl(expiration)).build();
}
}

I have defined 2 cache managers. One that is infinite (default), and once that will cause all keys to expire in 1 hour called “cacheManager1Hour” .

The cluster information is passed in from the applications.properties:

redis.hostname=URL_TO_ELASTIC_CACHE_REDIS_CLUSTER
redis.port=6379
redis.prefix=testing

Implementing a simple service

Now that our Redis cluster is configured in Spring, annotation based caching is enabled. Let’s assume you have a long running task that takes 2 seconds to do its work. By annotation the service with @Cacheable the result of the method call will be cached. I have given this cachable a value of “getLongRunningTaskResult” which will be used in its compound key, a key (by default is generated for you), and a cacheManager (“cacheManager1Hour” configured above).

@Cacheable(value = "getLongRunningTaskResult", key="{#seconds}", cacheManager = "cacheManager1Hour")
public Optional<TaskDTO> getLongRunningTaskResult(long seconds) {
try {
long randomMultiplier = new Random().nextLong();
long calculatedResult = randomMultiplier * seconds;
TaskDTO taskDTO = new TaskDTO();
taskDTO.setCalculatedResult(calculatedResult);
Thread.sleep(2000); // 2 Second Delay to Simulate Workload
return Optional.of(taskDTO);
} catch (InterruptedException e) {
return Optional.of(null);
}
}

Note: It is important that the resulting object of the method is serializable otherwise the cacheManager will not be able to cache the result.

Testing for performance improvements

To easy test the API, I have included swagger-ui which make it simple for developers to interact with the api we have built. I have also created a few simple endpoints to create, and flush the cache.

@ApiOperation(
value = "Perform the long running task")
@RequestMapping(value = "/{seconds}", method = RequestMethod.GET)
public ResponseEntity<TaskDTO> longRunningTask(@PathVariable long seconds) {
Optional<TaskDTO> user = taskService.getLongRunningTaskResult(seconds);
return ResponseUtil.wrapOrNotFound(user);
}

@ApiOperation(
value = "Resets the cache for a key")
@RequestMapping(value = "/reset/{seconds}", method = RequestMethod.DELETE)
public ResponseEntity<?> reset(@PathVariable long seconds) {
taskService.resetLongRunningTaskResult(seconds);
return new ResponseEntity<>(HttpStatus.ACCEPTED);
}

Once you deploy your app to EC2 navigate to URL path: /swagger-ui.html

From this GUI you can easily test your API performance improvements. Calling the GET endpoint for the first time should take roughly 2 seconds to return the new calculated result. Calling it subsequently will return an almost instant response as the long running task is now cached in Redis.

Final thoughts

Today’s applications demand a responsive user experience, design your queries and calculations to be as performant as possible, but every once in awhile when you can sacrifice real time data, just cache it.

Full project code is available at: https://github.com/sixthpoint/spring-boot-elasticache-redis-tutorial