0

希望我的最后一个问题。到目前为止,我已经实现了我自己的自定义 UserDetails 和 UserDetailsS​​ervice 类,以便我可以传递在创建密码时使用的随机盐。密码的哈希是 SHA512。但是,在尝试登录时,我总是得到用户/密码组合不正确,我似乎无法弄清楚原因。

我将哈希和盐作为 blob 存储在数据库中,关于问题所在的任何想法?

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>

CustomUserDetails.java

public class CustomUserDetails implements UserDetails {

    private int userID;
    private String username;
    private String password;
    private Collection<GrantedAuthority> authorities;
    private boolean accountNonExpired;
    private boolean accountNonLocked;
    private boolean credentialsNonExpired;
    private boolean enabled;
    private String salt;

    public CustomUserDetails() {
    }

    public CustomUserDetails(int userID, Collection<GrantedAuthority> authorities, String username, String password, boolean accountNonExpired, boolean accountNonLocked, boolean credentialsNonExpired, boolean enabled, String salt) {
        this.userID = userID;
        this.authorities = authorities;
        this.username = username;
        this.password = password;
        this.accountNonExpired = accountNonExpired;
        this.accountNonLocked = accountNonLocked;
        this.credentialsNonExpired = credentialsNonExpired;
        this.enabled = enabled;
        this.salt = salt;
    }

    @Override
    public Collection<GrantedAuthority> getAuthorities() {
        return authorities;
    }

    public int getUserID() {
        return userID;
    }

    public void setUserID(int userID) {
        this.userID = userID;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return accountNonExpired;
    }

    @Override
    public boolean isAccountNonLocked() {
        return accountNonLocked;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return credentialsNonExpired;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }

    public String getSalt() {
        return salt;
    }

    public void setAccountNonExpired(boolean accountNonExpired) {
        this.accountNonExpired = accountNonExpired;
    }

    public void setAccountNonLocked(boolean accountNonLocked) {
        this.accountNonLocked = accountNonLocked;
    }

    public void setAuthorities(Collection<GrantedAuthority> authorities) {
        this.authorities = authorities;
    }

    public void setCredentialsNonExpired(boolean credentialsNonExpired) {
        this.credentialsNonExpired = credentialsNonExpired;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setSalt(String salt) {
        this.salt = salt;
    }
}

CustomUserDetailsS​​ervice.java

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.
4

2 回答 2

3

我遇到了同样的原始问题,但从未得到解答,因此希望将来可以节省一些时间:

Spring-Security 在比较摘要之前默认添加大括号。我错过了这一点并旋转了几个小时(d'oh)。

确保存储(或生成)用大括号括起来的盐值(即,当 Spring 说“{salt}”时,它们真诚地表示“打开大括号 + 你的盐值 + 关闭大括号”。

我想这对大多数人来说是显而易见的,但直到我最终调试它时我才注意到它。

于 2013-02-16T18:39:57.987 回答
-3

我认为值得指出的是,将用于每个用户密码的 salt 存储在数据库的 salt 列中(尽管很常见)存在漏洞。加盐的原因首先是为了防止字典攻击受损的数据库。如果攻击者可以访问您的数据库并且没有使用盐,他们可以将通用哈希算法应用于标准字典中的每个单词,以创建新的哈希字典。当他们在数据库中找到这些单词之一的匹配项时,他们会查阅自己字典中的映射,以找到在应用算法时产生该散列的原始未散列单词。瞧!攻击者有密码。

现在......如果您使用盐并且每个用户的盐都不同,那么您会在该攻击计划中投入巨大的活动扳手。但是...如果您为数据库中的每个用户存储盐(并通过将列命名为“盐”来使其显而易见),那么您实际上并没有过多地干扰这个攻击计划。

我所知道的最安全的方法是这个。

  1. 在您的用户表中没有盐列。
  2. 在 UserDetails 类上实现 getSalt() 方法。让它返回用户注册时设置的其他用户属性,并且永远不会更改。例如加入日期。将其与在 UserDetails 类中硬编码的字符串文字/常量连接起来。

通过这种方式,盐对每个用户都是唯一的。从数据库中看不出什么值被用作盐。即使攻击者猜到了部分盐的用途,他/她也需要访问您的源代码以了解盐的 REST。这意味着您的数据库和应用程序代码都需要在您最终遇到真正的问题之前受到损害。

假设您了解我刚才所说的所有内容的优点,这里还有一个相当大的优势,因为我刚才所说的实际上比您已经在做的更容易实现。当正确的事情变得更容易时,喜欢它!

于 2012-05-18T19:20:38.057 回答