zoukankan      html  css  js  c++  java
  • shiro加密算法

    第一节的时候我介绍过,shiro有很多加密算法,如md5和sha,而且还支持加盐,使得密码的解析变得更有难度,更好的保障了数据的安全性。

    这里我们要介绍的是md5算法,因为比较常用。首先我们来看看md5算法的各种实现方式:

    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的方式,下面看看它们的输出:

    输出信息:

    e10adc3949ba59abbe56e057f20f883e
    7ca5a3bfd6fc151442219490509cb4d8
    3d80e8a34ae898c07d3fb237bae26b7d
    3d80e8a34ae898c07d3fb237bae26b7d

    可以看到,不加盐、加盐、加盐加散列次数后的加密字段是不一样的,而它们的数据安全性也是递增的!但是注意散列次数不能设置过大,否则运行效率会变低。下面讲讲加密算法在realm中的应用。

    一般我们在数据库保存的用户密码都是经过加密后的密码,所以我们想把加密后的用户信息传给shiro进行认证,就必须把该密码加密的算法、添加的盐以及散列的次数告诉shiro,因此我们需要配置realm里面的凭证匹配器credentialsMatcher,当用户将账号密码输进来时,shiro就会根据我们设置的加密规则对密码进行加密加盐,然后与realm中查询封装好的数据库数据进行比对认证。

    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:

    [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:

    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类型。

    设置好凭证匹配器,改好自定义的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,如果有什么补充或者修改的请在评论区留言,谢谢!

  • 相关阅读:
    spring学习总结009 --- 重复id或name的bean定义允许覆盖allowBeanDefinitionOverriding
    spring学习总结008 --- IOC流程图
    spring学习总结007 --- IOC容器级生命周期接口
    spring学习总结006 --- Bean级生命周期接口
    spring学习总结005 --- IOC容器启动源码(事件机制)
    字体图标
    pycharm永久激活
    Linux常用命令
    Android Studio 更新后导入旧项目Bug解决
    Ubuntu更新源
  • 原文地址:https://www.cnblogs.com/wujianwu/p/11260732.html
Copyright © 2011-2022 走看看