好吧——我在这里转了一个星期,试图自己解决这个问题,但一无所获。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
(实现新的哈希密码匹配)。注意添加的PasswordService
,PasswordMatcher
,并更新了验证查询
,其中现在包括盐列。为了简洁起见,我只包含了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调用。。。
可以我做了更多的挖掘,似乎我不能像上面尝试的那样,直接将CDI注入Shiro JDBC领域。。。Shiro用自己的bean进行的管理与CDI用自己的bean进行的管理之间存在冲突。我确实找到了这个答案,它似乎试图在CDI和Shiro之间架起一座桥梁,方法是初始化领域,将用户DAO从CDI插入领域,然后将领域实例绑定到JNDI,以便稍后在Shiro中检索。ini
,然而,所有的示例/文档都是如此零碎,以至于我无法完全实现。
因此,我只是使用了我在问题中列出的日语教程中的自定义JdbcRealm
逐字记录。我不想为了bean中的DB交互而离开CDI,但是因为JDBC数据源已经在shiro中定义了。ini
,所有连接信息和查询都从那里直接拉入,而不需要在自定义域中硬编码/重新定义它们。至少没有完全的代码重复。。。为了完整起见,这里粘贴了我的最后一个自定义JdbcRealm
bean:
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中提取,那么在我的程序中存储用户名和密码的最佳方式是什么?现在它只是以明文形式存在。 有没有更安全的方法? 谢啦