我們知道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("用戶名或者密碼錯誤,登錄失敗"); } } }
數據庫中對應的數據如下:
運行完控制台打印如下:
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的具體實現,有什么補充或修改的請在評論區留言,謝謝!