• 学习shiro第二天


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

  • 相关阅读:
    http请求
    mac chrome NET::ERR_CERT_INVALID
    百度小程序获取用户手机号
    js 异步总结
    百度小程序 es6 支持情况
    每日日报
    每日日报
    每日日报
    05程序员修炼之道:从小工到专家阅读笔记之二
    05程序员修炼之道:从小工到专家阅读笔记之一
  • 原文地址:https://www.cnblogs.com/wujianwu/p/11247683.html
Copyright © 2020-2023  润新知