Spring 春季安全-随机盐+;散列+;自定义用户详细信息服务
最后一个问题,希望我能回答。到目前为止,我已经实现了自己的自定义UserDetails和UserDetailsService类,这样我就可以传递创建密码时使用的随机salt。密码的哈希为SHA512。然而,在尝试登录时,我总是发现user/pw组合不正确,我似乎不知道为什么 我将散列和盐作为blob存储在数据库中,您知道问题所在吗Spring 春季安全-随机盐+;散列+;自定义用户详细信息服务,spring,spring-security,Spring,Spring Security,最后一个问题,希望我能回答。到目前为止,我已经实现了自己的自定义UserDetails和UserDetailsService类,这样我就可以传递创建密码时使用的随机salt。密码的哈希为SHA512。然而,在尝试登录时,我总是发现user/pw组合不正确,我似乎不知道为什么 我将散列和盐作为blob存储在数据库中,您知道问题所在吗 Security-applicationContext.xml <?xml version="1.0" encoding="UTF-8"?> <b
Security-applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.xsd">
<sec:http auto-config='true' access-denied-page="/access-denied.html">
<!-- NO RESTRICTIONS -->
<sec:intercept-url pattern="/login.html" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<sec:intercept-url pattern="/*.html" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<!-- RESTRICTED PAGES -->
<sec:intercept-url pattern="/admin/*.html" access="ROLE_ADMIN" />
<sec:intercept-url pattern="/athlete/*.html" access="ROLE_ADMIN, ROLE_STAFF" />
<sec:form-login login-page="/login.html"
login-processing-url="/loginProcess"
authentication-failure-url="/login.html?login_error=1"
default-target-url="/member" />
<sec:logout logout-success-url="/login.html"/>
</sec:http>
<beans:bean id="customUserDetailsService" class="PATH.TO.CustomUserDetailsService"/>
<beans:bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.ShaPasswordEncoder">
<beans:constructor-arg value="512"/>
</beans:bean>
<sec:authentication-manager>
<sec:authentication-provider user-service-ref="customUserDetailsService">
<sec:password-encoder ref="passwordEncoder">
<sec:salt-source user-property="salt"/>
</sec:password-encoder>
</sec:authentication-provider>
</sec:authentication-manager>
</beans:beans>
调用方法:
byte[] salt = generateSalt();
byte[] hash = generateHash(salt, password);
Which I then store in the db.
我认为,值得指出的是,将用于每个用户密码的salt存储在数据库的salt列中(尽管很常见)会带来漏洞。salt的首要原因是防止针对受损数据库的字典攻击。如果攻击者可以访问您的数据库,并且没有使用salt,那么他们可以对标准字典中的每个单词应用通用哈希算法来创建新的哈希字典。当他们在数据库中找到其中一个单词的匹配项时,他们会查阅自己词典中的映射,以查找在应用算法时生成该哈希的原始未删除单词。瞧!攻击者拥有密码 现在。。。如果你使用了一种盐,并且每个用户的盐都不一样,那么你就在攻击计划中使用了一个巨大的扳手。但是如果您将每个用户的salt存储在数据库中(并通过将列命名为“salt”使其变得明显),那么实际上您并没有对该攻击计划造成太多干扰 我所知道的最安全的方法就是这样
假设你理解我刚才所说的一切的优点,这里还有一个相当大的优势,我刚才所说的实际上比你已经在做的更容易实现。当正确的事情变成更容易的事情时,爱它 我遇到了同样的原始问题,但一直没有得到回答,因此希望这能在将来节省一些时间: Spring Security默认情况下在比较摘要之前添加大括号。我错过了,转了好几个小时(哦) 确保存储(或生成)用大括号括起来的盐值(即,当Spring说“{salt}”时,它们的真正意思是“打开大括号+盐值+关闭大括号”
我想这对大多数人来说是显而易见的,但直到我最终调试到它之前,我才注意到这一点。如果他们有源代码,他们不知道salt是如何生成的吗?他们会从代码中获得字符串文本,他们会知道db中的字段。如果是像join date或user id这样简单的东西,就不会是h很难破解,更不用说只在网站上查找了。不管怎样,即使我像你提到的那样实现了它,这仍然不能解决SpringSecurity无法正确验证密码的问题。问题是变量类型byte[]吗?@Kent“salt的首要原因是防止对受损数据库的字典攻击。"这是错误的,SALT用于防止预计算攻击,而不是字典攻击。它还可以防止帐户之间的共享密码。如果密码被破解,则您只能访问该帐户,而不是该帐户以及使用相同密码的所有人。您基本上是通过obs提倡安全好奇。@Felix,你说得对。如果有人能看到源代码和数据库,他们就能找出一种盐,但这比将盐存储在数据库中要好,因为这样一来,有人需要访问数据库,才能为给定的用户学习盐。re:你原来的问题-很抱歉,我没能回答。再看一遍……有一个灾难SS和数据库之间关于散列密码是什么的争论。解决这一分歧的最好方法是让将密码写入数据库的代码使用passwordEncoder bean来计算散列。@Scott,如果我有什么错误,那只是“字典”攻击标签。但是,详细信息是正确的。如果您将salt存储在一个名为salt的列中的用户表中,您刚刚通知了一个已经泄露了您的数据库的被攻击者,该用户的密码使用了什么salt。现在,他们只需从一些算法中进行猜测,就可以执行您的密码“预先计算的攻击”可能会给一个给定的用户产生密码。当盐源不明显,在数据库和代码之间分裂时,攻击向量就被关闭了(我知道“默默无闻的安全”)是不好的,但是这真的是一个警告,而不是只执行“默默无闻的安全”。(指任何彻底的安全解决方案中的许多)合法组件。如果你怀疑我,我会问你什么是哈希值或加密值……它本身不是隐藏的信息吗?)
public class CustomUserDetailsService implements UserDetailsService {
private User_dao userDao;
@Autowired
public void setUserDao(User_dao userDao) {
this.userDao = userDao;
}
@Override
public CustomUserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
MyUser myUser = new MyUser();
myUser.setUsername(username);
try {
userDao.getUserByUsername(myUser);
} catch (Throwable e) {
}
if (myUser == null) {
throw new UsernameNotFoundException("Username not found", username);
} else {
List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
authList.add(new GrantedAuthorityImpl(myUser.getUserRole().getAuthority()));
int userID = myUser.getUserID();
boolean accountNonExpired = true;
boolean accountNonLocked = myUser.isNonLocked();
boolean credentialsNonExpired = true;
boolean enabled = myUser.isEnabled();
String password = "";
String salt = "";
password = new String(myUser.getHash);
salt = new String(myUser.getSalt());
CustomUserDetails user = new CustomUserDetails(userID, authList, username, password, accountNonExpired, accountNonLocked, credentialsNonExpired, enabled, salt);
return user;
}
}
}
public byte[] generateSalt() throws NoSuchAlgorithmException {
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
byte[] salt = new byte[20];
random.nextBytes(salt);
return salt;
}
public byte[] generateHash(byte[] salt, String pass) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA-512");
digest.update(salt);
byte[] hash = digest.digest(pass.getBytes());
return hash;
}
byte[] salt = generateSalt();
byte[] hash = generateHash(salt, password);
Which I then store in the db.