第一節的時候我介紹過,shiro有不少加密算法,如md5和sha,並且還支持加鹽,使得密碼的解析變得更有難度,更好的保障了數據的安全性。java
這裏咱們要介紹的是md5算法,由於比較經常使用。首先咱們來看看md5算法的各類實現方式:mysql
package com.wujianwu.test; import org.apache.shiro.crypto.hash.Md5Hash; import org.apache.shiro.crypto.hash.SimpleHash; public class TestMd5 { public static void main(String[] args) { String password = "123456";//要加密的字符串 String salt = "wjw";//鹽 Integer hashIterations = 2;//散列次數 //1.不加鹽的md5 Md5Hash md5 = new Md5Hash(password); System.out.println(md5.toString()); //2.加鹽的md5 md5 = new Md5Hash(password, salt); System.out.println(md5.toString()); //3.加鹽再設置散列次數的md5 md5 = new Md5Hash(password, salt, hashIterations); System.out.println(md5.toString()); //4.利用SimpleHash來設置md5(上面三種均可以經過這個來設置,這裏舉例加鹽加散列次數的) //第一個參數是算法名稱,這裏指定md5,第二個是要加密的密碼,第三個參數是加鹽,第四個是散列次數 SimpleHash hash = new SimpleHash("md5", password, salt,hashIterations); System.out.println(hash.toString()); } }
上面列舉了md5算法的各類實現,包括不加鹽的,加鹽的,加鹽加散列次數的(從HashedCredentialsMatcher的源碼可得知,默認散列次數爲1),還有經過SimpleHash來實現md5的方式,下面看看它們的輸出:算法
輸出信息:sql
e10adc3949ba59abbe56e057f20f883e
7ca5a3bfd6fc151442219490509cb4d8
3d80e8a34ae898c07d3fb237bae26b7d
3d80e8a34ae898c07d3fb237bae26b7d
能夠看到,不加鹽、加鹽、加鹽加散列次數後的加密字段是不同的,而它們的數據安全性也是遞增的!可是注意散列次數不能設置過大,不然運行效率會變低。下面講講加密算法在realm中的應用。數據庫
通常咱們在數據庫保存的用戶密碼都是通過加密後的密碼,因此咱們想把加密後的用戶信息傳給shiro進行認證,就必須把該密碼加密的算法、添加的鹽以及散列的次數告訴shiro,所以咱們須要配置realm裏面的憑證匹配器credentialsMatcher,當用戶將帳號密碼輸進來時,shiro就會根據咱們設置的加密規則對密碼進行加密加鹽,而後與realm中查詢封裝好的數據庫數據進行比對認證。apache
public abstract class AuthenticatingRealm extends CachingRealm implements Initializable { //TODO - complete JavaDoc private static final Logger log = LoggerFactory.getLogger(AuthenticatingRealm.class); private static final AtomicInteger INSTANCE_COUNT = new AtomicInteger(); /** * The default suffix appended to the realm name used for caching authentication data. * * @since 1.2 */ private static final String DEFAULT_AUTHORIZATION_CACHE_SUFFIX = ".authenticationCache"; /** * Credentials matcher used to determine if the provided credentials match the credentials stored in the data store. */ private CredentialsMatcher credentialsMatcher;
下面咱們開始配置憑證匹配器:安全
shiro.ini:session
[main] #配置散列算法,用於用戶輸入身份和密碼後進行加密而後與數據庫數據比對 credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher credentialsMatcher.hashAlgorithmName=md5 credentialsMatcher.hashIterations=2 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.credentialsMatcher=$credentialsMatcher myRealm.dataSource=$dataSource securityManager.realm=$myRealm
咱們設置了一個憑證匹配器HashedCredentialsMatcher,而後再設置它裏面的相關屬性,如算法名稱爲md5,散列次數是兩次,而後將設置好的憑證匹配器賦予咱們自定義的realm,再修改一下咱們的realm的部分代碼,將數據庫中的鹽查詢出來,並調用SimpleAuthenticationInfo的帶有鹽參數的構造方法來封裝數據庫查出來的用戶數據(其實就是告訴shiro要比對的是加密後的信息),如下爲新的myRealm:app
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.authc.credential.CredentialsMatcher; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.crypto.hash.SimpleHash; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; 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.getPassword()+"=============="); info = new SimpleAuthenticationInfo(userName, user.getPassword(), ByteSource.Util.bytes(user.getPasswordSalt()), 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; } /** * 根據用戶名從數據庫查詢出用戶信息 * @param username * @return */ private User getUserInfo(String username){ User user = new User(); String sql = "select username,password,password_salt 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); String salt = set.getString(3); user.setPassword(password); user.setUsername(username1); user.setPasswordSalt(salt); } } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally { closeAll(connection, set, statement); } return user; } /** * 關閉資源 * @param conn * @param set * @param statement */ 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(); } } }
注意SimpleAuthenticationInfo構造函數中的鹽是ByteSource類型的,所以咱們須要用接口ByteSource中的靜態內部類Util下的bytes(String str)方法來將咱們的鹽轉換成ByteSource類型。ide
設置好憑證匹配器,改好自定義的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.crypto.hash.Md5Hash; import org.apache.shiro.crypto.hash.SimpleHash; 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 19:59:30,535 INFO [com.mchange.v2.log.MLog] - MLog clients using slf4j logging. 2019-07-28 19:59:30,963 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 19:59:31,055 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 19:59:31,085 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 -> 2zm2h7a4fnv3mn329o49|180bc464, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceSynchronousCheckins -> false, forceUseNamedDriverClass -> false, identityToken -> 2zm2h7a4fnv3mn329o49|180bc464, 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 ] 用戶加密後的密碼3d80e8a34ae898c07d3fb237bae26b7d============== 2019-07-28 19:59:31,399 INFO [org.apache.shiro.session.mgt.AbstractValidatingSessionManager] - Enabling session validation scheduler... 2019-07-28 19:59:31,405 INFO [com.wujianwu.test.TestMyRealm] - 用戶登陸認證成功
能夠看到,咱們模擬的用戶輸入的帳號是zhangsan,密碼是123456,但數據庫中的密碼是加密加鹽後的密碼,因此咱們給realm配置了加密算法的規則,讓它將咱們傳過去的密碼進行了一樣的加密加鹽(這裏鹽不須要咱們設置,是從數據庫中查詢出來的),而後再和數據庫的數據進行比對認證,所以這裏認證是成功的(123456進行md5加密和wjw加鹽後而後再散列2次就是這一串東西了:3d80e8a34ae898c07d3fb237bae26b7d,至於內部如何實現,有興趣的大佬能夠去看一下《算法》這本書= =)
以上就是散列算法(加密算法)在shiro中的使用Demo,若是有什麼補充或者修改的請在評論區留言,謝謝!