根据Spring Data REST Documentation,POST方法从给定的请求主体创建一个新实体。但是,我发现它还可以更新现有实体。在某些情况下,这可能会有问题。这是一个例子:
DemoApplication.java
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
UserRepository.java
package com.example;
import org.springframework.data.repository.PagingAndSortingRepository;
public interface UserRepository extends PagingAndSortingRepository<User, String> {}
User.java
package com.example;
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
public class User {
@Id
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
pom.xml(在项目标签内)
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
application.properties:empty
网址:http://localhost:8080/users
方法:POST
JSON内容:
{"username":"user","password":"password"}
我假设上面的POST请求是第一次获得HTTP 201,而且只有一次。但是,我能够多次发送上述POST请求,并且始终获得HTTP 201。此外,我还可以使用POST请求更改数据库中的密码。
我认为这是一个安全问题。例如,我可能允许通过POST请求进行匿名用户注册。但是,在上述情况下,现有用户可能会被覆盖。
问题:如果旧实体已存在且具有相同的ID,如何阻止从POST请求创建新实体?或者,我是否错过了解释Spring Data REST文档?
补充说明:
此问题的原因是Spring Data REST背后的设计。因为Spring Data REST是基于Spring Data JPA构建的,它不会直接暴露给“外部”。所以它“信任”进来的数据。org.springframework.data.repository.core.support.AbstractEntityInformation中的新方法显示了如何将数据确定为新数据还是新数据。
public boolean isNew(T entity) {
ID id = getId(entity);
Class<ID> idType = getIdType();
if (!idType.isPrimitive()) {
return id == null;
}
if (id instanceof Number) {
return ((Number) id).longValue() == 0L;
}
throw new IllegalArgumentException(String.format("Unsupported primitive id type %s!", idType));
}
isNew方法的结果最终会影响org.springframework.data.jpa.repository.support.SimpleJpaRepository中的保存方法。
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
在此问题中提到的情况中,用户名字段(也是用户实体的ID)将始终包含数据以创建新用户。因此,当它转到isNew时,id == null将始终返回false。然后save方法将始终执行合并操作。
以上提示是我能提供的。即便如此,我不知道是否有解决方案来解决这个问题。
网址链接只是引用。它们可能与我使用的版本不完全相同。
答案 0 :(得分:0)
为了使实体与Spring Data REST(以及Spring Data JPA)一起正常工作,实体类需要实现Persistable。要重写的更明显的方法是isNew()。将调用此方法,而不是问题中提到的AbstractEntityInformation中的方法。要使实体知道自己的状态(新的或旧的),还需要版本变量。通过在显式字段上注释@Version,Spring Data JPA将更新此字段。因此,一旦首次构造实体,该字段就是它的默认值(null或0,具体取决于它使用的数据类型)。此外,由于Spring Data REST旨在向外界公开,为了保护版本不被滥用,@ JsonIgnore用于版本字段。
对于这个特定问题,需要将User.java类更改为:
package com.example;
import javax.persistence.*;
import org.springframework.data.domain.Persistable;
import com.fasterxml.jackson.annotation.JsonIgnore;
@Entity
public class User implements Persistable<String> {
/**
*
*/
private static final long serialVersionUID = 7509971300023426574L;
@Id
private String username;
private String password;
@Version
@JsonIgnore
private Long version;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
@Override
public String getId() {
return username;
}
@Override
public boolean isNew() {
return version == null;
}
}
正如@Alan Hay所提到的,还应对传入数据进行验证。这绝对是件好事。