昨天讲了shiro的认证流程以及代码实现,今天将对这个进行扩展。
因为我们的测试数据是shiro.ini文件中配置的静态数据,但实际上数据应该从数据库中查询出来才合理,因此我们今天讲讲JdbcRealm的使用。
本次需要的jar包如下:
commons-beanutils-1.9.3.jar
commons-logging-1.2.jar
jcl-over-slf4j-1.7.12.jar
log4j-1.2.16.jar
shiro-all-1.4.1.jar
slf4j-api-1.7.25.jar
slf4j-log4j12-1.6.4.jar
mysql-connector-java-5.1.47.jar
mchange-commons-java-0.2.11.jar
c3p0-0.9.5.2.jar
IniSecurityManagerFactory部分源码:
public static final String MAIN_SECTION_NAME = "main";
public static final String SECURITY_MANAGER_NAME = "securityManager";
public static final String INI_REALM_NAME = "iniRealm";
......
protected Map<String, ?> createDefaults(Ini ini, Ini.Section mainSection) {
Map<String, Object> defaults = new LinkedHashMap<String, Object>();
SecurityManager securityManager = createDefaultInstance();
defaults.put(SECURITY_MANAGER_NAME, securityManager);
if (shouldImplicitlyCreateRealm(ini)) {
Realm realm = createRealm(ini);
if (realm != null) {
defaults.put(INI_REALM_NAME, realm);
}
}
......
从这段源码可以看到,IniSecurityManagerFactory在初始化时默认将iniRealm设置到SecurityManager中,除非我们自己设置SecurityManager使用哪个realm,它获取判断realm不为空,才将我们设置的realm设置到SecurityManager中,因此我们要在shiro.ini中配置新的realm即JdbcRealm。
JdbcRealm部分源码:
/**
* The default query used to retrieve account data for the user.
*/
protected static final String DEFAULT_AUTHENTICATION_QUERY = "select password from users where username = ?";
/**
* The default query used to retrieve account data for the user when {@link #saltStyle} is COLUMN.
*/
protected static final String DEFAULT_SALTED_AUTHENTICATION_QUERY = "select password, password_salt from users where username = ?";
/**
* The default query used to retrieve the roles that apply to a user.
*/
protected static final String DEFAULT_USER_ROLES_QUERY = "select role_name from user_roles where username = ?";
/**
* The default query used to retrieve permissions that apply to a particular role.
*/
protected static final String DEFAULT_PERMISSIONS_QUERY = "select permission from roles_permissions where role_name = ?";
......
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
......
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);
}
// Rethrow any SQL errors as an authentication exception
throw new AuthenticationException(message, e);
} finally {
JdbcUtils.closeConnection(conn);
}
return info;
}
从JdbcRealm的源码中我们看到了许多sql语句,都是从表users中去查询字段username、password、password_salt等等,而且源码中还出现了dataSource,而这个dataSource是需要我们set进去的,因此要使用JdbcRealm,我们必须在数据库创建表users以及对应的字段,还需要为JdbcRealm注入dataSource。下面我们开始配置shiro.ini文件。
shiro.ini文件:
[main]
dataSource=com.mchange.v2.c3p0.ComboPooledDataSource
dataSource.driverClass=com.mysql.jdbc.Driver
dataSource.jdbcUrl=jdbc:mysql://localhost:3306/test
dataSource.user=root
dataSource.password=root
myRealm=org.apache.shiro.realm.jdbc.JdbcRealm
myRealm.dataSource=$dataSource
securityManager.realm=$myRealm
差点忘了说,在shiro.ini文件中,有许多模块组成,比如昨天的[users]用来存放静态数据,和上面的[main]是用来配置SecurityManager和它所需要的依赖组件如realm的模块,当然还有配置用户角色的模块[role],我们这里就不多讲了,留到授权模块再将。
了解了[main]的应用,我们再看看上面的配置,首先我们配置了一个dataSource(这里配的是数据源c3p0,记得导包哦),包括配置它的数据库驱动、Url、数据库登录名以及密码,然后配了一个myRealm指向了JdbcRealm,再将上面的dataSource设置到myRealm中从而实现了dataSource的注入(这里的$代表注入,跟spring的注入异曲同工),再将这个myRealm设置成SecurityManager的默认realm从而完成配置。
配置完成了,接下来我们还要在数据库中创建users表:
如图,在test数据库下创建users表,其中必须包含JdbcRealm类中的sql语句中出现的字段,这里我们设置了三个。
创建完表,我们开始编码,流程其实跟第一天的差不多:
package test_JdbcRealm;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TestJdbcRealm {
private static final Logger logger = LoggerFactory.getLogger(TestJdbcRealm.class);
public static void main(String[] args) {
//1.创建securityManager工厂
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//2.获取securityManager实例
SecurityManager securityManager = factory.getInstance();
//3.将SecurityManager实例设置到SecurityUtils工具类中
SecurityUtils.setSecurityManager(securityManager);
//4。创建Subject实例
Subject subject = SecurityUtils.getSubject();
//5.获取token,模拟用户登陆
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123456");
try {
//6.认证token
subject.login(token);
if(subject.isAuthenticated()) {
logger.info("登陆成功");
}
} catch (AuthenticationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
logger.error("用户名或密码错误,登陆失败");
}
}
}
2019-07-25 23:30:43,705 INFO [com.mchange.v2.log.MLog] - MLog clients using slf4j logging.
2019-07-25 23:30:44,102 INFO [com.mchange.v2.c3p0.C3P0Registry] - Initializing c3p0-0.9.5.2 [built 08-December-2015 22:06:04 -0800; debug? true; trace: 10]
2019-07-25 23:30:44,211 INFO [org.apache.shiro.config.IniSecurityManagerFactory] - Realms have been explicitly set on the SecurityManager instance - auto-setting of realms will not occur.
2019-07-25 23:30:44,233 INFO [com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource] - Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> 2zm2h8a4bl36b813yacsm|7e0ea639, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceSynchronousCheckins -> false, forceUseNamedDriverClass -> false, identityToken -> 2zm2h8a4bl36b813yacsm|7e0ea639, idleConnectionTestPeriod -> 0, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/test, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 15, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 3, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ]
2019-07-25 23:30:44,525 INFO [org.apache.shiro.session.mgt.AbstractValidatingSessionManager] - Enabling session validation scheduler...
2019-07-25 23:30:44,537 INFO [test_JdbcRealm.TestJdbcRealm] - 登陆成功
同样是这几个步骤,但是在加载配置文件生成SecurityManager的实例却是不一样的,因为我们在配置文件中配置了SecurityManager的默认realm为JdbcRealm,因此在subjec.login(token)认证时,最终会通过JdbcRealm的认证方法从数据库中查出用户数据来进行比对认证!
总结:
1.在使用一个类的时候,我们需要通过源码去了解它需要什么,然后才能有目的地进行配置,就像JdbcRealm这个类,它需要一张表和数据源来实现从数据库查询出数据进行认证;
2.shiro.ini包含多个组成部分,如[main],[users],[role]还有[url],我们需要了解各个部分地主要功能和作用;
3.从上面地例子我们可以得知,SecurityManager使用地realm是可以进行配置的!!因此也就说明了我们可以自定义realm来实现认证和授权逻辑!这部分后续我学习到了将会进行分享。
以上是我今天学习shiro所得,大家如果有什么补充或者建议,麻烦在评论区留言,谢谢!
来源:oschina
链接:https://my.oschina.net/u/4349898/blog/3450492