当前位置: 首页 > 知识库问答 >
问题:

在Shiro 1.3.0中实现散列密码身份验证的问题

贾飞鸿
2023-03-14

好吧——我在这里转了一个星期,试图自己解决这个问题,但一无所获。Shiro留档/教程似乎在各种版本和过时的设计范例之间过于分散,以至于我很难弄清楚如何自己实现这一点。即使是官方留档也会留下很多空白,让新手茫然和困惑。我一直在关注这个(现在有些过时)关于在JSF应用程序中设置ApacheShiro身份验证的BalusC教程(链接:http://balusc.blogspot.sg/2013/01/apache-shiro-is-it-ready-for-java-ee-6.html)。我能够通过“散列密码”部分跟踪这篇文章,这是这篇文章真正开始显示它的年龄(声称不支持盐渍)的地方,并驱使我去其他地方寻找实现这个关键特性的方法。

以下是我开发环境的基础知识。

IDE:NetBeans 8.0.2
服务器:TomEE 1.7.1
数据库:MySQL 5.5
应用程序框架:
JSF 2.2
OmniFaces 1.8.1(尚未投入使用,除了我使用o:form标记以期待进一步使用)
JPA2.0(正如TomEE 1.7.1声称不支持2.1,至少NetBeans告诉我的那样…)
Apache DeltaSpike 1.2.1(仅限核心和JPA模块;协助CDI JPA持久化)
Apache Shiro 1.3.0-SNAPSHOT(仅限核心和Web模块)

我能够得到的工作:
-从MySQL数据库验证用户;读取原始/纯文本用户名和密码
-注册表单,以原始/纯文本格式将新用户保存到用户表

以下是与此相关的配置:
shiro。伊尼

[main]
# As per BalusC's guide for Ajax aware custom User Filter. Do not use @WebFilter annotation or web.xml to register this filter!
user = ds.nekotoba.filter.FacesAjaxAwareUserFilter
user.loginUrl = /faces/public/login.xhtml

#Define Realm
jdbcRealm = org.apache.shiro.realm.jdbc.JdbcRealm
jdbcRealm.permissionsLookupEnabled = true
jdbcRealm.authenticationQuery = SELECT password FROM users WHERE username = ?
jdbcRealm.userRolesQuery = SELECT role FROM userroles WHERE user_id = (SELECT id FROM users WHERE username = ?)

#Define Datasource
dataSource = com.mysql.jdbc.jdbc2.optional.MysqlDataSource
dataSource.serverName = localhost
dataSource.port = ####
dataSource.user = ********
dataSource.password = ********
dataSource.databaseName = ********
jdbcRealm.dataSource = $dataSource

#Add realm to securityManager
securityManager.realms = $jdbcRealm

[urls]
/faces/javax.faces.resource/** = anon
/faces/public/login.xhtml = user
/faces/public/app/** = user

用户创建方法Create()

public Long create(User user) {
    //Set created date
    user.setCreatedDate(new Date());

    //Persist to DB
    em.persist(user);
    return user.getId();
}

程序化用户登录方法(来自自定义JSF登录表单)

public void login() throws IOException {
    try {
        SecurityUtils.getSubject().login(new UsernamePasswordToken(username, password, remember));
        SavedRequest savedRequest = WebUtils.getAndClearSavedRequest(Faces.getRequest());
        Faces.redirect(savedRequest != null ? savedRequest.getRequestUrl() : Globals.HOME_URL);
    } catch (AuthenticationException ae) {
        Messages.addGlobalError("不明なユーザ、また試してみてください。");
        ae.printStackTrace();
    }
}

这一切都很简单,而且有效——但当我尝试将所有这些转换为使用哈希密码时,我的问题就出现了。我能够获得一个散列密码,并通过修改的create方法将相关的salt保存到DB中,但我似乎无法在我的shiro中获得正确的设置。ini使用这些散列用户成功登录。

更新了shiro.ini(实现新的哈希密码匹配)。注意添加的PasswordServicePasswordMatcher,并更新了验证查询,其中现在包括盐列。为了简洁起见,我只包含了ini文件的更新部分。

#Include salt column in authentication query
jdbcRealm.authenticationQuery = SELECT password, salt FROM users WHERE username = ?

#Use Default PasswordService/Matcher. This should use SHA-256, with 500,000 iteration hashing.
passwordMatcher = org.apache.shiro.authc.credential.PasswordMatcher
passwordService = org.apache.shiro.authc.credential.DefaultPasswordService
passwordMatcher.passwordService = $passwordService
jdbcRealm.credentialsMatcher = $passwordMatcher

更新了create()方法,该方法对密码进行散列,并提取salt以在数据库中单独存储。

public Long create(User user) {
    HashingPasswordService hps = new DefaultPasswordService();
    //Hash password given in registration form using the DefaultPasswordService,
    //this should use the same defaults at the PasswordService/Matcher defined in shiro.ini
    Hash hash = hps.hashPassword(user.getPassword());
    //Set user.password to hashed version for persisting to DB
    String hashedPass = hash.toBase64();
    user.setPassword(hashedPass);
    //Get related salt from hash and set in user object for persisting to DB
    String salt = hash.getSalt().toBase64();
    user.setSalt("salt");

    //Set created date
    user.setCreatedDate(new Date());

    //Persist to DB
    em.persist(user);
    return user.getId();
}

这就是我陷入困境的地方——我假设在这一点上我需要更新登录方法,以便通过从数据库中检索盐来处理用户输入密码的散列,并散列提交的密码,然后匹配存储的散列数据库的密码-但我尝试过的所有尝试都不成功。试图从上面使用log()方法进行身份验证只会给我一个错误,说明提供的凭据不匹配...

我已经尝试过在这个(日语)教程中实现一些代码(日本語が少し知っていますよ), 但我觉得有点不对劲(链接:http://d.hatena.ne.jp/apyo/touch/20120603/1338739210).Shiro不支持散列/腌制吗?我不明白为什么我必须重写/重写Shiro方法(根据那个教程)来完成对散列数据的用户身份验证,这是框架所支持的。我会认为有一些交付的方法,我应该使用,我只是找不到正确的一个。当然,可能是我散列/存储/检索用户数据的方式导致了问题……我对框架太陌生,不知道。任何帮助都将不胜感激。

编辑好的——所以似乎必须覆盖交付的JdbcRealm中的方法来实现用户密码盐的验证是“常态”。我已经实现了以下自定义领域,但是当调用该方法时,我得到了一个NullPointerExcema

CustomJdbcRealm。java(基于上述链接教程)

import java.util.List;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SaltedAuthenticationInfo;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.DefaultPasswordService;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.util.ByteSource;

/**
 * @author mousouchop
 *
 * Modification Log: 2015-01-17 Original.
 */
public class CustomJdbcRealm extends JdbcRealm {
    @Inject
    private EntityManager em;

    /**
     * 認証情報を返却する。
     * @param token
     * @return 
     */
    @Override
    protected SaltedAuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
        throws AuthenticationException {

        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        String username = upToken.getUsername();
        SaltedAuthenticationInfo info = null;

        System.out.println("TEST######## TOKEN-UN: "+upToken.getUsername());

        // Here I switched out a plain JDBC query for a JPA query (Perhaps this is part of my problem?)
        List<String> queryResults = em.createNamedQuery("User.ps")
                .setParameter("username", username)
                .getResultList();
        System.out.println("TEST######## RESULT CT: "+queryResults.size());
        String password = queryResults.get(0);
        String salt = queryResults.get(1);
        System.out.println("TEST######## PW: "+password);
        System.out.println("TEST######## SLT: "+salt);

        // ShiroデフォルトのjdbcRealmをそのまま使い、SecurityManagerをshiro.iniで初期化する方法だと、
        // saltStyleが設定できない。saltをAuthenticationInfoに渡す方法は用意されているが、
        // DefaultPasswordServiceを使うとバグ?でsaltが無視されてしまう(まだ調査中です)
        // とりあえずブログ用に強引にゴリゴリとHashクラスを生成する。
        // ハッシュアルゴリズムが固定になってしまっているが、
        // まぁよしとする。別に動的に生成しなければいけない場面もないだろうし。
        Sha256Hash credentials = Sha256Hash.fromBase64String(password);
            credentials.setSalt(ByteSource.Util.bytes(Base64.decode(salt)));
        // SimpleHashクラスとDefautoPasswordServiceでハッシュ回数のデフォルト値が異なる。
        // 整合性が取れてないとも言えるが、これはこれでいいのかな。
    credentials.setIterations(DefaultPasswordService.DEFAULT_HASH_ITERATIONS);
        // principals,credentials,realm名を設定して、AuthenticationInfoを生成する。
        info = new SimpleAuthenticationInfo(username, credentials, getName());

        return info;
    }
}

在我的“测试”输出行中,第一行返回输入的用户名。。。我在打印任何其他文件之前获得了NPE。也许我会尝试在这个类中使用普通的JDBC调用。。。

共有1个答案

柳均
2023-03-14

可以我做了更多的挖掘,似乎我不能像上面尝试的那样,直接将CDI注入Shiro JDBC领域。。。Shiro用自己的bean进行的管理与CDI用自己的bean进行的管理之间存在冲突。我确实找到了这个答案,它似乎试图在CDI和Shiro之间架起一座桥梁,方法是初始化领域,将用户DAO从CDI插入领域,然后将领域实例绑定到JNDI,以便稍后在Shiro中检索。ini,然而,所有的示例/文档都是如此零碎,以至于我无法完全实现。

因此,我只是使用了我在问题中列出的日语教程中的自定义JdbcRealm逐字记录。我不想为了bean中的DB交互而离开CDI,但是因为JDBC数据源已经在shiro中定义了。ini,所有连接信息和查询都从那里直接拉入,而不需要在自定义域中硬编码/重新定义它们。至少没有完全的代码重复。。。为了完整起见,这里粘贴了我的最后一个自定义JdbcRealmbean:

CustomJdbcRealm.java

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SaltedAuthenticationInfo;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.DefaultPasswordService;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.util.JdbcUtils;

/**
 * @author mousouchop
 *
 * Modification Log: 2015-01-17 Original.
 */
public class CustomJdbcRealm extends JdbcRealm {

    /**
     * 認証情報を返却する。(Override AuthenticationInfo method)
     */
    @Override
    protected SaltedAuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {

        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        String username = upToken.getUsername();
        Connection conn = null;
        SaltedAuthenticationInfo info = null;

        try {
            conn = dataSource.getConnection();
            // DBよりパスワード、SALTを取得する。(Get password and salt from DB)
            String[] queryResults = getPasswordForUser(conn, username);
            String password = queryResults[0];
            String salt = queryResults[1];

            // ShiroデフォルトのjdbcRealmをそのまま使い、SecurityManagerをshiro.iniで初期化する方法だと、
            // saltStyleが設定できない。saltをAuthenticationInfoに渡す方法は用意されているが、
            // DefaultPasswordServiceを使うとバグ?でsaltが無視されてしまう(まだ調査中です)
            // とりあえずブログ用に強引にゴリゴリとHashクラスを生成する。
            // ハッシュアルゴリズムが固定になってしまっているが、
            // まぁよしとする。別に動的に生成しなければいけない場面もないだろうし。
            Sha256Hash credentials = Sha256Hash.fromBase64String(password);
            credentials.setSalt(ByteSource.Util.bytes(Base64.decode(salt)));
            // SimpleHashクラスとDefautoPasswordServiceでハッシュ回数のデフォルト値が異なる。
            // 整合性が取れてないとも言えるが、これはこれでいいのかな。
            credentials.setIterations(DefaultPasswordService.DEFAULT_HASH_ITERATIONS);
            // principals,credentials,realm名を設定して、AuthenticationInfoを生成する。
            info = new SimpleAuthenticationInfo(username, credentials, getName());
        } catch (SQLException e) {
            final String message = String.format("次のユーザーの認証中にSQLエラーが発生しました。ユーザー:[%s]%n", username);
            throw new AuthenticationException(message, e);
        } finally {
            JdbcUtils.closeConnection(conn);
        }
        return info;
    }

    /**
     * DBよりユーザーに紐づくパスワードとSALTを取得する。 org.apache.shiro.realm.jdbc.JdbcRealmよりコピー。
     * saltStyleに関する記述を除去。
     *
     * @param conn
     * @param username
     * @return
     * @throws SQLException
     */
    private String[] getPasswordForUser(Connection conn, String username) throws SQLException {

        String[] result;
        result = new String[2];
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            // 親クラスにデフォルトで用意されているSQLか、
            // shiro.iniにもパスワード取得用SQLを定義できる。
            ps = conn.prepareStatement(authenticationQuery);
            ps.setString(1, username);
            rs = ps.executeQuery();

            // 複数レコードが取れた場合のチェックフラグ
            boolean foundResult = false;
            while (rs.next()) {

                // ループ2周めだとfoundResultはtrue=エラー
                if (foundResult) {
                    throw new AuthenticationException(
                            String.format("ユーザー:%sのデータが複数あります。ユーザー名に対応するユーザーは必ず一人でないといけません。%n", username));
                }

                // password
                result[0] = rs.getString(1);
                // password_salt
                result[1] = rs.getString(2);
                foundResult = true;
            }
        } finally {
            JdbcUtils.closeResultSet(rs);
            JdbcUtils.closeStatement(ps);
        }
        return result;
    }
}

因此,这与我上面问题中更新的shiro.ini创建()方法一起应该允许您使用Apache Shiro1.2.*/1.3.0-SNAPSHOT实现哈希用户身份验证。

 类似资料:
  • 嗨,我已经设法用SQLAuthenticator在WebLogic12c中实现了容器管理的身份验证。当特定于提供程序的sqlauthenticator设置中将密码设置设置为明文且数据库值未加密时,我将使用在数据库中创建的用户成功登录。 如果我使用以下代码将用户密码存储在数据库中,但我无法登录: 通过提供密码“weblogic1”,此值存储在DB中:{SHA-1}r49g3weqasgoe6odq+

  • 我制作了一个PDB,我可以通过sys作为sysdba连接到这个PDB,但是我不能连接我自己的普通用户或本地用户。 原因是ORA-01017:密码错误。 在sqlnet.ora中设置之后: 密码以quation标记开始结束,然后生效。 创建用户和登录用户的命令: 为什么?如何将SQLNET.ALLOWED\u LOGON\u VERSION\u SERVER升级到12。我可以设置没有引号的密码吗?

  • 我正在创建一个web应用程序,我需要安全地执行身份验证。阅读了大量的文章和帖子,我对实现得出了以下结论。 后端密码散列:使用PBKDF2,用另一个唯一的salt再次对#1的salted-hash进行散列,生成#2的salted-hash,然后用#2 salt存储在数据库中。 因此,数据库总共有#2 salted-hash、#1 salt和#2 salt。 因此,当授权发生时,将使用#1 salt对

  • 在此之后,程序被终止,并出现上述错误。

  • 有人能告诉我如何在docker容器中更改Cassandra.yaml吗?我想在docker中为cassandra访问启用密码身份验证。

  • 假设我试图从使用基本身份验证/基本证书的RESTful api中提取,那么在我的程序中存储用户名和密码的最佳方式是什么?现在它只是以明文形式存在。 有没有更安全的方法? 谢啦