欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

学习shiro第二天

程序员文章站 2022-11-08 13:31:05
昨天讲了shiro的认证流程以及代码实现,今天将对这个进行扩展。 因为我们的测试数据是shiro.ini文件中配置的静态数据,但实际上数据应该从数据库中查询出来才合理,因此我们今天讲讲JdbcRealm的使用。 本次需要的jar包如下: commons-beanutils-1.9.3.jarcomm ......

昨天讲了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表:

学习shiro第二天

如图,在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所得,大家如果有什么补充或者建议,麻烦在评论区留言,谢谢!