`

shiro的使用2 灵活使用shiro的密码服务模块

    博客分类:
  • Java
阅读更多

 

 

  • shiro最闪亮的四大特征是认证,授权,加密,会话管理
    上一篇已经演示了如何使用shiro的授权模块,有了shiro这个利器,可以以统一的编码方式对用户的登入,登出,认证进行管理,相当的优雅。
    为了提高应用系统的安全性,这里主要关注shiro提供的密码服务模块;

    1,加密工具类的熟悉

     

    首先来个结构图,看看shiro哥哥提供了哪些加密工具类:



     

     

    为此,写了一个工具类来探测和熟悉这些工具类的使用:

    package com.util;
    import com.domain.User;
    import com.google.common.base.Preconditions;
    import com.google.common.base.Strings;
    import com.sun.crypto.provider.AESKeyGenerator;
    import org.apache.shiro.codec.Base64;
    import org.apache.shiro.codec.CodecSupport;
    import org.apache.shiro.codec.H64;
    import org.apache.shiro.codec.Hex;
    import org.apache.shiro.crypto.AesCipherService;
    import org.apache.shiro.crypto.SecureRandomNumberGenerator;
    import org.apache.shiro.crypto.hash.Md5Hash;
    import java.security.Key;
    /**
    * User: cutter.li
    * Date: 2014/6/27 0027
    * Time: 16:49
    * 备注: shiro进行加密解密的工具类封装
    */
    public final class EndecryptUtils {
        /**
         * base64进制加密
         *
         * @param password
         * @return
         */
        public static String encrytBase64(String password) {
            Preconditions.checkArgument(!Strings.isNullOrEmpty(password), "不能为空");
            byte[] bytes = password.getBytes();
            return Base64.encodeToString(bytes);
        }
        /**
         * base64进制解密
         * @param cipherText
         * @return
         */
        public static String decryptBase64(String cipherText) {
            Preconditions.checkArgument(!Strings.isNullOrEmpty(cipherText), "消息摘要不能为空");
            return Base64.decodeToString(cipherText);
        }
        /**
         * 16进制加密
         *
         * @param password
         * @return
         */
        public static String encrytHex(String password) {
            Preconditions.checkArgument(!Strings.isNullOrEmpty(password), "不能为空");
            byte[] bytes = password.getBytes();
            return Hex.encodeToString(bytes);
        }
        /**
         * 16进制解密
         * @param cipherText
         * @return
         */
        public static String decryptHex(String cipherText) {
            Preconditions.checkArgument(!Strings.isNullOrEmpty(cipherText), "消息摘要不能为空");
            return new String(Hex.decode(cipherText));
        }
        public static String generateKey()
        {
            AesCipherService aesCipherService=new AesCipherService();
            Key key=aesCipherService.generateNewKey();
            return Base64.encodeToString(key.getEncoded());
        }
        /**
         * 对密码进行md5加密,并返回密文和salt,包含在User对象中
         * @param username 用户名
         * @param password 密码
         * @return 密文和salt
         */
        public static User md5Password(String username,String password){
            Preconditions.checkArgument(!Strings.isNullOrEmpty(username),"username不能为空");
            Preconditions.checkArgument(!Strings.isNullOrEmpty(password),"password不能为空");
            SecureRandomNumberGenerator secureRandomNumberGenerator=new SecureRandomNumberGenerator();
            String salt= secureRandomNumberGenerator.nextBytes().toHex();
            //组合username,两次迭代,对密码进行加密
            String password_cipherText= new Md5Hash(password,username+salt,2).toBase64();
            User user=new User();
            user.setPassword(password_cipherText);
            user.setSalt(salt);
            user.setUsername(username);
            return user;
        }
        public static void main(String[] args) {
            String password = "admin";
            String cipherText = encrytHex(password);
            System.out.println(password + "hex加密之后的密文是:" + cipherText);
            String decrptPassword=decryptHex(cipherText);
            System.out.println(cipherText + "hex解密之后的密码是:" + decrptPassword);
            String cipherText_base64 = encrytBase64(password);
            System.out.println(password + "base64加密之后的密文是:" + cipherText_base64);
            String decrptPassword_base64=decryptBase64(cipherText_base64);
            System.out.println(cipherText_base64 + "base64解密之后的密码是:" + decrptPassword_base64);
            String h64=  H64.encodeToString(password.getBytes());
            System.out.println(h64);
            String salt="7road";
            String cipherText_md5= new Md5Hash(password,salt,4).toHex();
            System.out.println(password+"通过md5加密之后的密文是:"+cipherText_md5);
            System.out.println(generateKey());
            System.out.println("==========================================================");
            AesCipherService aesCipherService=new AesCipherService();
            aesCipherService.setKeySize(128);
            Key key=aesCipherService.generateNewKey();
            String aes_cipherText= aesCipherService.encrypt(password.getBytes(),key.getEncoded()).toHex();
            System.out.println(password+" aes加密的密文是:"+aes_cipherText);
            String aes_mingwen=new String(aesCipherService.decrypt(Hex.decode(aes_cipherText),key.getEncoded()).getBytes());
            System.out.println(aes_cipherText+" aes解密的明文是:"+aes_mingwen);
        }
    }

    2,一个综合点的例子,配置帐号的密码生成方式,并利用ehcache,设定输错密码多少次,用户被锁定一个小时;

    1,提供一个ehcache的简单实用类

     

    package com.util.cache;
    import net.sf.ehcache.Cache;
    import net.sf.ehcache.CacheManager;
    import net.sf.ehcache.Element;
    import net.sf.ehcache.config.CacheConfiguration;
    import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
    /**
    * User: cutter.li
    * Date: 2014/6/30 0030
    * Time: 15:32
    * 备注: ehcache的缓存工具类
    */
    public final class EhcacheUtil {
        private static final CacheManager cacheManager = CacheManager.getInstance();
        /**
         * 创建ehcache缓存,创建之后的有效期是1小时
         */
       private static Cache cache = new Cache(new CacheConfiguration("systemCache", 5000).memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.FIFO).timeoutMillis(300).timeToLiveSeconds( 60 * 60));
        static {
            cacheManager.addCache(cache);

        }

        public static void putItem(String key, Object item) {
            if (cache.get(key) != null) {
                cache.remove(key);
            }
            Element element = new Element(key, item);
            cache.put(element);
        }
        public static void removeItem(String key) {
            cache.remove(key);
        }
        public static void updateItem(String key, Object value) {
            putItem(key, value);
        }
        public static Object getItem(String key) {
            Element element=  cache.get(key);
            if(null!=element)
            {
                return element.getObjectValue();
            }
            return null;
        }
    }

     

    2,提供加密和校验密文的方法

     

    /**
         * 对密码进行md5加密,并返回密文和salt,包含在User对象中
         * @param username 用户名
         * @param password 密码
         * @return 密文和salt
         */
        public static User md5Password(String username,String password){
            Preconditions.checkArgument(!Strings.isNullOrEmpty(username),"username不能为空");
            Preconditions.checkArgument(!Strings.isNullOrEmpty(password),"password不能为空");
            SecureRandomNumberGenerator secureRandomNumberGenerator=new SecureRandomNumberGenerator();
            String salt= secureRandomNumberGenerator.nextBytes().toHex();
            //组合username,两次迭代,对密码进行加密
            String password_cipherText= new Md5Hash(password,username+salt,2).toHex();
            User user=new User();
            user.setPassword(password_cipherText);
            user.setSalt(salt);
            user.setUsername(username);
            return user;
        }
        /**
         * 通过username,password,salt,校验密文是否匹配 ,校验规则其实在配置文件中,这里为了清晰,写下来
         * @param username 用户名
         * @param password 原密码
         * @param salt  盐
         * @param md5cipherText 密文
         * @return
         */
        public static  boolean checkMd5Password(String username,String password,String salt,String md5cipherText)
        {
            Preconditions.checkArgument(!Strings.isNullOrEmpty(username),"username不能为空");
            Preconditions.checkArgument(!Strings.isNullOrEmpty(password),"password不能为空");
            Preconditions.checkArgument(!Strings.isNullOrEmpty(md5cipherText),"md5cipherText不能为空");
            //组合username,两次迭代,对密码进行加密
            String password_cipherText= new Md5Hash(password,username+salt,2).toHex();
            return md5cipherText.equals(password_cipherText);
        }

     

    3,配置认证的数据源使用的密码校验接口

     

       <bean id="myRealm" class="com.util.MysqlJdbcRealM">
            <property name="credentialsMatcher" ref="passwordMatcher"></property>
        </bean>
        <bean id="passwordMatcher" class="com.util.LimitRetryHashedMatcher">
       <property name="hashAlgorithmName" value="md5"></property>
            <property name="hashIterations" value="2"></property>
            <property name="storedCredentialsHexEncoded" value="true"></property>

        </bean>

     

     

    4,注册和登录方法的修改

     

      /**
         * 用户注册
         *
         * @param entity
         * @return
         */
        @Override
        public ResponseEntity<Map> createSubmit(User entity) {
            //加密用户输入的密码,得到密码的摘要和盐,保存到数据库
          User user = EndecryptUtils.md5Password(entity.getUsername(), entity.getPassword());
            entity.setPassword(user.getPassword());
            entity.setSalt(user.getSalt());
            Map<String, Object> map = Maps.newHashMap();
            try {
                boolean createResult = service.modify(entity, OperationType.create);
                map.put("success", createResult);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return new ResponseEntity<Map>(map, HttpStatus.OK);
        }
    ------------------------------------------------------------------华丽的分割线---------------------------------------------------------------------------------------------------
      @RequestMapping(value = "login", method = RequestMethod.POST)
        public ResponseEntity<Message> loginSubmit(String username, String password, String vcode, HttpServletRequest request) {
            message.setSuccess();
            validateLogin(message, username, password, vcode);
            try {
    //            String code = request.getSession().getAttribute(AppConstant.KAPTCHA_SESSION_KEY).toString();
    //            if (!vcode.equalsIgnoreCase(code)) {
    //                message.setCode(AppConstant.VALIDCODE_ERROR);
    //                message.setMsg("验证码错误");
    //            }
                if (message.isSuccess()) {
                    Subject subject = SecurityUtils.getSubject();
                    subject.login(new UsernamePasswordToken(username, password,false));
                    if (subject.isAuthenticated()) {
                            message.setMsg("登录成功");
                    } else {
                        message.setCode(AppConstant.USERNAME_NOTEXIST);
                        message.setMsg("用户名/密码错误");
                    }
                }
            }catch (ExcessiveAttemptsException ex)
            {
                message.setCode(AppConstant.USERNAME_NOTEXIST);
                message.setMsg("帐号被锁定1小时");
                ex.printStackTrace();
            }
            catch (AuthenticationException ex){
                message.setCode(AppConstant.USERNAME_NOTEXIST);
                message.setMsg("用户名/密码错误");
                ex.printStackTrace();
            }
            finally {
                return new ResponseEntity<Message>(message, HttpStatus.OK);
            }
        }
    ---------------------------------------------------------------认证的修改-------------------------------------------------------------------------------------
        //登录认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
            String username = String.valueOf(usernamePasswordToken.getUsername());
            User user = userService.findByUserName(username);
            SimpleAuthenticationInfo authenticationInfo = null;
            if (null != user) {
                String password = new String(usernamePasswordToken.getPassword());
    //密码校验移交给了shiro的提供的一个接口实现类,所以这里注释掉
    //            if (EndecryptUtils.checkMd5Password(username,password,user.getSalt(),user.getPassword())) {
                    authenticationInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName());
                    authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(username+user.getSalt()));
    //            }

            }
            return authenticationInfo;
        }

     

     

    5,重写密码校验的方法

     

    package com.util;
    import com.util.cache.EhcacheUtil;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.ExcessiveAttemptsException;
    import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
    import java.util.concurrent.atomic.AtomicInteger;
    /**
    * User: cutter.li
    * Date: 2014/6/30 0030
    * Time: 15:22
    * 备注: 限制登录次数,如果5次出错,锁定1个小时
    */
    public class LimitRetryHashedMatcher extends HashedCredentialsMatcher {
        @Override
        public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
            String username = (String) token.getPrincipal();
    //retrycount + 1
            Object element = EhcacheUtil.getItem(username);
            if (element == null) {
                EhcacheUtil.putItem(username, 1);
                element=0;
            }else{
                int count=Integer.parseInt(element.toString())+1;
                element=count;
                EhcacheUtil.putItem(username,element);
            }
            AtomicInteger retryCount = new AtomicInteger(Integer.parseInt(element.toString()));
            if (retryCount.incrementAndGet() > 5) {
    //if retrycount >5 throw
                throw new ExcessiveAttemptsException();
            }
            boolean matches = super.doCredentialsMatch(token, info);
            if (matches) {
    //clear retrycount
                EhcacheUtil.removeItem(username);
            }
            return matches;

        }
    }

     

    6,搞定收工


    连续输错5次密码之后,出现如下提示;



     

    7,小结

    通过封装常用的加密解密工具类,降低了对jdk自带密码工具类的学习成本

    可以灵活定义密码的生成和判断方式,并改变密码判断过程的逻辑;

  • 大小: 10.7 KB
  • 大小: 11.1 KB
0
0
分享到:
评论

相关推荐

    shiro-1.7.1.zip

    例如,Shiro的RememberMe服务、Session管理以及CSRF防护等功能都在这个模块中实现。 3. **shiro-lang-1.7.1.jar**: 该组件包含了Shiro的表达式语言支持,如在角色和权限定义时使用SpEL(Spring Expression Language...

    shiro jar包及源码下载

    "shiro-core-1.3.2-sources.jar"则是Shiro核心模块的源代码包,开发者可以通过阅读源码来深入理解Shiro的工作原理,这对于进行定制化开发或者排查问题非常有帮助。Shiro的核心组件包括以下几个方面: 1. **认证**:...

    shiro开发需要的所以的jar包

    2. **shiro-core**: Shiro的核心模块,包含了认证、授权、会话管理和密码工具等基础功能。 3. **shiro-web**: 为Web应用提供支持,如过滤器和会话管理。 4. **shiro-cas**: 对CAS(Central Authentication Service...

    Shiro_lib.zip

    在实际使用中,Shiro可以很容易地与Spring框架集成,以实现更灵活的控制。Shiro的API设计简洁,易于理解和使用,使得开发者无需深入理解安全理论也能实现基本的安全需求。 在"lib"这个文件夹下,你将找到以下可能的...

    shiro源码(shiro-root-1.8.0-source-release.zip)

    Shiro的Remember Me服务允许用户在一次登录后,下次访问时自动登录,提高用户体验。它使用安全的加密技术存储用户的记住我信息,避免了安全风险。 7. **Caching**: Shiro提供了缓存管理,可以缓存认证和授权信息...

    Spring shiro安全框架介绍

    Shiro的会话管理模块可以实现跨服务器的会话共享,这意味着即使是在分布式环境中,Shiro也能够保持一致性的会话状态。具体来说: - **原生会话支持**:Shiro的会话管理支持企业级特性,可以提供容器无关的集群解决...

    学习 权限框架shiro 的ssm

    Shiro提供了RememberMe服务,允许用户在一段时间内免于再次登录。 2. **授权**:确定用户是否有执行特定操作的权限。Shiro通过Role(角色)和Permission(权限)进行控制,可以实现细粒度的权限分配。 3. **会话...

    Shiro使用教程.rar

    Shiro 支持 Web 应用、命令行应用、Android 应用等多种环境,其设计目标是易于使用,同时保持足够的灵活性,以便适应各种复杂的业务场景。 二、Shiro 主要组件 1. **Subject**:主体,代表当前操作的用户,可以是...

    shiro相关实现例子以及shiro教程文档

    5. **加密服务**:解释Shiro提供的密码学工具,如散列、加密和编码,以及如何安全地存储敏感信息。 6. **Web集成**:展示如何在Web应用中使用Shiro,包括过滤器配置、Web安全设置等。 7. **实战案例**:提供实际...

    shiro1.3.2 源码和jar包

    2. **授权**:Shiro的权限控制灵活,可以基于角色或权限进行,支持细粒度的权限分配。你可以定义角色并赋予其不同的权限,然后根据Subject的角色或权限判断是否允许执行某操作。 3. **会话管理**:Shiro可以独立于...

    springmvc shiro使用

    Apache Shiro 是一个强大且易用的Java安全框架,可以提供认证、授权、加密和会话管理功能,同时具备良好的扩展性和灵活性。它通过三大核心组件——Subject(主题)、SecurityManager(安全管理器)和Realms(领域)...

    shiro框架学习心得

    7. **插件化**:Shiro 采用模块化设计,可以方便地扩展自定义的认证、授权策略。 8. **Web 安全实践**:Shiro 还可以处理CSRF、XSS等常见的Web安全问题,提供了一套完整的防护机制。 深入学习Shiro,还需要理解其...

    shiro-root-1.7.0_ROOT_shirocore1.7_shiro1.7源码_

    3. **Cryptography(加密)**:Shiro 提供了多种加密工具和服务,如密码散列、加密算法等,用于保护敏感数据的安全。这些工具可以帮助开发者进行密码存储、数据加密等操作,以防止数据泄露。 4. **Session ...

    shiro1.2.2版本所需的jar以及源码

    1. **Shiro Core**:这是Shiro的基础模块,提供了核心的安全服务,如身份验证(Authentication)、授权(Authorization)和会话管理(Session Management)。身份验证涉及用户身份的确认,授权则关乎用户权限的确定...

    shiro1.2.2

    Apache Shiro 是一个强大且易用的 Java 安全框架,...Shiro 的灵活性使其能够轻松适应各种复杂的安全需求,同时其简洁的 API 使得开发人员能够快速上手。通过合理配置和定制,Shiro 能为你的应用提供稳固的安全保障。

    SSM+Maven+Shiro

    在Shiro配置中,可以定义用户、角色和权限的关系,以及密码加密策略。同时,通过Maven的生命周期,可以在构建过程中自动执行测试、编译、打包等任务。 总的来说,SSM+Maven+Shiro的整合提供了完整的Web应用开发解决...

    shiro1.6版本

    Shiro 1.6.0 对加密模块进行了优化,提供了更好的加密算法支持,如SHA-256等现代哈希算法,这有助于提高密码存储的安全性,减少密码被破解的风险。 4. **授权与权限控制**: Shiro 提供了细粒度的权限控制,1.6...

    Shiro和AJAX完美整合

    Apache Shiro 和 AJAX 的整合是现代 Web 应用程序中常见的需求,特别是在使用 Spring(SSH 框架的一部分)开发企业级应用时。Shiro 是一个强大且易用的 Java 安全框架,提供了身份验证、授权、会话管理和加密等功能...

    shiro.zip下载

    7. **可扩展性**:Shiro 设计为模块化,开发者可以根据需要选择使用哪些组件,也可以通过扩展其核心接口来自定义实现,以适应特定的安全需求。 8. **API 简洁易用**:Shiro 的 API 设计直观易懂,使得开发者能够...

Global site tag (gtag.js) - Google Analytics