使用Spring Security和MongoDB进行REST API身份验证

大家好!离开周末,我们将与您分享一篇文章,该文章在“ Spring Framework开发人员 ”课程开始之前进行了翻译





在之前的文章中,我们创建了RESTful网络服务,现在我们将讨论安全性

介绍


在上一篇文章中,我们研究了如何使用Java Spring Boot和MongoDB框架创建REST API。但是,该API不需要任何身份验证,这意味着它可能仍未准备好使用。因此,本指南将向您展示如何使用Spring内置的安全性环境向此API添加身份验证级别。

为什么我们的API需要身份验证?


这些API提供了一个用于与内部数据进行交互的简单接口,因此您不希望任何人有权访问此数据并进行更改是很有意义的。身份验证可确保只有可信赖的用户才能访问API。

怎么运行的


我们将使用基本的HTTP身份验证,该身份验证使用用户名和密码。用户名和密码以冒号分隔,格式如下username:password

然后使用Base64编码对该行admin:p@55w0Rd进行编码,因此该行将在下一行进行编码YWRtaW46cEA1NXcwUmQ=(尽管我建议使用比“ p @ 55w0Rd”更强的密码)。我们可以通过添加标头将此身份验证附加到我们的请求Authentication上一个示例的标题如下所示(其中“基本”表示密码使用基本的HTTP身份验证):

Authentication: Basic YWRtaW46cEA1NXcwUmQ=

Spring如何管理安全性


Spring提供了一个名为Spring Security的加载项,该加载项使身份验证高度可定制且非常简单。设置时,我们甚至可以使用在上一篇文章中学到的一些技能!

我们需要什么


  • MongoDB实例中的一个新集合称为“用户”
  • 用户集合中的一个新文档,具有以下字段(任何其他字段都是可选的,但都是必需的):用户名,密码(使用BCrypt算法散列,稍后再介绍)
  • 上一篇文章的来源

BCrypt用于密码哈希


散列是一种单向加密算法。实际上,经过哈希处理后,几乎不可能发现原始数据的样子。BCrypt哈希算法首先将一段文本加盐,然后将其哈希为60个字符的字符串。Java BCrypt编码器提供了一种matches检查字符串是否与哈希匹配的方法。例如,p@55w0Rd用BCrypt散列的密码可能具有含义$2b$10$Qrc6rGzIGaHpbgPM5kVXdeNZ9NiyRWC69Wk/17mttHKnDR2lW49KS当调用matchesBCrypt 方法以获取未加密和散列的密码时,我们得到一个value true可以使用Spring Security内置的BCrypt编码器生成这些哈希。

为什么要对密码进行哈希处理?


我们都听说过最近的网络攻击,这些攻击导致大公司的密码被盗。那么,为什么只建议在黑客入侵后更改我们的密码?因为这些大公司确保密码始终在其数据库中散列!

尽管在此类数据被黑客入​​侵后总是值得更改密码,但由于密码哈希是一种单向算法,因此很难找到用户的真实密码。实际上,破解散列正确的复杂密码可能需要数年时间这为防止密码被盗提供了更高级别的保护。Spring Security简化了哈希处理,因此真正的问题应该是:“为什么不呢?”

向MongoDB添加用户


我将添加集合所需的最少字段users(用户),因此数据库中包含用户的文档将仅包含username(用户名)和哈希BCrypt password(密码)。在此示例中,我的用户名将为admin,密码将为welcome1,但我建议在生产级API中使用更可靠的用户名和密码。

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

这些是MongoDB中所需的所有设置!其余的配置将在我们的Java代码中完成。

添加用户模型和存储库


上一篇文章详细介绍了Mongo的模型和存储库,因此在这里我不会详细介绍它们的工作方式-如果您想刷新自己的知识,请随时访问我的上一篇文章!

缺点是Spring需要知道文档user(模型)的外观以及如何访问user数据库(存储库)中的集合我们可以将这些文件分别放在模型和存储库的同一文件夹中,就像在上一个练习中一样。

模型


该模型将是Java基础类定制_idusernamepassword该文件将被命名为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文件包含了我们项目的所有依赖关系,我们将添加其中的几个依赖关系,因此让我们从打开该文件并向下滚动到tag开始 我们需要的唯一新依赖关系是spring-starter-securitySpring有一个内置的版本管理器,因此我们必须添加到标签中的依赖项如下:<dependencies>

<dependencies>

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

而且Maven将为我们下载源文件,因此我们的依赖项必须准备就绪!

创建身份验证服务


我们需要告诉Spring我们的用户数据在哪里以及在哪里找到身份验证所需的信息。为此,我们可以创建一个身份验证服务(Authentication Service)。让我们从在src/main/resources/java/[package name]称为services 的新文件夹开始,然后我们可以在该配置文件夹中使用name创建一个新文件MongoUserDetailsService.java

MongoUserDetailsS​​ervice.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指示该类可以嵌入到另一个文件中(例如,SecurityConfiguration文件,我们将通过几个部分进行介绍)。

注释@Autowired结束private UsersRepository repository;是实现的示例,此属性为我们提供了UsersRepository工作实例。注释@Override指示将使用此方法代替默认的UserDetailsS​​ervice方法。首先,此方法Users使用findByUsername我们在中声明的方法从MongoDB数据源获取对象UsersRepository

然后,该方法检查是否找到了用户。然后,向用户授予权限/角色(这可以为访问级别添加其他身份验证级别,但是一个角色足以完成本课程)。最后,该方法返回一个Spring对象Userusernamepasswordrole身份验证的用户。

创建安全配置


我们将需要重新定义一些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指示该类将包含Java Bean,在此进行详细描述。注释@EnableConfigurationProperties指示该类将包含为特殊配置Bean的内容。然后,该指令会将extends WebSecurityConfigurerAdapter父类映射WebSecurityConfigurerAdapter到我们的配置类,为我们的类提供确保其安全性规则得到遵守的一切必要条件。最后,该类将自动注入一个实例(@AutowiredMongoUserDetailsService,我们稍后可以在该文件中使用它。

认证步骤


接下来,我们需要告诉Spring Security我们要如何处理用户身份验证。默认情况下,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保护,因为API不需要它
  • authorizeRequests().anyRequest().authenticated():声明对任何端点的所有请求都必须得到授权,否则必须被拒绝。
  • and().httpBasic(): Spring因此,它需要基本的HTTP身份验证(如上所述)。
  • .and().sessionManagement().disable():告诉Spring不要为用户存储会话信息,因为API不需要这样做

添加一个Bcrypt编码器


现在我们需要告诉Spring使用BCrypt编码器来散列和比较密码-这听起来很困难,但实际上非常简单。我们可以通过简单地将以下行添加到我们的类中来添加此编码器SecurityConfiguration

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

和所有的工作!这个简单的bean告诉Spring我们要使用的PasswordEncoder是Spring 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使用有效和错误的身份验证来测试快速请求,以确保配置按计划进行。

不正确的用户名/密码

URL:http://localhost:8080/pets/
方法:GET
登录:Basic YWRtaW46d2VsY29tZQ==

回复:

401 Unauthorized

有效的用户名/密码

URL: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”
 }
]

结论


它可以正常工作!Spring Boot API的基本HTTP身份验证可能很棘手,但是希望本指南将有助于使其更加易懂。在当今的网络环境中,身份验证是必须的,因此诸如Spring Security之类的工具对于确保数据的完整性和安全性至关重要。

就这样!课程上与我见面

All Articles