在我发布到课程网站上的第一个Spring Boot示例中,我将自己限制在Spring Boot的最基本特性上,因为我希望我们能够首先实现最基本的服务器特性。在这些课堂讲稿中,我将介绍Spring Boot的一些“很好拥有”的附加特性。
在这些课堂讲稿的顶部,你会发现一个按钮,你可以点击下载示例项目的更新版本。
与我们的服务器通信的客户端将使用HTTP协议。HTTP的关键特性之一是使用状态码。当客户端发送请求时,服务器将在响应体中使用状态码和数据进行响应。在请求不成功的情况下,状态码意味着向客户端提供一些关于哪里出错的反馈。此外,如果请求不成功,大多数服务器将在响应的正文中放置更详细的错误消息。
下面是一些Spring Boot控制器代码的示例,演示了如何在出现问题时设置状态码和错误消息。
@GetMapping("/login") public ResponseEntity<String> checkLogin(@RequestParam(value = "name") String user, @RequestParam(value = "password") String password) {user result = dao。findByNameAndPassword(用户、密码);if (result == null){返回ResponseEntity.status(HttpStatus.UNAUTHORIZED)。body(“无效的用户名或密码”);}返回ResponseEntity.ok().body(result.getKey());}
这里的方法用于处理登录过程。客户端将提供用户名和密码。如果用户名或密码无效,该方法调用的检查登录的dao方法被设置为返回null。
为了处理错误情况,我们需要设置响应代码并提供带有错误消息的自定义正文。为此,我们使用了一个ResponseEntity对象。使用ResponseEntity的第一步是更改控制器方法的返回类型,以返回ResponseEntity而不是常规数据类型。ResponseEntity充当状态码和正文的容器(如果需要的话,还可以包含响应头等附加元素)。为了构造一个ResponseEntity,我们使用了一个构建器模式:首先调用ResponseEntity类中的一个静态方法来启动这个过程,然后在第一个方法调用返回的对象上调用其他方法,直到我们完成构建对象的过程。在本例中,我们首先调用status()方法来设置响应中的状态码,然后调用body()方法来指定请求的主体。使用body()方法时需要注意的一个重要限制是,传递给body()方法的参数类型与ResponseEntity中尖括号中的类型非常匹配。
我们通过使用在HttpStatus枚举类型中定义的常量来指定要使用的状态码。在Http中有大量的状态码可供选择。本页提供了这些状态码的列表。
另一种类型的错误检查包括检查来自用户的输入,以确保它们是有效的。这种错误检查的过程通常被称为验证。
下面是验证的一个典型例子。下面是处理发布新用户的方法的代码。
@PostMapping public ResponseEntity<String> save(@RequestBody User User) {if (User . getname (). isblank () || User . getpassword (). isblank ()) {return ResponseEntity.status(HttpStatus.BAD_REQUEST)。主体(“空用户名或密码”);} String key = dao.save(user);if (key.equals("Duplicate")){返回ResponseEntity.status(HttpStatus.CONFLICT)。body(“用户名已经存在”);} else if (key.equals("Error")){返回ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)。body(“无法生成密钥”);}返回ResponseEntity.ok().body(key);}
这里需要执行的一个明显的验证检查是检查user对象中提供的用户名或密码是否为空。
验证可以捕获明显的错误,例如丢失数据或格式错误的数据。其他错误需要更仔细的检查。在本例中,验证检查可以找到缺失的名称或密码。额外的错误检查由DAO处理:例如,在向用户表中插入新用户之前,DAO将检查请求名称的用户是否已经存在。如果是这种情况,DAO方法将返回“Duplicate”。控制器方法中的代码只需要检查DAO方法返回的值,以查看在该处理级别是否捕获了任何错误。
在我介绍的Spring Boot赢博体育程序的第一个版本中,我们采用了一种非常简单的策略来处理密码。当我们在数据库中存储一个新的User时,我们只需将该用户的密码作为一个字段存储在users表中。不幸的是,这种简单的方法被认为是不安全的。这种方法的问题是,如果有人闯入您的数据库,他们可以窃取您赢博体育的用户名/密码组合,还可以从数据库中的个人资料信息中获取用户的电子邮件地址。这是一个问题,因为许多用户在多个站点上重复使用用户名/密码/电子邮件组合,所以您站点的安全漏洞间接地成为其他站点的安全漏洞。
此安全问题的标准解决方案是加密存储在数据库中的密码。在赢博体育程序的更新版本中,我将使用一种在现实世界中广泛使用的安全处理密码的策略:在数据库中存储盐渍和散列密码的实践。
这种安全实践从使用一个称为散列函数的特殊函数开始。哈希函数是一种加密算法,它将字符串作为输入,并将该字符串映射为一个非常大的整数,然后将该整数重新编码为字符串。哈希函数的特殊之处在于它们被设计为单向函数:给定一个输入字符串,哈希函数产生一个输出字符串,但是对于赢博体育实际目的,没有办法反转映射并恢复原始输入字符串。
为了使此过程更加安全,程序员通常将散列函数的使用与称为盐化的附加步骤结合起来。在这个版本的过程中,我们在通过哈希函数运行输入之前,将输入字符串与一个称为salt值的秘密值组合在一起。额外步骤的动机是它可以帮助防止通常所说的字典攻击。在字典攻击中,攻击者首先猜测您正在使用的散列函数,然后准备一长串常用密码。然后计算每个密码的哈希值,并将密码和哈希值的组合存储在字典中。如果攻击者随后窃取了您的散列密码数据库,那么他们可以通过在字典中进行反向查找来恢复您的许多用户的原始、未散列密码。在散列之前将密码加盐可以阻止这种攻击。如果攻击者在散列之前不知道您使用什么值来对密码加盐,他们就不能使用字典攻击。
为了在拍卖赢博体育程序中实现密码安全过程,我首先构造一个特殊的类,其中包含帮助我们散列和验证密码的方法:
进口org.springframework.stereotype.Service;进口com.password4j.BcryptFunction;进口com.password4j.Hash;进口com.password4j.Password;进口com.password4j.types.Bcrypt;@Service公共类PasswordService{静态私有最终字符串secret="CMSC455";public String hashPassword(String password) {BcryptFunction bcrypt = BcryptFunction. getinstance (bcrypt . getinstance)B、12);Hash Hash = password . Hash (password) .addPepper(secret) .with(bcrypt);返回hash.getResult ();}公共布尔验证哈希(字符串密码,字符串哈希){BcryptFunction bcrypt = BcryptFunction. getinstance (bcrypt . getinstance)。B、12);恢复密码。check(password, hash) .addPepper(secret) .with(bcrypt);}}
这段代码使用了一个流行的Java加密库password4j。为了在我的项目中使用这个库,我在pom.xml文件中添加了一个依赖项
<依赖> < groupId > com。password4j</groupId> <artifactId>password4j</artifactId> <version>1.6.1</version> </dependency>
然后从那个库中导入一些特殊用途的类。password4j库提供了各种散列函数:对于本例,我使用的是Bcrypt散列函数。password4j中的Password类提供了易于使用的hash()和check()函数来散列和验证散列后的密码。
我将这两个实用程序方法放入一个服务类中。为了在Spring Boot中创建一个服务类,我们在类声明前面添加了一个特殊的@Service注释。将这个注释附加到类上会使Spring在赢博体育程序启动时自动创建该类的一个实例。然后,该对象可以被注入到系统中需要它的任何其他类中。
下面的代码演示了如何在UserDAO类中使用这个新的服务类:
进口org.springframework.beans.factory.annotation.Autowired;进口org.springframework.jdbc.core.JdbcTemplate;进口org.springframework.jdbc.core.RowMapper;进口org.springframework.stereotype.Repository;进口edu.lawrence.auction.services.PasswordService;@Repository public类UserDAO {@Autowired private JdbcTemplate JdbcTemplate;PasswordService;public User findByNameAndPassword(String name,String password) {String sql = "SELECT * FROM users WHERE name=?";RowMapper<User> RowMapper = new UserRowMapper();用户结果= null;try {result = jdbcTemplate。queryForObject(sql, rowMapper, name);} catch(Exception ex) {} if(result != null && passwordService. net)verifyHash(password, result.getPassword())) {result.setPassword(“未公开”);} else{结果= null;}返回结果;}公共字符串保存(用户用户){//首先确保这不是一个重复的字符串sql = "SELECT * FROM users WHERE name=?";RowMapper<User> RowMapper = new UserRowMapper();用户旧= null;try {old = jdbcTemplate。查询forobject (sql, rowMapper, user.getName());} catch(Exception ex) {} if(old != null)返回“Duplicate”;//让MySQL生成唯一的id字符串idSQL = "select uuid()";String key = null;尝试{key = jdbcTemplate。queryForObject (idSQL String.class);} catch(Exception ex) {key = "Error";} if(key.equals("Error"))返回key;String hash = passwordService.hashPassword(user.getPassword());字符串insertSQL = "插入到用户(userid,name,password)的值(?, ?, ?);jdbcTemplate.update (insertSQL键,user.getName(),散列);返回键;}}
要自动将PasswordService类的实例注入到DAO中,我只需要在DAO类中声明一个适当类型的成员变量,并用@Autowired注释该成员变量。
在用于在数据库中存储用户的代码和用于检查用户名/密码组合的代码中,您现在可以看到我在将密码存储到数据库中之前使用密码服务类对密码进行加密,然后验证候选密码是否与数据库中的密码匹配。
对于我们构建的任何实际项目,我们最终都必须提出一种部署策略,使我们的服务器能够在互联网上访问。为了演示部署Spring Boot项目是多么容易,我已经在我控制的服务器上部署了这个项目。
服务器是我在家里安装的一台普通的Mac Mini电脑。我还注册了一个域cmsc106.net,并设置了一个DNS条目,将该域指向我的家庭网络。
在Mac Mini上,我开始使用自制软件安装Apache服务器。为了部署赢博体育程序,我还使用homebrew安装了Tomcat赢博体育服务器,然后我还配置了Apache服务器,将对cmsc106.net的赢博体育请求转发到Tomcat服务器。
为了准备将项目部署到Tomcat服务器,我对项目做了两个小的更改。
第一个变化是对项目中的赢博体育程序类做一个小的调整。我改变了
公共类AuctionApplication {
来
公共类AuctionApplication扩展SpringBootServletInitializer {
第二个变化是添加一行
<包装>战争> < /包装
到pom.xml文件。通常,在编译Spring Boot项目时,构建系统将生成一个可执行的jar文件作为最终项目。如果希望将项目部署到现有的Tomcat服务器,可以在pom.xml文件中放入这一行,告诉系统生成war文件而不是jar文件。一旦构建了war文件,您所要做的就是将war文件放入服务器上Tomcat文件夹中的webapps文件夹中。
您可以在Postman中看到赢博体育这些操作:只需用URL设置POST请求
http://cmsc106.net/auction/users
发布具有名称/密码组合的对象。如果您的发布成功,服务器将通过向您发回您创建的新用户的用户id进行响应。
现在,我们已经构造了第一个简单的Spring Boot赢博体育程序,它提供了一个简单的REST API,使用户能够创建新用户,并通过提供用户名和密码登录到系统。在软件开发的世界里,每当我们构建一个向用户提供一组功能的产品时,我们通常有义务向潜在用户提供文档来解释如何使用我们的系统。随着我们向赢博体育程序的API添加越来越多的功能,这个需求只会增长。
我在这个示例项目中添加的另一个特性涉及到Springdoc库的使用。为了将这个库添加到项目中,我首先在pom.xml文件中添加了一个依赖项:
<依赖> < groupId > org。springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>2.2.0</version> </dependency>
一旦我们将这个库添加到我们的项目中,我们就可以几乎免费地访问一个强大的新功能。您可以在我部署到cmsc106.net服务器的项目副本中看到这个新特性的运行情况。只要输入URL
http://cmsc106.net/auction/swagger-ui/index.html
在浏览器中查看springdoc为我的项目生成的文档页面。这个文档页面记录了当前UI提供的两种交互。当我将依赖项添加到项目中时,赢博体育这些都会自动生成。随着我为服务器构建API并添加大量新的交互,这个强大的新功能只会变得越来越有用。