大家好!离开周末,我们将与您分享一篇文章,该文章在“ 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
。当调用matches
BCrypt 方法以获取未加密和散列的密码时,我们得到一个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基础类定制_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文件包含了我们项目的所有依赖关系,我们将添加其中的几个依赖关系,因此让我们从打开该文件并向下滚动到tag开始。
我们需要的唯一新依赖关系是spring-starter-security。Spring有一个内置的版本管理器,因此我们必须添加到标签中的依赖项如下:<
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
。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
指示该类可以嵌入到另一个文件中(例如,SecurityConfiguration文件,我们将通过几个部分进行介绍)。注释@Autowired
结束private UsersRepository repository
;是实现的示例,此属性为我们提供了UsersRepository
工作实例。注释@Override
指示将使用此方法代替默认的UserDetailsService方法。首先,此方法Users
使用findByUsername
我们在中声明的方法从MongoDB数据源获取对象UsersRepository
。然后,该方法检查是否找到了用户。然后,向用户授予权限/角色(这可以为访问级别添加其他身份验证级别,但是一个角色足以完成本课程)。最后,该方法返回一个Spring对象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
指示该类将包含Java Bean,在此进行详细描述。注释@EnableConfigurationProperties
指示该类将包含为特殊配置Bean的内容。然后,该指令会将extends WebSecurityConfigurerAdapter
父类映射WebSecurityConfigurerAdapter
到我们的配置类,为我们的类提供确保其安全性规则得到遵守的一切必要条件。最后,该类将自动注入一个实例(@Autowired
)MongoUserDetailsService
,我们稍后可以在该文件中使用它。认证步骤
接下来,我们需要告诉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之类的工具对于确保数据的完整性和安全性至关重要。就这样!在课程上与我见面。