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

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

1,加密工具类的熟悉

 

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

shiro的使用2 灵活使用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次密码之后,出现如下提示;

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

7,小结

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

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

no pays,no gains!
来源:http://www.cnblogs.com/snidget/p/3817763.html
上一篇:容器十年 ——一部软件交付编年史


下一篇:Prof.Wang向未来问好 AI去替代“人”实现IT服务