使用 Spring Data REST 进行密码编码
Posted
技术标签:
【中文标题】使用 Spring Data REST 进行密码编码【英文标题】:Password encoding with Spring Data REST 【发布时间】:2015-07-27 10:36:16 【问题描述】:我应该如何使用 Spring Data REST 自动对我的实体提交的纯密码字段进行编码?
我正在使用 BCrypt 编码器,我想在客户端通过 POST、PUT 和 PATCH 发送请求时自动对请求的密码字段进行编码。
@Entity
public class User
@NotNull
private String username;
@NotNull
private String passwordHash;
...
getters/setters/etc
...
首先我尝试使用@HandleBeforeCreate 和@HandleBeforeSave 事件侦听器来解决,但它的参数中的用户已经合并,所以我无法区分用户的新密码或旧密码哈希:
@HandleBeforeSave
protected void onBeforeSave(User user)
if (user.getPassword() != null)
account.setPassword(passwordEncoder.encode(account.getPassword()));
super.onBeforeSave(account);
是否可以在 setter 方法上使用 @Projection 和 SpEL?
【问题讨论】:
【参考方案1】:你可以实现一个Jackson JsonDeserializer:
public class BCryptPasswordDeserializer extends JsonDeserializer<String>
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException
ObjectCodec oc = jsonParser.getCodec();
JsonNode node = oc.readTree(jsonParser);
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String encodedPassword = encoder.encode(node.asText());
return encodedPassword;
并将其应用于您的 JPA 实体属性:
// The value of the password will always have a length of
// 60 thanks to BCrypt
@Size(min = 60, max = 60)
@Column(name="password", nullable = false, length = 60)
@JsonDeserialize(using = BCryptPasswordDeserializer.class )
private String password;
【讨论】:
这是一个非常好的答案 - 对于这个问题。但是,这仅在将实体反序列化为 Java 对象时才有效。对于 REST api,这已经足够了,但是如果您的 api 也很流畅,或者以某种方式尝试使用 JPA 存储库在您的代码中保存实体,您将无法再从中受益。然后密码将按原样保留。知道我们如何实现后者吗?【参考方案2】:修改密码字段的setter方法即可,如下图:
public void setPassword(String password)
PasswordEncoder encoder = new BCryptPasswordEncoder();
this.password = encoder.encode(password);
参考: https://github.com/charybr/spring-data-rest-acl/blob/master/bookstore/src/main/java/sample/sdr/auth/bean/UserEntity.java
【讨论】:
它不会起作用,因为我不使用分离的 DTO,但我的应用程序与持久层共享实体,因此由 ORM(在本例中为 Hibernate)设置的密码哈希将被再次编码。跨度> 但@javax.persistence.Access(AccessType.FIELD) 似乎解决了与密码设置器的休眠冲突。 这可能不起作用,因为如果您的AuthenticationProvider
也使用密码编码器(并且应该),您将遇到不匹配,因为 AuthenticationProvider
将尝试对已编码的密码,因此认证失败。虽然没有downvotes。别往心里放。很好的建议,但不会奏效。【参考方案3】:
对@robgmills JsonDeserializer
解决方案的一些增强:
DelegatingPasswordEncoder
。更灵活,见spring docs。
不必每次反序列化时都创建PasswordEncoder
。
一个大项目可能有多个JsonDeserializer
- 最好让它们成为内部类。
通常为获取请求编码隐藏密码。我用过@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
,见https://***.com/a/12505165/548473
对于 Spring Boot 代码如下:
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
public static final PasswordEncoder PASSWORD_ENCODER = PasswordEncoderFactories.createDelegatingPasswordEncoder();
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception
auth.userDetailsService(userDetailsService()).passwordEncoder(PASSWORD_ENCODER);
....
public class JsonDeserializers
public static class PasswordDeserializer extends JsonDeserializer<String>
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException
ObjectCodec oc = jsonParser.getCodec();
JsonNode node = oc.readTree(jsonParser);
String rawPassword = node.asText();
return WebSecurityConfig.PASSWORD_ENCODER.encode(rawPassword);
...
@Entity
public class User ...
@Column(name = "password")
@Size(max = 256)
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@JsonDeserialize(using = JsonDeserializers.PasswordDeserializer.class)
private String password;
...
【讨论】:
以上是关于使用 Spring Data REST 进行密码编码的主要内容,如果未能解决你的问题,请参考以下文章
spring-data-rest 和控制器,使用相同的 objectMaper 进行序列化/反序列化
使用@RestController在Spring中进行分页,但不使用Spring Data Rest Pageable?
使用来自 Spring Security 的密码来验证 REST 调用
默认情况下,不要使用Spring Data Rest和Jpa公开Entity类中的字段
在 Spring Boot Data REST 中进行实体验证后运行 @HandleBeforeCreate
来自 UI 的 Rest Spring 身份验证调用:在请求正文中传递用户名和密码,而不是在 url 参数中,以使用 Spring Security 进行身份验证