Spring Boot - OAuth2 dengan JWT

Di bab ini, Anda akan mempelajari secara mendetail tentang mekanisme Keamanan Boot Musim Semi dan OAuth2 dengan JWT.

Server Otorisasi

Server Otorisasi adalah komponen arsitektur tertinggi untuk Keamanan API Web. Server Otorisasi bertindak sebagai titik otorisasi sentralisasi yang memungkinkan aplikasi dan titik akhir HTTP Anda mengidentifikasi fitur aplikasi Anda.

Server Sumber Daya

Resource Server adalah aplikasi yang menyediakan token akses kepada klien untuk mengakses Titik Akhir HTTP Server Sumber Daya. Ini adalah kumpulan pustaka yang berisi Titik Akhir HTTP, sumber daya statis, dan halaman web Dinamis.

OAuth2

OAuth2 adalah kerangka kerja otorisasi yang memungkinkan Keamanan Web aplikasi mengakses sumber daya dari klien. Untuk membangun aplikasi OAuth2, kita perlu fokus pada Jenis Hibah (kode Otorisasi), ID Klien, dan rahasia Klien.

 

Token JWT

Token JWT adalah Token Web JSON, digunakan untuk mewakili klaim yang dijamin antara dua pihak. Anda dapat mempelajari lebih lanjut tentang token JWT di www.jwt.io/ .

Sekarang, kita akan membangun aplikasi OAuth2 yang memungkinkan penggunaan Server Otorisasi, Server Sumber Daya dengan bantuan Token JWT.

Anda dapat menggunakan langkah-langkah berikut untuk mengimplementasikan Spring Boot Security dengan token JWT dengan mengakses database.

Pertama, kita perlu menambahkan dependensi berikut di file konfigurasi build kita.

Pengguna Maven dapat menambahkan dependensi berikut di file pom.xml Anda.

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

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

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

<dependency>
   <groupId>org.springframework.security.oauth</groupId>
   <artifactId>spring-security-oauth2</artifactId>
</dependency>

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

<dependency>
   <groupId>com.h2database</groupId>
   <artifactId>h2</artifactId>
</dependency>

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

<dependency>
   <groupId>org.springframework.security</groupId>
   <artifactId>spring-security-test</artifactId>
   <scope>test</scope>
</dependency>

Pengguna Gradle dapat menambahkan dependensi berikut dalam 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")

dimana,

  • Spring Boot Starter Security - Menerapkan Keamanan Musim Semi

  • Spring Security OAuth2 - Menerapkan struktur OAUTH2 untuk mengaktifkan Server Otorisasi dan Server Sumber Daya.

  • Spring Security JWT - Menghasilkan Token JWT untuk keamanan Web

  • Spring Boot Starter JDBC - Mengakses database untuk memastikan pengguna tersedia atau tidak.

  • Spring Boot Starter Web - Menulis titik akhir HTTP.

  • H2 Database - Menyimpan informasi pengguna untuk otentikasi dan otorisasi.

File konfigurasi build lengkap diberikan di bawah ini.

<?xml version = "1.0" encoding = "UTF-8"?>
<project xmlns = "http://maven.apache.org/POM/4.0.0" 
   xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0 
   http://maven.apache.org/xsd/maven-4.0.0.xsd">
   
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.tutorialspoint</groupId>
   <artifactId>websecurityapp</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>websecurityapp</name>
   <description>Demo project for Spring Boot</description>

   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>1.5.9.RELEASE</version>
      <relativePath /> <!-- lookup parent from repository -->
   </parent>

   <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
      <java.version>1.8</java.version>
   </properties>

   <dependencies>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-jdbc</artifactId>
      </dependency>
      
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-security</artifactId>
      </dependency>
      
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      
      <dependency>
         <groupId>org.springframework.security.oauth</groupId>
         <artifactId>spring-security-oauth2</artifactId>
      </dependency>
      
      <dependency>
         <groupId>org.springframework.security</groupId>
         <artifactId>spring-security-jwt</artifactId>
      </dependency>
      
      <dependency>
         <groupId>com.h2database</groupId>
         <artifactId>h2</artifactId>
      </dependency>
      
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
      </dependency>
      
      <dependency>
         <groupId>org.springframework.security</groupId>
         <artifactId>spring-security-test</artifactId>
         <scope>test</scope>
      </dependency>
   </dependencies>

   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
   </build>
   
</project>

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

Sekarang, di aplikasi Spring Boot utama, tambahkan anotasi @EnableAuthorizationServer dan @EnableResourceServer untuk bertindak sebagai server Auth dan Server Sumber Daya di aplikasi yang sama.

Selain itu, Anda dapat menggunakan kode berikut untuk menulis titik akhir HTTP sederhana untuk mengakses API dengan Keamanan Musim Semi dengan menggunakan Token JWT.

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

Gunakan kode berikut untuk menentukan kelas POJO untuk menyimpan informasi Pengguna untuk otentikasi.

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<GrantedAuthority> grantedAuthoritiesList = new ArrayList<>();
   
   public String getPassword() {
      return password;
   }
   public void setPassword(String password) {
      this.password = password;
   }
   public Collection<GrantedAuthority> getGrantedAuthoritiesList() {
      return grantedAuthoritiesList;
   }
   public void setGrantedAuthoritiesList(Collection<GrantedAuthority> grantedAuthoritiesList) {
      this.grantedAuthoritiesList = grantedAuthoritiesList;
   }
   public String getUsername() {
      return username;
   }
   public void setUsername(String username) {
      this.username = username;
   }
}

Sekarang, gunakan kode berikut dan tentukan kelas CustomUser yang memperluas kelas org.springframework.security.core.userdetails.User untuk otentikasi Spring Boot.

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

Anda dapat membuat kelas @Repository untuk membaca informasi Pengguna dari database dan mengirimkannya ke layanan pengguna Kustom dan juga menambahkan otoritas yang diberikan "ROLE_SYSTEMADMIN".

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<GrantedAuthority> grantedAuthoritiesList = new ArrayList<>();
      String userSQLQuery = "SELECT * FROM USERS WHERE USERNAME=?";
      List<UserEntity> 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;
   }
}

Anda bisa membuat kelas layanan detail Pengguna Kustom yang memperluas org.springframework.security.core.userdetails.UserDetailsService untuk memanggil kelas repositori DAO seperti yang ditunjukkan.

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

Selanjutnya, buat kelas @configuration untuk mengaktifkan Keamanan Web, tentukan pembuat kata sandi (BCryptPasswordEncoder), dan tentukan kacang AuthenticationManager. Kelas konfigurasi Keamanan harus memperluas kelas 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();
   }
}

Sekarang, tentukan kelas Konfigurasi OAuth2 untuk menambahkan ID Klien, Rahasia Klien, Tentukan JwtAccessTokenConverter, Kunci pribadi dan Kunci publik untuk kunci penandatangan token dan kunci pemverifikasi, dan konfigurasikan ClientDetailsServiceConfigurer untuk validitas Token dengan cakupan.

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

   }
}

Sekarang, buat kunci pribadi dan kunci publik dengan menggunakan openssl.

Anda dapat menggunakan perintah berikut untuk membuat kunci pribadi.

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

Anda dapat menggunakan Untuk pembuatan kunci publik menggunakan perintah di bawah ini.

openssl rsa -in jwt.pem -pubout

Untuk versi Spring Boot setelah rilis 1.5, tambahkan properti di bawah ini dalam file application.properties Anda untuk menentukan urutan filter Sumber Daya OAuth2.

security.oauth2.resource.filter-order=3

Pengguna file YAML dapat menambahkan properti di bawah ini dalam file YAML.

security:
   oauth2:
      resource:
         filter-order: 3

Sekarang, buat file schema.sql dan data.sql di bawah resource classpath src/main/resources/directory untuk menghubungkan aplikasi ke database H2.

File schema.sql seperti yang ditunjukkan -

CREATE TABLE USERS (ID INT PRIMARY KEY, USERNAME VARCHAR(45), PASSWORD VARCHAR(60));

File data.sql seperti yang ditunjukkan -

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 - Kata sandi harus disimpan dalam format Bcrypt Encoder di tabel database.

Anda dapat membuat file JAR yang dapat dieksekusi, dan menjalankan aplikasi Spring Boot menggunakan perintah Maven atau Gradle berikut.

Untuk Maven, Anda dapat menggunakan perintah yang diberikan di bawah ini -

mvn clean install

Setelah “BUILD SUCCESS”, Anda dapat menemukan file JAR di bawah direktori target.

Untuk Gradle, Anda dapat menggunakan perintah seperti yang ditunjukkan -

gradle clean build

Setelah "BUILD SUCCESSFUL", Anda dapat menemukan file JAR di bawah direktori build / libs.

Sekarang, jalankan file JAR dengan menggunakan perintah yang ditunjukkan di sini -

java –jar <JARFILE>

Aplikasi dimulai pada port Tomcat 8080.

Sekarang tekan URL metode POST melalui POSTMAN untuk mendapatkan token OAUTH2.

http://localhost:8080/oauth/token

Sekarang, tambahkan Header Permintaan sebagai berikut -

  • Authorization - Autentikasi Dasar dengan Id Klien dan rahasia Klien Anda.

  • Content Type - aplikasi / x-www-form-urlencoded

Sekarang, tambahkan Parameter Permintaan sebagai berikut -

  • grant_type = password
  • username = nama pengguna Anda
  • password = kata sandi Anda

Sekarang, tekan API dan dapatkan access_token seperti yang ditunjukkan -

Sekarang, Tekan Resource Server API dengan token akses Bearer di Request Header seperti yang ditunjukkan.

Kemudian Anda dapat melihat hasilnya seperti yang ditunjukkan di bawah ini -