Back to Home

Spring Boot - OAuth2 and the JWT / OTUS Blog. Online education

jwt · oauth2 · spring boot

Spring Boot - OAuth2 and JWT

Original author: tutorialspoint.com
  • Transfer
Salute, friends! Tomorrow will start the first classes in the new thread of the course "Developer on Spring Framework" . In this regard, we traditionally share useful material on the topic.



In this article, we will explore the use of OAuth2 and JWT in conjunction with Spring Boot and Spring Security.

Authorization Server


An Authorization Server is the most important component in the Web API security architecture. The authorization server acts as a single authorization point and allows your applications and HTTP endpoints to define the functions of your application.

Resource Server


The authorization server provides clients with an access token to access the HTTP Endpoints of the Resource Server. A resource server is a collection of libraries that contains HTTP Endpoints, static resources, and dynamic web pages.

OAuth2


OAuth2 is an authorization protocol that allows a client (third party) to access the resources of your application. To build an OAuth2 application, we need to know the Grant Type (authorization code), Client ID, and Client secret.

JWT Token


A JWT token is a JSON Web Token. It is used to represent secure identification information (claims) between two parties. For more information about JWT-token you can find on the site www.jwt.io .

We are going to make an OAuth2 application using JWT tokens, which will include an authorization server and a resource server.

First, we need to add the dependencies to our build file.

Maven users can add the following dependencies to pom.xml.

org.springframework.bootspring-boot-starter-jdbcorg.springframework.bootspring-boot-starter-securityorg.springframework.bootspring-boot-starter-weborg.springframework.security.oauthspring-security-oauth2org.springframework.securityspring-security-jwtcom.h2databaseh2org.springframework.bootspring-boot-starter-testtestorg.springframework.securityspring-security-testtest

Translator's note - for java older than 9, you must also add the following dependencies:

javax.xml.bindjaxb-api2.3.0com.sun.xml.bindjaxb-impl2.2.11com.sun.xml.bindjaxb-core2.2.11

Gradle users can add the following dependencies to a file build.gradle.

compile('org.springframework.boot:spring-boot-starter-security')
compile('org.springframework.boot:spring-boot-starter-web')
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile('org.springframework.security:spring-security-test')
compile("org.springframework.security.oauth:spring-security-oauth2")
compile('org.springframework.security:spring-security-jwt')
compile("org.springframework.boot:spring-boot-starter-jdbc")
compile("com.h2database:h2:1.4.191") 

Where,

  • Spring Boot Starter Security - implements Spring Security
  • Spring Security OAuth2 - implements OAUTH2 structures for the operation of the authorization server and resource server.
  • Spring Security JWT - Generates JWT Tokens
  • Spring Boot Starter JDBC - database access for user verification.
  • Spring Boot Starter Web - Provides HTTP Endpoints.
  • H2 Database - stores user information for authentication and authorization.

The full file is pom.xmlgiven below.

4.0.0com.tutorialspointwebsecurityapp0.0.1-SNAPSHOTjarwebsecurityappDemo project for Spring Bootorg.springframework.bootspring-boot-starter-parent1.5.9.RELEASEUTF-8UTF-81.8org.springframework.bootspring-boot-starter-jdbcorg.springframework.bootspring-boot-starter-securityorg.springframework.bootspring-boot-starter-weborg.springframework.security.oauthspring-security-oauth2org.springframework.securityspring-security-jwtcom.h2databaseh2org.springframework.bootspring-boot-starter-testtestorg.springframework.securityspring-security-testtestorg.springframework.bootspring-boot-maven-plugin

Gradle — build.gradle

buildscript {
   ext {
      springBootVersion = '1.5.9.RELEASE'
   }
   repositories {
      mavenCentral()
   }
   dependencies {
      classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
   }
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
group = 'com.tutorialspoint'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
   mavenCentral()
}
dependencies {
   compile('org.springframework.boot:spring-boot-starter-security')
   compile('org.springframework.boot:spring-boot-starter-web')
   testCompile('org.springframework.boot:spring-boot-starter-test')
   testCompile('org.springframework.security:spring-security-test')
   compile("org.springframework.security.oauth:spring-security-oauth2")
   compile('org.springframework.security:spring-security-jwt')
   compile("org.springframework.boot:spring-boot-starter-jdbc")
   compile("com.h2database:h2:1.4.191")  
}

Now add annotations to the main Spring Boot application file @EnableAuthorizationServerand @EnableResourceServermake the application work both as an authorization server and as a resource server.

Also add a simple HTTP Endpoint (/ products) to access the Spring Security-protected API using a JWT token.

package com.tutorialspoint.websecurityapp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@EnableAuthorizationServer
@EnableResourceServer
@RestController
public class WebsecurityappApplication {
   public static void main(String[] args) {
      SpringApplication.run(WebsecurityappApplication.class, args);
   }
   @RequestMapping(value = "/products")
   public String getProductName() {
      return "Honey";   
   }
}

Define a POJO class to store user authentication information.

package com.tutorialspoint.websecurityapp;
import java.util.ArrayList;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
public class UserEntity {
   private String username;
   private String password;
   private Collection grantedAuthoritiesList = new ArrayList<>();
   public String getPassword() {
      return password;
   }
   public void setPassword(String password) {
      this.password = password;
   }
   public Collection getGrantedAuthoritiesList() {
      return grantedAuthoritiesList;
   }
   public void setGrantedAuthoritiesList(Collection grantedAuthoritiesList) {
      this.grantedAuthoritiesList = grantedAuthoritiesList;
   }
   public String getUsername() {
      return username;
   }
   public void setUsername(String username) {
      this.username = username;
   }
}

Next, for authentication, add the CustomUser class, which extends org.springframework.security.core.userdetails.User.

package com.tutorialspoint.websecurityapp;
import org.springframework.security.core.userdetails.User;
public class CustomUser extends User {
   private static final long serialVersionUID = 1L;
   public CustomUser(UserEntity user) {
      super(user.getUsername(), user.getPassword(), user.getGrantedAuthoritiesList());
   }
}

Create @Repository-класс“ROLE_SYSTEMADMIN” to get user information from the database and add rights. This class will also be used in CustomDetailsService.

package com.tutorialspoint.websecurityapp;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Repository;
@Repository
public class OAuthDao {
   @Autowired
   private JdbcTemplate jdbcTemplate;
   public UserEntity getUserDetails(String username) {
      Collection grantedAuthoritiesList = new ArrayList<>();
      String userSQLQuery = "SELECT * FROM USERS WHERE USERNAME=?";
      List list = jdbcTemplate.query(userSQLQuery, new String[] { username },
         (ResultSet rs, int rowNum) -> {
         UserEntity user = new UserEntity();
         user.setUsername(username);
         user.setPassword(rs.getString("PASSWORD"));
         return user;
      });
      if (list.size() > 0) {
         GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_SYSTEMADMIN");
         grantedAuthoritiesList.add(grantedAuthority);
         list.get(0).setGrantedAuthoritiesList(grantedAuthoritiesList);
         return list.get(0);
      }
      return null;
   }
}

To invoke the DAO repository, you can create your own UserDetailsServiceby inheriting from org.springframework.security.core.userdetails.UserDetailsService, as shown below.

package com.tutorialspoint.websecurityapp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class CustomDetailsService implements UserDetailsService {
   @Autowired
   OAuthDao oauthDao;
   @Override
   public CustomUser loadUserByUsername(final String username) throws UsernameNotFoundException {
      UserEntity userEntity = null;
      try {
         userEntity = oauthDao.getUserDetails(username);
         CustomUser customUser = new CustomUser(userEntity);
         return customUser;
      } catch (Exception e) {
         e.printStackTrace();
         throw new UsernameNotFoundException("User " + username + " was not found in the database");
      }
   }
}

Next create @Сonfiguration-классto enable Web Security. Define password encryption parameters ( BCryptPasswordEncoder) and bin in it AuthenticationManager.
This class SecurityConfigurationmust inherit from the class WebSecurityConfigurerAdapter.

package com.tutorialspoint.websecurityapp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
   @Autowired
   private CustomDetailsService customDetailsService;
   @Bean
   public PasswordEncoder encoder() {
      return new BCryptPasswordEncoder();
   }
   @Override
   @Autowired
   protected void configure(AuthenticationManagerBuilder auth) throws Exception {
      auth.userDetailsService(customDetailsService).passwordEncoder(encoder());
   }
   @Override
   protected void configure(HttpSecurity http) throws Exception {
      http.authorizeRequests().anyRequest().authenticated().and().sessionManagement()
         .sessionCreationPolicy(SessionCreationPolicy.NEVER);
   }
   @Override
   public void configure(WebSecurity web) throws Exception {
      web.ignoring();
   }
   @Override
   @Bean
   public AuthenticationManager authenticationManagerBean() throws Exception {
      return super.authenticationManagerBean();
   }
}

Now add a class to configure OAuth2. In it, define the Client ID, Client Secret, JwtAccessTokenConverter, private and public keys for signing and verifying the token, and also configure ClientDetailsServiceConfigurerfor valid token scopes.

package com.tutorialspoint.websecurityapp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
@Configuration
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
   private String clientid = "tutorialspoint";
   private String clientSecret = "my-secret-key";
   private String privateKey = "private key";
   private String publicKey = "public key";
   @Autowired
   @Qualifier("authenticationManagerBean")
   private AuthenticationManager authenticationManager;
   @Bean
   public JwtAccessTokenConverter tokenEnhancer() {
      JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
      converter.setSigningKey(privateKey);
      converter.setVerifierKey(publicKey);
      return converter;
   }
   @Bean
   public JwtTokenStore tokenStore() {
      return new JwtTokenStore(tokenEnhancer());
   }
   @Override
   public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
      endpoints.authenticationManager(authenticationManager).tokenStore(tokenStore())
      .accessTokenConverter(tokenEnhancer());
   }
   @Override
   public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
      security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
   }
   @Override
   public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
      clients.inMemory().withClient(clientid).secret(clientSecret).scopes("read", "write")
         .authorizedGrantTypes("password", "refresh_token").accessTokenValiditySeconds(20000)
         .refreshTokenValiditySeconds(20000);
   }
}

Now create private and public keys with openssl.
To generate a private key, you can use the following commands -

openssl genrsa -out jwt.pem 2048
openssl rsa -in jwt.pem 

For public key -

openssl rsa -in jwt.pem -pubout 

For Spring Boot older than version 1.5, add the application.propertiesfollowing property to the file (to determine the filtering order of OAuth2 resources).

security.oauth2.resource.filter-order=3 

If you are using a YAML file, add the following.

security:
   oauth2:
      resource:
         filter-order: 3 

Now create the files schema.sqland data.sqlin classpaththe directory src/main/resources/directoryto connect the application to the H2 database.

The file schema.sqllooks like this:

CREATE TABLE USERS (ID INT PRIMARY KEY, USERNAME VARCHAR(45), PASSWORD VARCHAR(60));
Файл data.sql:
INSERT INTO USERS (ID, USERNAME,PASSWORD) VALUES (
   1, '[email protected]','$2a$08$fL7u5xcvsZl78su29x1ti.dxI.9rYO8t0q5wk2ROJ.1cdR53bmaVG');
INSERT INTO USERS (ID, USERNAME,PASSWORD) VALUES (
   2, '[email protected]','$2a$08$fL7u5xcvsZl78su29x1ti.dxI.9rYO8t0q5wk2ROJ.1cdR53bmaVG'); 

Note - The password in the database table must be stored in the Bcrypt Encoder format.
You can create an executable JAR file and run the Spring Boot application using the following Maven or Gradle commands.


For Maven, you can use the command below -

mvn clean install

After “BUILD SUCCESS” you can find the JAR files in the directory target.

For Gradle, you can use the command -

gradle clean build

After “BUILD SUCCESSFUL” you can find the JAR files in the directory build/libs.
Now run the JAR file using the command - The

java –jar <JARFILE>

application started in Tomcat on port 8080.



Now send a POST request via POSTMAN to receive an OAUTH2 token. Now add the request headers -

http://localhost:8080/oauth/token


  • Authorization - Basic with your Client Id and Client secret.
  • Content-Type - application / x-www-form-urlencoded



And request parameters -

  • grant_type = password
  • username = your name
  • password = your password



Now run and get access_tokenas shown.



Now create a request to the resource server API with a Bearer token in the header.



We get the result as shown below.



We are waiting for your comments, and we also inform you that until May 31 you can join the course at a special price.

Read Next