内容简介:Java for Web学习笔记(一零七):Spring框架中使用JPA(7)密码和BCrypt
密码安全存放
密码安全有两个方面:
- 用户密码传递的安全性,可以使用https来保护,也有将密码进行哈希(例如MD5)后进行传递的(复杂一点的,密码是参与哈希,还包含一些动态参数,例如时间戳等进行salt),或者两者结合起来
- 数据库密码存储的安全性,一旦被拖库,或者别的被黑,仍能安全保护好用户的密码。
目前,一般可采用BCrypt加密方式,我们绝不能将密码的明文,或者经过弱哈希(如MD5和SHA)就存放在数据库中。BCrypt作为工业级产品,为每个密码产生不同的salt,使得字典生产困难得多,而MD5和SHA的破译则简单得多。
数据库表格
BCrypt加密后需要60字节存放,下面是一个存放表格的例子:
CREATE TABLE UserPrincipal ( UserId BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, Username VARCHAR(30) NOT NULL, HashedPassword BINARY(60) NOT NULL, UNIQUE KEY UserPrincipal_Username (Username) ) ENGINE = InnoDB;
bcrypt加密后的字符串形如:$2a$10$vacuqbDw9I7rr6RRH8sByuktOzqTheQMfnK3XCT2WlaL7vt/3AMby,其中:$是分割符,无意义;2a是bcrypt加密版本号;10是cost的值;而后的前22位是salt值;再然后的字符串就是密码的密文了;
我们将使用UserPrincipal作为Entity,代码如下:
/** 考虑到在session中存放Principal,我们将实现Principal接口,并提供Colonable接口 * 对于principal,我们如何判断其相同(本例采用用户名),将重写对象的hashcode()、equals()和toString()。*/ @Entity @Table(uniqueConstraints={ @UniqueConstraint(name = "UserPrincipal_Username",columnNames="Username")}) public class UserPrincipal implements Principal,Cloneable,Serializable{ private static final long serialVersionUID = 1L; private static final String SESSION_ATTRIBUTE_KEY = "cn.wei.flowingflying.customer_support.user.principal"; private long id; private String username; private byte[] password; @Id @Column(name="UserId") @GeneratedValue(strategy=GenerationType.IDENTITY) public long getId() { return id; } public void setId(long id) { this.id = id; } @Basic public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } @Basic @Column(name = "HashedPassword") public byte[] getPassword() { return password; } public void setPassword(byte[] password) { this.password = password; } @Override @Transient public String getName() { return this.username; } /* 对于Principal而言,重要的是两者之间的比较,因此重写object的下面几个方法 */ @Override @Transient public int hashCode() { return this.username.hashCode(); } @Override @Transient public boolean equals(Object obj) { return obj instanceof UserPrincipal && ((UserPrincipal)obj).username.equals(this.username); } @Override @Transient protected UserPrincipal clone() { try { return (UserPrincipal) super.clone(); } catch (CloneNotSupportedException e) { throw new RuntimeException(e); //转成RuntimeException,使得transaction可以回滚 } } @Override @Transient public String toString() { return this.username; } /* 对于提供静态的方法供调用,以便在session中存放和获取principal */ public static Principal getPrincipal(HttpSession session){ return session == null ? null : (Principal)session.getAttribute(SESSION_ATTRIBUTE_KEY); } public static void setPrincipal(HttpSession session, Principal principal){ session.setAttribute(SESSION_ATTRIBUTE_KEY, principal); } }
密码校验
引入jbcrypt
jbcrypt:OpenBSD-style Blowfish password hashing for Java industry-standard jBCrypt Java implementation of the BCrypt hash algorithm.
在pom.xml中
<dependency> <groupId>org.mindrot</groupId> <artifactId>jbcrypt</artifactId> <version>0.4</version> </dependency>
repository接口的实现
之前已经介绍了通用的repository接口的实现,我们需要加上通过UNIQUE KEY(username)的获取,具体如下:
public interface UserRepository extends GenericRepository<Long, UserPrincipal>{ UserPrincipal getByUsername(String username); }
@Repository public class DefaultUserRepository extends GenericJpaRepository<Long, UserPrincipal> implements UserRepository{ @Override public UserPrincipal getByUsername(String username) { // 【方式1】通过Java Persistence query,即JPQL // return this.entityManager.createQuery( // "SELECT u FROM UserPrincipal u WHERE u.username = :username", // UserPrincipal.class // ).setParameter("username", username).getSingleResult(); // 【方式2】通过JPA的标准接口 CriteriaBuilder builder = this.entityManager.getCriteriaBuilder(); CriteriaQuery<UserPrincipal> query = builder.createQuery(this.entityClass); Root<UserPrincipal> root = query.from(this.entityClass); return this.entityManager.createQuery( query.select(root).where(builder.equal(root.get("username"), username)) ).getSingleResult(); } }
密码校验相关代码
@Validated public interface AuthenticationService { Principal authenticate(@NotBlank(message = "{validate.authenticate.username}") String username, @NotBlank(message = "{validate.authenticate.password}") String password); void saveUser(@NotNull(message = "{validate.authenticate.saveUser}") @Valid UserPrincipal principal, String newPassword ); }
@Service public class DefaultAuthenticationService implements AuthenticationService{ private static final Logger log = LogManager.getLogger(); private static final SecureRandom RANDOM; private static final int HASHING_ROUNDS = 10; static { try { RANDOM = SecureRandom.getInstanceStrong(); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException(e); } } @Inject UserRepository userRepository; @Override @Transactional public Principal authenticate(@NotBlank(message = "{validate.authenticate.username}") String username, @NotBlank(message = "{validate.authenticate.password}") String password) { UserPrincipal principal = this.userRepository.getByUsername(username); if(principal == null){ log.warn("Authentication failed for non-existent user {}.", username); return null; } if(!BCrypt.checkpw(password, new String(principal.getPassword(),StandardCharsets.UTF_8))){ log.warn("Authentication failed for user {}.", username); return null; } log.debug("User {} successfully authenticated.", username); return principal; } @Override @Transactional public void saveUser(@NotNull(message = "{validate.authenticate.saveUser}") @Valid UserPrincipal principal, String newPassword) { if(newPassword != null && newPassword.length() > 0){ String salt = BCrypt.gensalt(HASHING_ROUNDS, RANDOM); principal.setPassword(BCrypt.hashpw(newPassword, salt).getBytes()); } if(principal.getId() < 1) this.userRepository.add(principal); else this.userRepository.update(principal); } }相关链接: 我的Professional Java for Web Applications相关文章
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 安全指南 | 某交易所框架严重逻辑漏洞,可重置任意用户密码!
- Mysql用户忘记密码及密码过期问题的处理方法
- 解决记不住密码的问题,“密码戒指”会是最佳方案吗?
- 密码学初学者可以理解的密码学库
- 用户密码加密存储十问十答,一文说透密码安全
- 密码学幼稚园 | 密码朋克的社会实验(二)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
正则表达式必知必会(修订版)
福达 (Ben Forta) / 杨涛 / 人民邮电出版社 / 2015-1-1 / 29.00元
《正则表达式必知必会》从简单的文本匹配开始,循序渐进地介绍了很多复杂内容,其中包括回溯引用、条件性求值和前后查找,等等。每章都为读者准备了许多简明又实用的示例,有助于全面、系统、快速掌握正则表达式,并运用它们去解决实际问题。正则表达式是一种威力无比强大的武器,几乎在所有的程序设计语言里和计算机平台上都可以用它来完成各种复杂的文本处理工作。而且书中的内容在保持语言和平台中立的同时,还兼顾了各种平台之......一起来看看 《正则表达式必知必会(修订版)》 这本书的介绍吧!