مصادقة REST API مع Spring Security و MongoDB

تحية للجميع! عند مغادرتك لقضاء عطلة نهاية الأسبوع ، نشارك معك مقالة تمت ترجمتها قبل بدء الدورة التدريبية "Developer on the Spring Framework" .





في المقالات السابقة ، أنشأنا خدمة ويب RESTful ، سنتحدث الآن عن الأمان

المقدمة


في منشور سابق ، نظرنا في كيفية إنشاء REST API باستخدام Java Spring Boot وإطار عمل MongoDB. ومع ذلك ، لم تتطلب واجهة برمجة التطبيقات أي مصادقة ، مما يعني أنه ربما لا يزال غير جاهز للاستخدام. لذلك ، سيوضح لك هذا الدليل كيفية استخدام بيئة الأمان المدمجة في Spring لإضافة مستوى مصادقة إلى واجهة برمجة التطبيقات هذه.

لماذا تحتاج API الخاصة بنا إلى المصادقة؟


توفر واجهات برمجة التطبيقات (API) واجهة بسيطة للتفاعل مع البيانات الداخلية ، لذلك فمن المنطقي أنك لا تريد أن يتمكن أي شخص من الوصول إلى هذه البيانات وتغييرها. تضمن المصادقة إمكانية الوصول إلى واجهة برمجة التطبيقات (API) فقط للمستخدمين الموثوق بهم.

كيف تعمل


سنستخدم مصادقة HTTP الأساسية ، التي تستخدم اسم مستخدم وكلمة مرور. يتم فصل اسم المستخدم وكلمة المرور على سطر واحد بنقطتين بالتنسيق التالي username:password.

يتم ترميز هذا الخط بعد ذلك باستخدام ترميز Base64 ، لذلك admin:p@55w0Rdسيتم ترميز الخط في السطر التالي YWRtaW46cEA1NXcwUmQ=(على الرغم من أنني أقترح استخدام كلمة مرور أقوى من "p @ 55w0Rd"). يمكننا إرفاق هذه المصادقة لطلباتنا عن طريق إضافة رأس Authentication. سيبدو هذا العنوان للمثال السابق كما يلي (حيث تعني كلمة "Basic" أن كلمة المرور تستخدم مصادقة HTTP الأساسية):

Authentication: Basic YWRtaW46cEA1NXcwUmQ=

كيف يدير الربيع الأمن


يوفر Spring وظيفة إضافية تسمى Spring Security ، مما يجعل المصادقة قابلة للتخصيص للغاية وبسيطة للغاية. يمكننا أيضًا استخدام بعض المهارات التي تعلمناها في مشاركة سابقة عند إعدادها!

ماذا نحتاج


  • مجموعة جديدة في نسخة MongoDB تسمى "المستخدمون"
  • مستند جديد في مجموعة المستخدمين يحتوي على الحقول التالية (أي حقول أخرى اختيارية ، لكنها ضرورية): اسم المستخدم وكلمة المرور (مجزأة باستخدام خوارزمية BCrypt ، والمزيد حول ذلك لاحقًا)
  • مصادر من الوظيفة السابقة

BCrypt لتجزئة كلمة المرور


التجزئة هي خوارزمية تشفير أحادية الاتجاه. في الواقع ، بعد التجزئة ، يكاد يكون من المستحيل اكتشاف شكل البيانات الأصلية. تقوم خوارزمية التجزئة BCrypt أولاً بتمليح جزء من النص ثم تجزئته إلى سلسلة مكونة من 60 حرفًا. يوفر برنامج تشفير Java BCrypt طريقة matchesللتحقق مما إذا كانت السلسلة تتطابق مع التجزئة. على سبيل المثال ، قد يكون لكلمة المرور p@55w0Rdالتي تم تجزئتها باستخدام BCrypt معنى $2b$10$Qrc6rGzIGaHpbgPM5kVXdeNZ9NiyRWC69Wk/17mttHKnDR2lW49KS. عند استدعاء طريقة matchesBCrypt لكلمة مرور غير مشفرة ومجزأة ، نحصل على قيمة true. يمكن إنشاء هذه التجزئات باستخدام برنامج تشفير BCrypt المدمج في Spring Security.

لماذا يجب تجزئة كلمات المرور؟


لقد سمعنا جميعًا عن الهجمات السيبرانية الأخيرة التي أسفرت عن كلمات مرور مسروقة من شركات كبيرة. فلماذا يوصى فقط بتغيير كلمات المرور الخاصة بنا بعد القرصنة؟ لأن هذه الشركات الكبيرة تأكدت من تجزئة كلمات المرور دائمًا في قواعد بياناتها!

على الرغم من أنه يستحق تغيير كلمات المرور دائمًا بعد عمليات اختراق البيانات هذه ، إلا أن تجزئة كلمة المرور تجعل من الصعب للغاية العثور على كلمة المرور الحقيقية للمستخدم ، حيث إنها خوارزمية أحادية الاتجاه. في الواقع ، قد يستغرق الأمر سنوات لاختراق تجزئة كلمة المرور المعقدة بشكل صحيح. يوفر هذا مستوى إضافيًا من الحماية ضد سرقة كلمة المرور. ويبسط نظام Spring Security التجزئة ، لذا يجب أن يكون السؤال الحقيقي: "لماذا لا؟"

إضافة مستخدم إلى MongoDB


سأضيف الحد الأدنى من الحقول اللازمة لمجموعتي ( usersالمستخدمون) ، لذا فإن المستند الذي يحتوي على المستخدمين في قاعدة البيانات الخاصة بي سيحتوي فقط على username(اسم المستخدم) وتجزئة BCrypt password(كلمة المرور). في هذا المثال ، سيكون اسم المستخدم الخاص بي ، وستكون adminكلمة المرور الخاصة بي welcome1، ولكن أقترح استخدام اسم مستخدم وكلمة مرور أكثر قوة في واجهة برمجة التطبيقات الخاصة بمستوى الإنتاج.

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

هذه هي جميع الإعدادات المطلوبة في MongoDB! سيتم إجراء بقية التهيئة في كود Java الخاص بنا.

إضافة نموذج مستخدم ومستودع


وصف المنشور السابق بالتفصيل عن نماذج ومستودعات Mongo ، لذلك لن أخوض في تفاصيل حول كيفية عملها هنا - إذا كنت ترغب في تحديث معرفتك ، فلا تتردد في زيارة مشاركتي السابقة!

الجانب السلبي هو أن Spring يحتاج إلى معرفة شكل المستند user(النموذج) وكيفية الوصول إلى المجموعة userفي قاعدة البيانات (المستودعات). يمكننا وضع هذه الملفات في نفس مجلدات النماذج والمستودعات ، على التوالي ، كما فعلنا في التمرين السابق.

نموذج


سيكون النموذج فئة جافا الأساسية مع العرف _id، usernameو password. سيتم تسمية الملف Users.java. وستبدو كما يلي:

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

مخزن


سيتم استدعاء المستودع وسيبدو على هذا النحو UsersRepository.java- تذكر ، سنحتاج إلى العثور على المستخدمين من خلالهم username، لذلك سنحتاج إلى تضمين الطريقة findByUsernameفي واجهة المستودع.

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

وهذا كل شيء بالنسبة للنموذج والمستودع!

إضافة تبعيات الأمان


يجب أن يكون هناك ملف باسم في الدليل الجذر للمشروع pom.xml. لم نلمس هذا الملف حتى الآن ، ولكن ملف pom يحتوي على جميع تبعيات مشروعنا ، وسنقوم بإضافة اثنين منهم ، لذلك دعونا نبدأ بفتح هذا الملف والتمرير لأسفل إلى العلامة . التبعية الجديدة الوحيدة التي نحتاجها هي أمن بداية الربيع . يحتوي Spring على مدير إصدار مدمج ، لذا فإن التبعية التي يجب أن نضيفها إلى العلامة هي كما يلي:<dependencies>

<dependencies>

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

وسوف يقوم Maven بتنزيل ملفات المصدر لنا ، لذا يجب أن تكون تبعياتنا جاهزة للعمل!

إنشاء خدمة المصادقة


نحتاج إلى إخبار Spring بمكان بيانات المستخدم الخاص بنا وأين نجد المعلومات اللازمة للمصادقة. للقيام بذلك ، يمكننا إنشاء خدمة مصادقة (خدمة المصادقة). لنبدأ بإنشاء مجلد جديد في src/main/resources/java/[package name]خدمات تسمى ، ويمكننا إنشاء ملف جديد في مجلد التكوين هذا بالاسم MongoUserDetailsService.java.

MongoUserDetailsService.java


يحتوي هذا الفصل على مكون رئيسي واحد ، لذلك سأعطي الفصل بأكمله هنا ثم أشرحه أدناه:

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

يبدأ هذا المقتطف بعمليات الاستيراد التي نحتاجها في الملف. علاوة على ذلك ، implements UserDetailsServiceيشير القسم إلى أن هذا الفصل سينشئ خدمة للبحث عن المستخدمين ومصادقتهم. بعد ذلك ، @Componentيشير التعليق التوضيحي إلى أن هذه الفئة يمكن تضمينها في ملف آخر (على سبيل المثال ، ملف تكوين الأمان ، والذي سننتقل إليه عبر عدة أقسام).

الشرح @Autowiredعلى private UsersRepository repository. مثال على التنفيذ ، هذه الخاصية تزودنا بمثيل لنا UsersRepositoryللعمل. @Overrideيشير التعليق التوضيحي إلى أنه سيتم استخدام هذه الطريقة بدلاً من أسلوب UserDetailsService الافتراضي. أولاً ، تحصل هذه الطريقة على الكائن Usersمن مصدر بيانات MongoDB باستخدام الطريقة findByUsernameالتي أعلنا عنها UsersRepository.

ثم تتحقق الطريقة مما إذا تم العثور على المستخدم أم لا. ثم يتم منح المستخدم أذونات / دور (يمكن أن يضيف هذا مستويات مصادقة إضافية لمستويات الوصول ، ولكن دورًا واحدًا سيكون كافيًا لهذا الدرس). وأخيرا، إرجاع الأسلوب كائن الربيع Userمع username، passwordو roleمصادقة المستخدم.

إنشاء تكوين الأمان


سنحتاج إلى إعادة تعريف بعض بروتوكولات الأمان المضمنة في Spring لاستخدام قاعدة بياناتنا وخوارزمية التجزئة ، لذلك نحتاج إلى ملف تهيئة خاص. لإنشائه ، يجب علينا إنشاء مجلد جديد src/main/resources/java/[package name]بالاسم config، ونحتاج أيضًا إلى إنشاء ملف جديد في مجلد التكوين هذا بالاسم SecurityConfiguration.java. يحتوي هذا الملف على العديد من الأجزاء المهمة ، لذلك دعونا نبدأ بالفئة الأساسية لتكوين 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;
}

هناك بالفعل ما يكفي للتعامل معها ، لذلك دعونا نبدأ من فوق. @Configurationيشير تعليق توضيحي إلى أن الفصل سيحتوي على حبوب جافا ، موصوفة بالتفصيل هنا. @EnableConfigurationPropertiesيشير التعليق التوضيحي إلى ما سيحتوي عليه الفصل كحبة تكوين خاصة. بعد ذلك ، ستعمل التعليمات على extends WebSecurityConfigurerAdapterتعيين الفصل الأصل WebSecurityConfigurerAdapterإلى فصل التكوين الخاص بنا ، وتزويد فصلنا بكل ما هو ضروري لضمان احترام قواعد الأمان الخاصة به. أخيرًا ، سيقوم الفصل تلقائيًا بإدخال مثيل ( @Autowired) MongoUserDetailsService، والذي يمكننا استخدامه لاحقًا في هذا الملف.

خطوة المصادقة


بعد ذلك ، نحتاج إلى إخبار Spring Security بكيفية التعامل مع مصادقة المستخدم. افتراضيا، الأمن الربيع لديه اسم المستخدم وكلمة المرور محددة سلفا، حماية CSRF، و إدارة الجلسة . ومع ذلك ، نريد من مستخدمينا استخدام اسم المستخدم وكلمة المرور للوصول إلى قاعدة البيانات. بالإضافة إلى ذلك ، نظرًا لأنه ستتم إعادة مصادقة مستخدمينا عند كل طلب ، بدلاً من تسجيل الدخول ، فإننا لا نحتاج إلى حماية CSRF وإدارة الجلسة ، لذلك يمكننا إضافة طريقة باسم configureيتجاوز نظام المصادقة الافتراضي لإخبار Spring بالتحديد بكيفية نريد التعامل مع المصادقة ، وسوف نبدو كما يلي:

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

مرة أخرى ، يحدث الكثير من الأشياء هنا ، لذلك سنقوم بتصنيفها على مراحل. @Overrideيخبر التعليق التوضيحي Spring Boot باستخدام الطريقة configure (HttpSecurity http)بدلاً من تكوين Spring الافتراضي. ثم نسمي سلسلة من الطرق للكائن httpحيث يتم التكوين الفعلي. تقوم هذه الطرق بما يلي:

  • csrf().disable(): لتعطيل حماية CSRF لأنه غير مطلوب لواجهة برمجة التطبيقات
  • authorizeRequests().anyRequest().authenticated(): يعلن أنه يجب الموافقة على جميع الطلبات إلى أي نقطة نهاية ، وإلا يجب رفضها.
  • and().httpBasic(): Springبحيث يتوقع مصادقة HTTP الأساسية (الموضحة أعلاه).
  • .and().sessionManagement().disable(): يخبر Spring بعدم تخزين معلومات الجلسة للمستخدمين ، لأن هذا ليس ضروريًا لواجهة برمجة التطبيقات

إضافة برنامج تشفير Bcrypt


نحتاج الآن إلى إخبار Spring باستخدام برنامج تشفير BCrypt لتجزئة ومقارنة كلمات المرور - تبدو هذه مهمة صعبة ، ولكنها في الواقع بسيطة للغاية. يمكننا إضافة هذا التشفير بمجرد إضافة الأسطر التالية إلى فصلنا SecurityConfiguration:

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

وكل العمل! هذا الفول البسيط يخبر الربيع أن PasswordEncoder الذي نريد استخدامه هو Boot Boot BCryptPasswordEncoder()لترميز ومقارنة تجزئات كلمة المرور. يتضمن Spring Boot أيضًا العديد من برامج ترميز كلمات المرور الأخرى - أوصي بتجربتها إذا كنت تريد التجربة!

حدد مدير المصادقة


أخيرًا ، يجب أن نشير في موقعنا إلى SecurityConfigurationأننا نريد استخدامه MongoUserDetailsService(الذي أنشأناه في القسم السابق) للمصادقة. يمكننا القيام بذلك باستخدام الطريقة التالية:

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

هذه الطريقة ببساطة تتجاوز التكوين الافتراضي AuthenticationManagerBuilder، بدلاً من خدمة نقل البيانات المخصصة الخاصة بنا بدلاً من ذلك.

ملف 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);
 }
}

التحقق من المصادقة


سأختبر GETطلبًا سريعًا بمصادقة صالحة وغير صحيحة للتأكد من أن التكوين يعمل كما هو مخطط له.

اسم المستخدم / كلمة المرور غير صحيحة

: http://localhost:8080/pets/
الطريقة: GET
تسجيل الدخول: Basic YWRtaW46d2VsY29tZQ==

الرد:

401 Unauthorized

اسم مستخدم / كلمة مرور صالحة

: http://localhost:8080/pets/
الطريقة: GET
تسجيل الدخول: Basic YWRtaW46d2VsY29tZTE=

رد:

[
 {
   “_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”
 }
]

استنتاج


وهي تعمل كما يجب! يمكن أن تكون مصادقة HTTP الأساسية لواجهة برمجة تطبيقات Spring Boot صعبة ، ولكن نأمل أن يساعد هذا الدليل في جعلها أكثر قابلية للفهم. المصادقة أمر لا بد منه في المناخ السيبراني اليوم ، لذا فإن أدوات مثل Spring Security ضرورية لضمان سلامة بياناتك وأمانها.

هذا كل شئ! قابلني في الدورة .

All Articles