OAuth implementation using Spring Security

OAuth 2.0 is a way of securing APIs. Spring Security provides Spring Cloud Security and Spring Cloud OAuth2 components for implementing the rant flows we discussed above.

We'll create one more service, security-service, which will control authentication and authorization.

Create a new Maven project and follow these steps:

  1. Add the Spring Security and Spring Security OAuth2 dependencies in pom.xml:
     <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-security</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>
  2. Use the @EnableResourceServer annotation in your application class. This will allow this application to work as a resource server. @EnableAuthorizationServer is another annotation we will use to enable the authorization server as per OAuth 2.0 specifications:
    @SpringBootApplication
    @RestController
    @EnableResourceServer
    public class SecurityApp {
    
        @RequestMapping("/user")
        public Principal user(Principal user) {
            return user;
        }
    
        public static void main(String[] args) {
            SpringApplication.run(SecurityApp.class, args);
        }
    
        @Configuration
        @EnableAuthorizationServer
        protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {
    
            @Autowired
            private AuthenticationManager authenticationManager;
    
            @Override
            public void configure(AuthorizationServerEndpointsConfigurer endpointsConfigurer) throws Exception {
                endpointsConfigurer.authenticationManager(authenticationManager);
            }
    
            @Override
            public void configure(ClientDetailsServiceConfigurer clientDetailsServiceConfigurer) throws Exception {
      // Using hardcoded inmemory mechanism because it is just an example
                clientDetailsServiceConfigurer.inMemory()
                 .withClient("acme")
                 .secret("acmesecret")
                 .authorizedGrantTypes("authorization_code", "refresh_token", "implicit", "password", "client_credentials")
                 .scopes("webshop");
            }
        }
    }
  3. Update the security-service configuration in application.yml, as shown in the following code:
    • server.contextPath: It denotes the context path.
    • security.user.password: We'll use the hardcoded password for this demonstration. You can re-configure it for real use:
      application.yml
      info:
          component:
              Security Server
      
      server:
          port: 9001
          ssl:
              key-store: classpath:keystore.jks
              key-store-password: password
              key-password: password
          contextPath: /auth
      
      security:
          user:
              password: password
      
      logging:
          level:
              org.springframework.security: DEBUG

Now we have our security server in place, we'll expose our APIs using the new microservice api-service, which will be used for communicating with external applications and UIs.

Create a new Maven project and follow these steps:

  1. Add the Spring Security and Spring Security OAuth2 dependencies in pom.xml:
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-undertow</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
    <dependency>
      <groupId>com.packtpub.mmj</groupId>
      <artifactId>online-table-reservation-common</artifactId>
      <version>PACKT-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-security</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-eureka</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-hystrix</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-bus-amqp</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpclient</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <!-- Testing starter -->
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
  2. Use the @EnableResourceServer annotation in your application class. This will allow this application to work as a resource server:
    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableCircuitBreaker
    @EnableResourceServer
    @ComponentScan({"com.packtpub.mmj.api.service", "com.packtpub.mmj.common"})
    public class ApiApp {
    
        private static final Logger LOG = LoggerFactory.getLogger(ApiApp.class);
    
        static {
            // for localhost testing only
            LOG.warn("Will now disable hostname check in SSL, only to be used during development");
            HttpsURLConnection.setDefaultHostnameVerifier((hostname, sslSession) -> true);
        }
    
        @Value("${app.rabbitmq.host:localhost}")
        String rabbitMqHost;
    
        @Bean
        public ConnectionFactory connectionFactory() {
            LOG.info("Create RabbitMqCF for host: {}", rabbitMqHost);
            CachingConnectionFactory connectionFactory = new CachingConnectionFactory(rabbitMqHost);
            return connectionFactory;
        }
    
        public static void main(String[] args) {
            LOG.info("Register MDCHystrixConcurrencyStrategy");
            HystrixPlugins.getInstance().registerConcurrencyStrategy(new MDCHystrixConcurrencyStrategy());
            SpringApplication.run(ApiApp.class, args);
        }
    }
  3. Update the api-service configuration in application.yml, as shown in the following code:
    • security.oauth2.resource.userInfoUri: It denotes the security service user URI.
      application.yml
      info:
        component: API Service
      
      spring:
        application:
            name: api-service
        aop:
            proxyTargetClass: true
      
      server:
        port: 7771
      
      security:
        oauth2:
          resource:
           userInfoUri: https://localhost:9001/auth/user
      
      management:
        security:
          enabled: false
      ## Other properties like Eureka, Logging and so on

Now we have our security server in place, we'll expose our APIs using the new microservice api-service, which will be used for communicating with external applications and UIs.

Now let's test and explore how it works for different OAuth 2.0 grant types.

Note

We'll make use of the postman extension to the Chrome browser to test the different flows.

Authorization code grant

We will enter the following URL in our browser. A request for authorization code is as follows:

https://localhost:9001/auth/oauth/authorize?response_type=code&client_id=client&redirect_uri=http://localhost:7771/1&scope=apiAccess&state=1234

Here, we provide the client ID (hardcoded client is by default we have registered in our security service), redirect URI, scope (hardcoded value apiAccess in security service) and state. You must be wondering about the state parameter. It contains the random number that we re-validate in response to prevent cross site request forgery.

If the resource owner (user) is not already authenticated, it will ask for the user name and password. Provide user as the username and password as the password; we have hardcoded these values in security service.

Once the login is successful, it will ask to provide your (resource owner) approval:

Authorization code grant

OAuth 2.0 authorization code grant – resource grant approval

Select Approve and click on Authorize. This action will redirect the application to http://localhost:7771/1?code=o8t4fi&state=1234.

As you can see, it has returned the authorization code and state.

Now, we'll use this code to retrieve the access code. We'll use the postman Chrome extension. First we'll add the authorization header using Username as client and Password as clientsecret, as shown in the following screenshot:

Authorization code grant

OAuth 2.0 authorization code grant – access token request – adding the authentication

This will add the Authorization header to the request with the value Basic Y2xpZW50OmNsaWVudHNlY3JldA==.

Now, we'll add a few other parameters to the request, as shown in the following screenshot, and then submit the request:

Authorization code grant

OAuth 2.0 authorization code grant – access token request and response

This returns the following response, as per the OAuth 2.0 specification:

{
  "access_token": "6a233475-a5db-476d-8e31-d0aeb2d003e9",
  "token_type": "bearer",
  "refresh_token": "8d91b9be-7f2b-44d5-b14b-dbbdccd848b8",
  "expires_in": 43199,
  "scope": "apiAccess"
}

Now we can use this information to access the resources owned by the resource owner. For example, if https://localhost:8765/api/restaurant/1 represents the restaurant with the ID of 1, then it should return the respective restaurant details.

Without the access token, if we enter the URL, it returns the error Unauthorized, with the message Full authentication is required to access this resource.

Now, let's access this URL with the access token, as shown in the following screenshot:

Authorization code grant

OAuth 2.0 authorization code grant – using the access token for API access

As you can see, we have added the Authorization header with the access token.

Now, we will explore implicit grant implementation.

Implicit grant

Implicit grants are very similar to authorization code grants, except for the code grant step. If you remove the first step—the code grant step (where the client application receives the authorization token from the authorization server)—from the authorization code grant, the rest of the steps are the same. Let's check it out.

Enter the following URL and parameters in the browser and press Enter. Also, make sure to add basic authentication, with client as the username and password as the password if asked:

https://localhost:9001/auth/oauth/authorize?response_type=token&redirect_uri=https://localhost:8765&scope=apiAccess&state=553344&client_id=client

Here, we are calling the authorization endpoint with the following request parameters: Response type, client ID, redirect URI, scope, and state.

When the request is successful, the browser will be redirected to the following URL with new request parameters and values:

https://localhost:8765/#access_token=6a233475-a5db-476d-8e31-d0aeb2d003e9&token_type=bearer&state=553344&expires_in=19592

Here, we receive the access_token, token_type, state, and expiry duration for the token. Now, we can make use of this access token to access the APIs, as used in the authorization code grant.

Resource owner password credential grant

In this grant, we provide the username and password as parameters when requesting the access token, along with the grant_type, client, and scope parameters. We also need to use the client ID and secret to authenticate the request. These grant flows use client applications in place of browsers, and are normally used in mobile and desktop apps.

In the following postman tool screenshot, the authorization header has already been added using basic authentication with client_id and password:

Resource owner password credential grant

OAuth 2.0 resource owner password credentials grant – access token request and response

Once the access token is received by the client, it can be used in a similar way to how it is used in the authorization code grant.

Client credentials grant

In this flow, the client provides their own credentials and retrieves the access token. It does not use the resource owner's credentials and permissions.

As you can see in the following screenshot, we directly enter the token endpoint with only two parameters: grant_type and scope. The authorization header is added using client_id and client secret:

Client credentials grant

OAuth 2.0 client credentials grant – access token request and response

You can use the access token similarly as it is explained for the authorization code grant.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset