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自带密码工具类的学习成本;

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

shiro的使用2 灵活使用shiro的密码服务模块的更多相关文章

  1. 使用shiro的密码服务模块

    http://jinnianshilongnian.iteye.com/blog/2021439 http://www.cnblogs.com/snidget/p/3817763.html

  2. 《跟我学Shiro》学习笔记 第一章:Shiro简介

    前言 现在在学习Shiro,参照着张开涛老师的博客进行学习,然后自己写博客记录一下学习中的知识点,一来可以加深理解,二来以后遗忘了可以查阅.没有学习过Shiro的小伙伴,也可以和我一起学习,大家共同进 ...

  3. springboot+shiro+cas实现单点登录之shiro端搭建

    github:https://github.com/peterowang/shiro-cas 本文如有配置问题,请查看之前的springboot集成shiro的文章 1.配置ehcache缓存,在re ...

  4. Apache Shiro 使用手册(三)Shiro 授权

    授权即访问控制,它将判断用户在应用程序中对资源是否拥有相应的访问权限. 如,判断一个用户有查看页面的权限,编辑数据的权限,拥有某一按钮的权限,以及是否拥有打印的权限等等. 一.授权的三要素 授权有着三 ...

  5. Apache Shiro 使用手册(三)Shiro 授权(转发:http://kdboy.iteye.com/blog/1155450)

    授权即访问控制,它将判断用户在应用程序中对资源是否拥有相应的访问权限. 如,判断一个用户有查看页面的权限,编辑数据的权限,拥有某一按钮的权限,以及是否拥有打印的权限等等. 一.授权的三要素 授权有着三 ...

  6. (转) Apache Shiro 使用手册(三)Shiro 授权

    解惑之处: 使用冒号分隔的权限表达式是org.apache.shiro.authz.permission.WildcardPermission 默认支持的实现方式. 这里分别代表了 资源类型:操作:资 ...

  7. 第一章 Shiro简介——《跟我学Shiro》(转)

    目录贴:跟我学Shiro目录贴 1.1  简介 Apache Shiro是Java的一个安全框架.目前,使用Apache Shiro的人越来越多,因为它相当简单,对比Spring Security,可 ...

  8. Apache Shiro 使用手册(一)Shiro架构介绍 - kdboy - ITeye技术网站

    转载 原文地址 http://kdboy.iteye.com/blog/1154644 一.什么是Shiro Apache Shiro是一个强大易用的Java安全框架,提供了认证.授权.加密和会话管理 ...

  9. Shiro 学习笔记(二)——shiro身份验证

    身份验证: 在应用中证明他就是他本人.一般上用身份证.用户/密码 来证明. 在shiro中,用户需要提供principals (身份)和credentials(证明)给shiro,从而应用能验证用户身 ...

随机推荐

  1. javascript中变量提升的理解

    网上找了两个经典的例子 var foo = 1; function bar() { if (!foo) { var foo = 10; } alert(foo); } bar(); // 10 var ...

  2. 深入浅出Redis-redis哨兵集群

    1.Sentinel 哨兵 Sentinel(哨兵)是Redis 的高可用性解决方案:由一个或多个Sentinel 实例 组成的Sentinel 系统可以监视任意多个主服务器,以及这些主服务器属下的所 ...

  3. Nexus(一)环境搭建

    昨天,成功搭建了自己的 Maven 环境(详见:Maven(一)环境搭建),今天就来研究和探讨下 Nexus 的搭建! 使用背景: 安装环境:Windows 10 -64位 JDK版本:1.7 Mav ...

  4. ActionContext.getContext().getSession()

    ActionContext.getContext().getSession() 获取的是session,然后用put存入相应的值,只要在session有效状态下,这个值一直可用 ActionConte ...

  5. Android MVP+Retrofit+RxJava实践小结

    关于MVP.Retrofit.RxJava,之前已经分别做了分享,如果您还没有阅读过,可以猛戳: 1.Android MVP 实例 2.Android Retrofit 2.0使用 3.RxJava ...

  6. mysql查询性能优化

    mysql查询过程: 客户端发送查询请求. 服务器检查查询缓存,如果命中缓存,则返回结果,否则,继续执行. 服务器进行sql解析,预处理,再由优化器生成执行计划. Mysql调用存储引擎API执行优化 ...

  7. Ubuntu下配置apache开启https

    一.HTTPS简述随着网络的日常,信息安全越来越重要,传统的网站都是http协议明文传输,而HTTPS协议是由SSL+HTTP协议构建的可进行加密传输.身份认证的网络协议,比http协议安全. 那ht ...

  8. (整理)MyBatis入门教程(一)

    本文转载: http://www.cnblogs.com/hellokitty1/p/5216025.html#3591383 本人文笔不行,根据上面博客内容引导,自己整理了一些东西 首先给大家推荐几 ...

  9. ASP.NET Linux部署(2) - MS Owin + WebApi + Mono + Jexus

    ASP.NET Linux部署(2) - MS Owin + WebApi + Mono + Jexus 本文承接我的上一篇博文: ASP.NET 5 Linux部署,那篇文章主要是针对最新的ASP. ...

  10. lucene 基础知识点

    部分知识点的梳理,参考<lucene实战>及网络资料 1.基本概念 lucence 可以认为分为两大组件: 1)索引组件 a.内容获取:即将原始的内容材料,可以是数据库.网站(爬虫).文本 ...