我想将Apache Shiro与数据库身份验证一起使用。但我无法进行数据库设计更改。我想使用我的自定义SQL命令和Java逻辑来验证用户。这可能吗?我在shiro.ini中尝试了这个配置:
saltedJdbcRealm = com.crm.web.authentication.JdbcRealm
自定义Java类:
public class JdbcRealm extends AuthorizingRealm
{
@Resource(name = "jdbc/DefaultDB")
private DataSource dataSource;
protected static final String DEFAULT_AUTHENTICATION_QUERY = "select passwd from user where username = ?";
protected static final String DEFAULT_SALTED_AUTHENTICATION_QUERY = "select passwd, passwd_salt from user where username = ?";
protected static final String DEFAULT_USER_ROLES_QUERY = "select role_name from user_roles where username = ?";
protected static final String DEFAULT_PERMISSIONS_QUERY = "select permission from roles_permissions where role_name = ?";
private static final Logger log = LoggerFactory.getLogger(JdbcRealm.class);
public enum SaltStyle
{
NO_SALT, CRYPT, COLUMN, EXTERNAL
};
protected String authenticationQuery = DEFAULT_AUTHENTICATION_QUERY;
protected String userRolesQuery = DEFAULT_USER_ROLES_QUERY;
protected String permissionsQuery = DEFAULT_PERMISSIONS_QUERY;
protected boolean permissionsLookupEnabled = false;
protected SaltStyle saltStyle = SaltStyle.NO_SALT;
public void setDataSource(DataSource dataSource)
{
this.dataSource = dataSource;
}
public void setAuthenticationQuery(String authenticationQuery)
{
this.authenticationQuery = authenticationQuery;
}
public void setUserRolesQuery(String userRolesQuery)
{
this.userRolesQuery = userRolesQuery;
}
public void setPermissionsQuery(String permissionsQuery)
{
this.permissionsQuery = permissionsQuery;
}
public void setPermissionsLookupEnabled(boolean permissionsLookupEnabled)
{
this.permissionsLookupEnabled = permissionsLookupEnabled;
}
public void setSaltStyle(SaltStyle saltStyle)
{
this.saltStyle = saltStyle;
if (saltStyle == SaltStyle.COLUMN && authenticationQuery.equals(DEFAULT_AUTHENTICATION_QUERY))
{
authenticationQuery = DEFAULT_SALTED_AUTHENTICATION_QUERY;
}
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException
{
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = upToken.getUsername();
// Null username is invalid
if (username == null)
{
throw new AccountException("Null usernames are not allowed by this realm.");
}
Connection conn = null;
SimpleAuthenticationInfo info = null;
try
{
conn = dataSource.getConnection();
String password = null;
String salt = null;
switch (saltStyle)
{
case NO_SALT:
password = getPasswordForUser(conn, username)[0];
break;
case CRYPT:
// TODO: separate password and hash from getPasswordForUser[0]
throw new ConfigurationException("Not implemented yet");
//break;
case COLUMN:
String[] queryResults = getPasswordForUser(conn, username);
password = queryResults[0];
salt = queryResults[1];
break;
case EXTERNAL:
password = getPasswordForUser(conn, username)[0];
salt = getSaltForUser(username);
}
if (password == null)
{
throw new UnknownAccountException("No account found for user [" + username + "]");
}
info = new SimpleAuthenticationInfo(username, password.toCharArray(), getName());
if (salt != null)
{
info.setCredentialsSalt(ByteSource.Util.bytes(salt));
}
}
catch (SQLException e)
{
final String message = "There was a SQL error while authenticating user [" + username + "]";
if (log.isErrorEnabled())
{
log.error(message, e);
}
throw new AuthenticationException(message, e);
}
finally
{
JdbcUtils.closeConnection(conn);
}
return info;
}
private String[] getPasswordForUser(Connection conn, String username) throws SQLException
{
String[] result;
boolean returningSeparatedSalt = false;
switch (saltStyle)
{
case NO_SALT:
case CRYPT:
case EXTERNAL:
result = new String[1];
break;
default:
result = new String[2];
returningSeparatedSalt = true;
}
PreparedStatement ps = null;
ResultSet rs = null;
try
{
ps = conn.prepareStatement(authenticationQuery);
ps.setString(1, username);
// Execute query
rs = ps.executeQuery();
// Loop over results - although we are only expecting one result, since usernames should be unique
boolean foundResult = false;
while (rs.next())
{
// Check to ensure only one row is processed
if (foundResult)
{
throw new AuthenticationException("More than one user row found for user [" + username + "]. Usernames must be unique.");
}
result[0] = rs.getString(1);
if (returningSeparatedSalt)
{
result[1] = rs.getString(2);
}
foundResult = true;
}
}
finally
{
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(ps);
}
return result;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)
{
//null usernames are invalid
if (principals == null)
{
throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
}
String username = (String) getAvailablePrincipal(principals);
Connection conn = null;
Set<String> roleNames = null;
Set<String> permissions = null;
try
{
conn = dataSource.getConnection();
// Retrieve roles and permissions from database
roleNames = getRoleNamesForUser(conn, username);
if (permissionsLookupEnabled)
{
permissions = getPermissions(conn, username, roleNames);
}
}
catch (SQLException e)
{
final String message = "There was a SQL error while authorizing user [" + username + "]";
if (log.isErrorEnabled())
{
log.error(message, e);
}
// Rethrow any SQL errors as an authorization exception
throw new AuthorizationException(message, e);
}
finally
{
JdbcUtils.closeConnection(conn);
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roleNames);
info.setStringPermissions(permissions);
return info;
}
protected Set<String> getRoleNamesForUser(Connection conn, String username) throws SQLException
{
PreparedStatement ps = null;
ResultSet rs = null;
Set<String> roleNames = new LinkedHashSet<String>();
try
{
ps = conn.prepareStatement(userRolesQuery);
ps.setString(1, username);
// Execute query
rs = ps.executeQuery();
// Loop over results and add each returned role to a set
while (rs.next())
{
String roleName = rs.getString(1);
// Add the role to the list of names if it isn't null
if (roleName != null)
{
roleNames.add(roleName);
}
else
{
if (log.isWarnEnabled())
{
log.warn("Null role name found while retrieving role names for user [" + username + "]");
}
}
}
}
finally
{
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(ps);
}
return roleNames;
}
protected Set<String> getPermissions(Connection conn, String username, Collection<String> roleNames) throws SQLException
{
PreparedStatement ps = null;
Set<String> permissions = new LinkedHashSet<>();
try
{
ps = conn.prepareStatement(permissionsQuery);
for (String roleName : roleNames)
{
ps.setString(1, roleName);
ResultSet rs = null;
try
{
// Execute query
rs = ps.executeQuery();
// Loop over results and add each returned role to a set
while (rs.next())
{
String permissionString = rs.getString(1);
// Add the permission to the set of permissions
permissions.add(permissionString);
}
}
finally
{
JdbcUtils.closeResultSet(rs);
}
}
}
finally
{
JdbcUtils.closeStatement(ps);
}
return permissions;
}
protected String getSaltForUser(String username)
{
return username;
}
}
但是当我运行代码时,我得到了:
org.apache.shiro.authc.AuthenticationException: Authentication token of type [class org.apache.shiro.authc.UsernamePasswordToken] could not be authenticated by any configured realms. Please ensure that at least one realm can authenticate these tokens.
我在shiro.ini中错过了一些配置
答案 0 :(得分:4)
这就是我们在XML(shiro.xml)中的表现:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<bean id="logout" class="org.apache.shiro.web.filter.authc.LogoutFilter">
<property name="redirectUrl" value="YOUR_LOGIN_URL" />
</bean>
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login"/>
<property name="successUrl" value="YOUR_SUCCESS_URL"/>
<property name="unauthorizedUrl" value="YOUR_ACCESS_DENIED_URL"/>
<property name="filters">
<util:map>
<entry key="logout" value-ref="logout"/>
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
/** = authc <!--SPECIFY_OTHERS_FILTERS_CHAINS-->
</value>
</property>
</bean>
<bean id="builtInCacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager"/>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- Single realm app. If you have multiple realms, use the 'realms' property instead. -->
<property name="realm" ref="myRealm"/>
<property name="cacheManager" ref="builtInCacheManager"/>
<!-- By default the servlet container sessions will be used. Uncomment this line-->
<!-- to use shiro's native sessions (see the JavaDoc for more): -->
<!-- <property name="sessionMode" value="native"/> -->
</bean>
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- Define the Shiro Realm implementation you want to use to connect to your back-end -->
<!-- security datasource: -->
<bean id="myRealm" class="org.apache.shiro.realm.jdbc.JdbcRealm">
<property name="credentialsMatcher" ref="hashMatcher"/>
<property name="authenticationQuery" value="select password from user_login where user_id = ?"/>
<property name="userRolesQuery" value="YOUR_ROLE_QUERY"/>
<property name="permissionsQuery" value="YOUR_PERMISSION_QUERY" />
<property name="permissionsLookupEnabled" value="true"></property>
<property name="dataSource" ref="YOUR_DATA_SOURCE_NAME"/> <!-- i.e. being used for the DB connection -->
</bean>
<!-- Hash Matcher Bean responsible for matching credentials of logging user -->
<bean id="hashMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!-- Algorithm name -->
<property name="hashAlgorithmName" value="SHA-512"/>
<!-- No. of Hash Iterations. Note: must match with iterations used to save password in database. -->
<property name="hashIterations" value="10000"/>
<!-- true if Stored Credentials(i.e. password and salt) are in Hexadecimal form. False denotes BASE64 encoding.-->
<property name="storedCredentialsHexEncoded" value="true"/>
</bean>
<!-- Enable Shiro Annotations for Spring-configured beans. Only run after -->
<!-- the lifecycleBeanProcessor has run: -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
</beans>
您可以将其包含在应用程序配置文件(web.xml)
中答案 1 :(得分:2)
所有shiro都需要将身份验证的会话标记为AuthenticationInfo对象。它是如何构建的取决于你。 领域应该与安全管理器联系在一起。
答案 2 :(得分:2)
我想给你2条建议。希望它会对你有所帮助。
尚未为Realm完全配置配置文件。 您应该为AuthorizingRealm编写一个类,然后将该类配置为配置文件。
如果使用弹簧,则配置如下所示:
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="localRealm" />
</bean>
<bean id="localRealm" class="com.xxxx.xxxxx.infra.LocalSecurityRealm">
<constructor-arg index="0" ref="securityApplication" />
</bean>
在shiro.ini
配置文件
authenticator = com.realm.MyRealm
您需要首先确保实际到达supports()
并执行。
@Override
public boolean supports(AuthenticationToken authenticationToken) {
return (authenticationToken instanceof UsernamePasswordToken)
}
如果您有多个领域且一个抛出错误,则不会处理其他领域。 因此,如果你需要解决抛出的异常,你可以为authz做this,为authc做this。