shiro的使用2 靈活使用shiro的密碼服務模塊

shiro最閃亮的四大特徵是認證,受權,加密,會話管理
上一篇已經演示瞭如何使用shiro的受權模塊,有了shiro這個利器,能夠以統一的編碼方式對用戶的登入,登出,認證進行管理,至關的優雅。
爲了提升應用系統的安全性,這裏主要關注shiro提供的密碼服務模塊;

1,加密工具類的熟悉

 

首先來個結構圖,看看shiro哥哥提供了哪些加密工具類:

image

 

爲此,寫了一個工具類來探測和熟悉這些工具類的使用:

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);
    }
}java

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,重寫密碼校驗的方法 sql

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次密碼以後,出現以下提示;數據庫

image

7,小結

經過封裝經常使用的加密解密工具類,下降了對jdk自帶密碼工具類的學習成本apache

能夠靈活定義密碼的生成和判斷方式,並改變密碼判斷過程的邏輯;緩存

相關文章
相關標籤/搜索