REST API Authentication dengan Spring Security dan MongoDB

Halo semuanya! Berangkat untuk akhir pekan, kami berbagi dengan Anda sebuah artikel yang diterjemahkan sebelum dimulainya kursus "Pengembang pada Kerangka Musim Semi" .





Pada artikel sebelumnya, kami membuat layanan web RESTful, sekarang kami akan berbicara tentang keamanan

pengantar


Dalam posting sebelumnya, kami melihat cara membuat REST API menggunakan Java Spring Boot dan kerangka kerja MongoDB. Namun, API tidak memerlukan otentikasi apa pun, yang berarti masih belum siap untuk digunakan. Oleh karena itu, panduan ini akan menunjukkan kepada Anda cara menggunakan lingkungan keamanan bawaan Spring untuk menambahkan tingkat otentikasi ke API ini.

Mengapa API kami memerlukan otentikasi?


API menyediakan antarmuka sederhana untuk berinteraksi dengan data internal, jadi masuk akal jika Anda tidak ingin ada orang yang memiliki akses ke data ini dan mengubahnya. Otentikasi memastikan bahwa hanya pengguna yang dapat dipercaya yang dapat mengakses API.

Bagaimana itu bekerja


Kami akan menggunakan otentikasi HTTP dasar , yang menggunakan nama pengguna dan kata sandi. Nama pengguna dan kata sandi dipisahkan pada satu baris dengan titik dua dalam format berikut username:password.

Baris ini kemudian dikodekan menggunakan pengkodean Base64 , sehingga baris admin:p@55w0Rdakan dikodekan di baris berikutnya YWRtaW46cEA1NXcwUmQ=(meskipun saya akan menyarankan menggunakan kata sandi yang lebih aman daripada "p @ 55w0Rd"). Kami dapat melampirkan otentikasi ini ke permintaan kami dengan menambahkan header Authentication. Judul ini untuk contoh sebelumnya akan terlihat seperti ini (di mana "Dasar" berarti kata sandi menggunakan otentikasi HTTP dasar):

Authentication: Basic YWRtaW46cEA1NXcwUmQ=

Bagaimana Spring Mengelola Keamanan


Spring menawarkan add-in yang disebut Spring Security , yang membuat otentikasi sangat dapat disesuaikan dan sangat sederhana. Kami bahkan dapat menggunakan beberapa keterampilan yang kami pelajari di pos sebelumnya saat menyiapkan!

Apa yang kita butuhkan


  • Koleksi baru dalam instance MongoDB kami yang disebut "pengguna"
  • Dokumen baru dalam koleksi pengguna dengan bidang-bidang berikut (setiap bidang lain opsional, tetapi ini diperlukan): nama pengguna, kata sandi (hash menggunakan algoritma BCrypt, lebih lanjut tentang itu nanti)
  • Sumber dari pos sebelumnya

BCrypt untuk hashing kata sandi


Hashing adalah algoritma enkripsi satu arah. Faktanya, setelah hashing, hampir tidak mungkin menemukan seperti apa data aslinya. Algoritme BCrypt hash pertama-tama menggarami sepotong teks dan kemudian hash ke string 60 karakter. Java BCrypt encoder menawarkan metode matchesyang memeriksa apakah string cocok dengan hash. Misalnya, kata sandi p@55w0Rdyang di hash dengan BCrypt mungkin memiliki makna $2b$10$Qrc6rGzIGaHpbgPM5kVXdeNZ9NiyRWC69Wk/17mttHKnDR2lW49KS. Saat memanggil metode matchesBCrypt untuk kata sandi yang tidak terenkripsi dan hash, kami mendapatkan nilai true. Hash ini dapat dihasilkan menggunakan BCrypt encoder yang dibangun ke dalam Spring Security.

Mengapa kita harus kata sandi hash?


Kita semua telah mendengar tentang serangan cyber baru-baru ini yang mengakibatkan kata sandi dicuri dari perusahaan besar. Jadi mengapa hanya disarankan untuk mengubah kata sandi kami setelah peretasan? Karena perusahaan-perusahaan besar ini memastikan bahwa kata sandi selalu hash dalam database mereka!

Meskipun selalu layak untuk mengubah kata sandi setelah peretasan data seperti itu, hashing kata sandi membuatnya sangat sulit untuk menemukan kata sandi asli pengguna, karena ini adalah algoritma satu arah. Bahkan, mungkin perlu bertahun - tahun untuk memecahkan kata sandi yang rumit yang hash dengan benar. Ini memberikan tingkat perlindungan tambahan terhadap pencurian kata sandi. Dan Spring Security menyederhanakan hashing, jadi pertanyaan sebenarnya adalah: "Mengapa tidak?"

Menambahkan pengguna ke MongoDB


Saya akan menambahkan bidang minimum yang diperlukan untuk koleksi saya users(pengguna), jadi dokumen dengan pengguna di basis data saya hanya akan berisi username(nama pengguna) dan hasht BCrypt password(kata sandi). Dalam contoh ini, nama pengguna saya akan admin, dan kata sandi saya akan welcome1, tetapi saya akan menyarankan menggunakan nama pengguna dan kata sandi yang lebih kuat di API tingkat produksi.

db.users.insert({
  “username” : “admin”,
  “password” : “$2a$10$AjHGc4x3Nez/p4ZpvFDWeO6FGxee/cVqj5KHHnHfuLnIOzC5ag4fm”
});

Ini semua pengaturan yang diperlukan di MongoDB! Konfigurasi lainnya akan dilakukan dalam kode Java kami.

Menambahkan model pengguna dan repositori


Posting sebelumnya dijelaskan secara rinci tentang model dan repositori Mongo, jadi saya tidak akan membahas secara terperinci tentang bagaimana mereka bekerja di sini - jika Anda ingin menyegarkan kembali pengetahuan Anda, jangan ragu untuk mengunjungi posting saya sebelumnya!

The downside adalah bahwa Spring perlu tahu seperti apa dokumen user(model) akan terlihat dan bagaimana mengakses koleksi userdalam database (repositori). Kita dapat menempatkan file-file ini dalam folder model dan repositori yang sama, seperti yang kita lakukan pada latihan sebelumnya.

Model


Model akan menjadi kelas dasar Java dengan kustom _id, usernamedan password. File akan dinamai Users.java. dan akan terlihat seperti ini:

package com.example.gtommee.rest_tutorial.models;

import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;

public class Users {
  @Id
  public ObjectId _id;

  public String username;
  public String password;

  public Users() {}

  public Users(ObjectId _id, String username, String password) 
{
    this._id = _id;
    this.username = username;
    this.password = password;
  }

  public void set_id(ObjectId _id) { this._id = _id; }

  public String get_id() { return this._id.toHexString(); }

  public void setPassword(String password) { this.password = password; }

  public String getPassword() { return password; }

  public void setUsername(String username) { this.username = username; }

  public String getUsername() { return username; }
}

Gudang


Repositori akan dipanggil UsersRepository.javadan akan terlihat seperti ini - ingat, kita akan perlu mencari pengguna oleh mereka username, jadi kita perlu memasukkan metode findByUsernamedalam antarmuka repositori.

package com.example.gtommee.rest_tutorial.repositories;

import com.example.gtommee.rest_tutorial.models.Users;
import org.springframework.data.mongodb.repository.MongoRepository;

public interface UsersRepository extends MongoRepository<Users, String> {
  Users findByUsername(String username);
}

Dan itu untuk model dan repositori!

Menambahkan Ketergantungan Keamanan


Seharusnya ada file dengan nama di direktori root proyek pom.xml. Kami belum menyentuh file ini, tetapi file pom berisi semua dependensi proyek kami, dan kami akan menambahkan beberapa dari mereka, jadi mari kita mulai dengan membuka file ini dan gulir ke bawah ke tag . Satu-satunya dependensi baru yang kita butuhkan adalah spring-starter-security . Spring memiliki pengelola versi bawaan, jadi ketergantungan yang harus kita tambahkan ke tag adalah sebagai berikut:<dependencies>

<dependencies>

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

Dan Maven akan mengunduh file sumber untuk kami, jadi dependensi kami harus siap untuk digunakan!

Buat Layanan Otentikasi


Kami perlu memberi tahu Spring di mana data pengguna kami dan di mana menemukan informasi yang diperlukan untuk otentikasi. Untuk melakukan ini, kita dapat membuat layanan otentikasi (Layanan Otentikasi). Mari kita mulai dengan membuat folder baru di src/main/resources/java/[package name]layanan yang disebut, dan kita dapat membuat file baru di folder konfigurasi ini dengan namanya MongoUserDetailsService.java.

MongoUserDetailsService.java


Kelas ini memiliki satu komponen utama, jadi saya akan memberikan seluruh kelas di sini dan kemudian menjelaskannya di bawah:

package com.example.gtommee.rest_tutorial.services;

import com.example.gtommee.rest_tutorial.models.Users;
import com.example.gtommee.rest_tutorial.repositories.UsersRepository;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

@Component
public class MongoUserDetailsService implements UserDetailsService{
  @Autowired
  private UsersRepository repository;

  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    Users user = repository.findByUsername(username);

    if(user == null) {
      throw new UsernameNotFoundException(“User not found”);
    }

    List<SimpleGrantedAuthority> authorities = Arrays.asList(new SimpleGrantedAuthority(“user”));

    return new User(user.getUsername(), user.getPassword(), authorities);
  }
}

Cuplikan ini dimulai dengan impor yang kita butuhkan di file. Lebih lanjut, bagian ini implements UserDetailsServicemengindikasikan bahwa kelas ini akan membuat layanan untuk mencari dan mengautentikasi pengguna. Kemudian, anotasi @Componentmenunjukkan bahwa kelas ini dapat disematkan di file lain (misalnya, file SecurityConfiguration, yang akan kita bahas melalui beberapa bagian).

Penjelasan @Autowiredatas private UsersRepository repository; adalah contoh implementasi, properti ini memberi kami contoh dari kami UsersRepositoryuntuk bekerja. Anotasi @Overridemenunjukkan bahwa metode ini akan digunakan sebagai pengganti metode UserDetailsService default. Pertama, metode ini mendapatkan objek Usersdari sumber data MongoDB menggunakan metode findByUsernameyang kami nyatakan UsersRepository.

Metode tersebut kemudian memeriksa apakah pengguna ditemukan atau tidak. Kemudian pengguna diberikan izin / peran (ini dapat menambahkan tingkat otentikasi tambahan untuk tingkat akses, tetapi satu peran akan cukup untuk pelajaran ini). Akhirnya, metode mengembalikan objek Spring Userdengan username, passworddan rolepengguna yang diautentikasi.

Buat Konfigurasi Keamanan


Kami perlu mendefinisikan ulang beberapa protokol keamanan bawaan Spring untuk menggunakan database dan algoritma hash kami, jadi kami memerlukan file konfigurasi khusus. Untuk membuatnya, kita harus membuat folder baru src/main/resources/java/[package name]dengan nama config, dan kita juga perlu membuat file baru di folder konfigurasi ini dengan nama SecurityConfiguration.java. File ini memiliki beberapa bagian penting, jadi mari kita mulai dengan kelas dasar SecurityConfiguration:

SecurityConfiguration.java


package com.example.gtommee.rest_tutorial.config;

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableConfigurationProperties
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
 @Autowired
 MongoUserDetailsService userDetailsService;
}

Sudah ada cukup banyak untuk ditangani, jadi mari kita mulai dari atas. Anotasi @Configurationmenunjukkan bahwa kelas tersebut akan berisi kacang Jawa, dijelaskan secara rinci di sini. Anotasi @EnableConfigurationPropertiesmenunjukkan apa yang akan berisi kelas sebagai kacang konfigurasi khusus. Kemudian instruksi akan extends WebSecurityConfigurerAdaptermemetakan kelas induk WebSecurityConfigurerAdapterke kelas konfigurasi kita, menyediakan kelas kita dengan semua yang diperlukan untuk memastikan bahwa aturan keamanannya dihormati. Akhirnya, kelas akan secara otomatis menyuntikkan instance ( @Autowired) MongoUserDetailsService, yang dapat kita gunakan nanti dalam file ini.

Langkah otentikasi


Selanjutnya, kita perlu memberi tahu Spring Security bagaimana kita ingin menangani otentikasi pengguna. Secara default, Spring Security memiliki nama pengguna dan kata sandi yang sudah ditentukan, Perlindungan CSRF, dan manajemen sesi . Namun, kami ingin pengguna kami menggunakan nama pengguna dan kata sandi mereka untuk mengakses database. Selain itu, karena pengguna kami akan diautentikasi ulang pada setiap permintaan, daripada masuk, kami tidak memerlukan Perlindungan CSRF dan manajemen sesi, sehingga kami dapat menambahkan metode dengan nama configureyang menimpa skema otentikasi default untuk memberi tahu Spring secara persis bagaimana kami ingin menangani otentikasi, dan akan terlihat seperti ini:

@Override
protected void configure(HttpSecurity http) throws Exception {
 http
   .csrf().disable()
   .authorizeRequests().anyRequest().authenticated()
   .and().httpBasic()
   .and().sessionManagement().disable();
}

Sekali lagi, banyak hal terjadi di sini, jadi kami akan mengatasinya secara bertahap. Anotasi tersebut @Overridememberi tahu Spring Boot untuk menggunakan metode configure (HttpSecurity http)alih-alih konfigurasi Spring default. Kemudian kita memanggil serangkaian metode untuk objek di httpmana konfigurasi aktual terjadi. Metode-metode ini melakukan hal berikut:

  • csrf().disable(): Menonaktifkan Perlindungan CSRF karena tidak diperlukan untuk API
  • authorizeRequests().anyRequest().authenticated(): Menyatakan bahwa semua permintaan ke titik akhir apa pun harus disahkan, jika tidak mereka harus ditolak.
  • and().httpBasic(): Springsehingga mengharapkan otentikasi HTTP dasar (dibahas di atas).
  • .and().sessionManagement().disable(): memberitahu Spring untuk tidak menyimpan informasi sesi untuk pengguna, karena ini tidak diperlukan untuk API

Menambahkan Bcrypt Encoder


Sekarang kita perlu memberitahu Spring untuk menggunakan BCrypt encoder untuk hashing dan membandingkan kata sandi - ini terdengar seperti tugas yang sulit, tetapi sebenarnya itu sangat sederhana. Kami dapat menambahkan encoder ini dengan hanya menambahkan baris berikut ke kelas kami SecurityConfiguration:

@Bean
public PasswordEncoder passwordEncoder() {
   return new BCryptPasswordEncoder();
}

Dan semua pekerjaan! Kacang sederhana ini memberi tahu Spring bahwa PasswordEncoder yang ingin kita gunakan adalah Spring Boot BCryptPasswordEncoder()untuk menyandikan dan membandingkan hash kata sandi. Spring Boot juga menyertakan beberapa penyandi kata sandi lainnya - Saya sarankan mencobanya jika Anda ingin bereksperimen!

Tentukan Manajer Otentikasi


Akhirnya, kita harus menunjukkan di kita SecurityConfigurationbahwa kita ingin menggunakan MongoUserDetailsService(yang kita buat di bagian sebelumnya) untuk otentikasi kami. Kita dapat melakukan ini menggunakan metode berikut:

@Override
public void configure(AuthenticationManagerBuilder builder) 
throws Exception {
  builder.userDetailsService(userDetailsService);
}

Metode ini hanya mengesampingkan konfigurasi default AuthenticationManagerBuilder, sebagai gantinya menggantikan layanan transfer data kustom kami sendiri.

File Final SecurityConfiguration.java


import com.example.gtommee.rest_tutorial.services.MongoUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableConfigurationProperties
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
 @Autowired
 MongoUserDetailsService userDetailsService;

 @Override
 protected void configure(HttpSecurity http) throws Exception {
   http
     .csrf().disable()
     .authorizeRequests().anyRequest().authenticated()
     .and().httpBasic()
     .and().sessionManagement().disable();
 }

 @Bean
 public PasswordEncoder passwordEncoder() {
   return new BCryptPasswordEncoder();
 }

 @Override
 public void configure(AuthenticationManagerBuilder builder) 
throws Exception {
   builder.userDetailsService(userDetailsService);
 }
}

Pemeriksaan otentikasi


Saya akan menguji GETpermintaan cepat dengan otentikasi yang valid dan salah untuk memastikan bahwa konfigurasi berfungsi seperti yang direncanakan.

Nama Pengguna / Kata Sandi

URL: http://localhost:8080/pets/
Metode: GET
Login: Basic YWRtaW46d2VsY29tZQ==

Balas:

401 Unauthorized

Nama Pengguna / Kata Sandi yang Valid

: http://localhost:8080/pets/
Metode: GET
Masuk: Basic YWRtaW46d2VsY29tZTE=

Jawab:

[
 {
   “_id”: “5aeccb0a18365ba07414356c”,
   “name”: “Spot”,
   “species”: “dog”,
   “breed”: “pitbull”
 },
 {
   “_id”: “5aeccb0a18365ba07414356d”,
   “name”: “Daisy”,
   “species”: “cat”,
   “breed”: “calico”
 },
 {
   “_id”: “5aeccb0a18365ba07414356e”,
   “name”: “Bella”,
   “species”: “dog”,
   “breed”: “australian shepard”
 }
]

Kesimpulan


Ini berfungsi sebagaimana mestinya! Otentikasi HTTP dasar untuk Spring Boot API bisa rumit, tetapi mudah-mudahan panduan ini akan membantu membuatnya lebih dimengerti. Otentikasi adalah suatu keharusan dalam iklim cyber saat ini, jadi alat seperti Spring Security sangat penting untuk memastikan integritas dan keamanan data Anda.

Itu saja! Temui aku di lapangan .

All Articles