shiro自定义realm

我们知道shiro这个框架提供了信息认证和授权的功能性接口,但是shiro是不会帮我们维护数据的,shiro中的用户信息以及用户所对应的权限都是需要我们从数据库查询出来然后传给shiro相对应的接口,因此单单一个jdbcRealm已经无法满足我们的需求了,因为jdbcRealm是写死了的,里面查询的只能是users表。所以,为了满足我们的需求,我们必须自定义realm,从而才能不局限于一张表的数据查询,还能加自己的一些判断逻辑。下面讲讲怎么实现自定义realm。

自定义realm首先我们就要写一个realm,而这个realm我们一般要继承AuthorizingRealm类,因为这个类里面就有实现接收用户认证信息和接收用户权限信息的两个方法,而realm就是用来从数据库查询这些数据的。下面是我自定义的realm:

package com.wujianwu.realm;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import javax.sql.DataSource;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

import com.wujianwu.bean.User;

public class MyRealm  extends AuthorizingRealm{

    private DataSource dataSource;
    @Override
    public String getName() {
        // TODO Auto-generated method stub
        return "myRealm";
    }
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String userName = (String) token.getPrincipal();
        SimpleAuthenticationInfo info = null;
        
        User user = getUserInfo(userName);
        System.out.println("用户名"+user.getUsername()+"==============");
        info = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(),getName());
        
        return info;
    }
    
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
        // TODO Auto-generated method stub
        return null;
    }
    public DataSource getDataSource() {
        return dataSource;
    }
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    
    private User getUserInfo(String username){
        User user = new User();
        String sql = "select username,password from users where username=?";
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet set = null;
        try {
            connection = dataSource.getConnection();
            statement = connection.prepareStatement(sql);
            statement.setString(1, username);
            set = statement.executeQuery();
            
            if(set.next()) {
                String username1 = set.getString(1);
                String password = set.getString(2);
                user.setPassword(password);
                user.setUsername(username1);
            }    
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally {
            closeAll(connection, set, statement);
        }
        return user;
    }
    
    private void closeAll(Connection conn,ResultSet set,PreparedStatement statement) {
        try {
            if(set != null) {
                set.close();
            }
            if(statement != null) {
                statement.close();
            }
            if(conn != null) {
                conn.close();
            }
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

可以看到,我的realm只编写了接收认证信息这个方法的代码,毕竟我们还没学到授权= =。我这里其实也是模仿了jdbcRealm,在MyRealm中设置了数据源的属性,然后通过subject.login(token)中传过来的token来获取用户输入的身份,然后根据身份区数据库查询出用户的凭证和其他信息(可以看到这里我用的还是users表,是因为我太懒了,直接用现有的表,当然这里是可以查询任何你想要查询的表的!),然后用simpleAuthenticationInfo封装用户的信息返回给shiro去认证,实际上这里我们只是从数据库将用户数据查询出来封装然后返回给shiro而已,最终的认证还是shiro帮我们完成的。(注意,记得关闭资源,如我代码中的closeAll方法)

好了,写好了自定义的realm,我们当然还需要把它配置到SecurityManager中去,下面是配置文件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=com.wujianwu.realm.MyRealm
myRealm.dataSource=$dataSource
securityManager.realm=$myRealm

可以看到,我们配置了myRealm中的dataSource,并将最终的myRealm设置到SecurityManager中去,从而实现了我们自定义realm的配置

然后就是测试了,还是那一套熟悉的流程= =,代码如下(这次懒得写注释了):

package com.wujianwu.test;

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 TestMyRealm {

    private static final Logger logger = LoggerFactory.getLogger(TestMyRealm.class);
    public static void main(String[] args) {
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123456");
        try {
            subject.login(token);
            if(subject.isAuthenticated()) {
                logger.info("用户登录认证成功");
            }
        } catch (AuthenticationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            logger.error("用户名或者密码错误,登录失败");
        }
    }
}

数据库中对应的数据如下:

《shiro自定义realm》

运行完控制台打印如下:

2019-07-28 16:24:50,001 INFO [com.mchange.v2.log.MLog] - MLog clients using slf4j logging. 
2019-07-28 16:24:50,405 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-28 16:24:50,520 INFO [org.apache.shiro.config.IniSecurityManagerFactory] - Realms have been explicitly set on the SecurityManager instance - auto-setting of realms will not occur. 
2019-07-28 16:24:50,545 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 -> 2zm2h6a4fg70y51p9sdrv|7e0ea639, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceSynchronousCheckins -> false, forceUseNamedDriverClass -> false, identityToken -> 2zm2h6a4fg70y51p9sdrv|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 ] 
用户名zhangsan==============
2019-07-28 16:24:50,842 INFO [org.apache.shiro.session.mgt.AbstractValidatingSessionManager] - Enabling session validation scheduler... 
2019-07-28 16:24:50,847 INFO [com.wujianwu.test.TestMyRealm] - 用户登录认证成功 

可以看到,确确实实调用到了我们自定义的realm(因为打印出了我在myRealm中想要输出的东西“用户名zhangsan==============”)。

因为我们这里只是单单一个shiro框架,所以我们在查询数据库数据时才用了jdbc那一套流程,之后进行ssm整合时就不用这么麻烦了,直接在realm中注入mapper,然后调mapper的方法查询就行了,当然这些都是题外话,以后实现了我也会更新到博客中。

以上就是自定义realm的具体实现,有什么补充或修改的请在评论区留言,谢谢!

 

点赞