<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          SpringBoot 整合 Shiro 密碼加密

          共 11668字,需瀏覽 24分鐘

           ·

          2022-01-26 13:29

          數(shù)據(jù)庫(kù)中密碼相關(guān)字段都不是明文,肯定是加密之后的,傳統(tǒng)方式一般是使用MD5加密。

          單純使用不加鹽的MD5加密方式,當(dāng)兩個(gè)用戶的密碼相同時(shí),會(huì)發(fā)現(xiàn)數(shù)據(jù)庫(kù)中存在相同內(nèi)容的密碼,這樣也是不安全的。我們希望即便是兩個(gè)人的原始密碼一樣,加密后的結(jié)果也不一樣。

          下面進(jìn)行shiro密碼 加密加鹽配置:

          1.ShiroConfig中添加密碼比較器
          /**
          ?*?配置密碼比較器
          ?*?@return
          ?*/

          @Bean("credentialsMatcher")
          public?RetryLimitHashedCredentialsMatcher?retryLimitHashedCredentialsMatcher(){
          ????RetryLimitHashedCredentialsMatcher?retryLimitHashedCredentialsMatcher?=?new?RetryLimitHashedCredentialsMatcher();
          ????retryLimitHashedCredentialsMatcher.setRedisManager(redisManager());

          ????//如果密碼加密,可以打開下面配置
          ????//加密算法的名稱
          ????retryLimitHashedCredentialsMatcher.setHashAlgorithmName("MD5");
          ????//配置加密的次數(shù)
          ????retryLimitHashedCredentialsMatcher.setHashIterations(2);
          ????//是否存儲(chǔ)為16進(jìn)制
          ????retryLimitHashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);

          ????return?retryLimitHashedCredentialsMatcher;
          }
          2.將密碼比較器配置給ShiroRealm
          /**
          ?*??身份認(rèn)證realm;?(這個(gè)需要自己寫,賬號(hào)密碼校驗(yàn);權(quán)限等)
          ?*?@return
          ?*/

          @Bean
          public?ShiroRealm?shiroRealm(){
          ????ShiroRealm?shiroRealm?=?new?ShiroRealm();
          ????shiroRealm.setCachingEnabled(true);
          ????//啟用身份驗(yàn)證緩存,即緩存AuthenticationInfo信息,默認(rèn)false
          ????shiroRealm.setAuthenticationCachingEnabled(true);
          ????//緩存AuthenticationInfo信息的緩存名稱?在ehcache-shiro.xml中有對(duì)應(yīng)緩存的配置
          ????shiroRealm.setAuthenticationCacheName("authenticationCache");
          ????//啟用授權(quán)緩存,即緩存AuthorizationInfo信息,默認(rèn)false
          ????shiroRealm.setAuthorizationCachingEnabled(true);
          ????//緩存AuthorizationInfo信息的緩存名稱??在ehcache-shiro.xml中有對(duì)應(yīng)緩存的配置
          ????shiroRealm.setAuthorizationCacheName("authorizationCache");
          ????//配置自定義密碼比較器
          ????shiroRealm.setCredentialsMatcher(retryLimitHashedCredentialsMatcher());
          ????return?shiroRealm;
          }
          3.密碼比較器RetryLimitHashedCredentialsMatcher

          自定義的密碼比較器,跟前面博客中邏輯沒(méi)有變化,唯一變的是 繼承的類從?SimpleCredentialsMatcher?變?yōu)?HashedCredentialsMatcher

          在密碼比較器中做了:如果用戶輸入密碼連續(xù)錯(cuò)誤5次,將鎖定賬號(hào),具體參考博客:https://blog.csdn.net/qq_34021712/article/details/80461177

          RetryLimitHashedCredentialsMatcher完整內(nèi)容如下:

          package?com.shiro.config;

          import?java.util.concurrent.atomic.AtomicInteger;

          import?com.springboot.test.shiro.modules.user.dao.UserMapper;
          import?com.springboot.test.shiro.modules.user.dao.entity.User;
          import?org.apache.log4j.Logger;
          import?org.apache.shiro.authc.AuthenticationInfo;
          import?org.apache.shiro.authc.AuthenticationToken;
          import?org.apache.shiro.authc.LockedAccountException;
          import?org.apache.shiro.authc.credential.HashedCredentialsMatcher;
          import?org.springframework.beans.factory.annotation.Autowired;


          /**
          ?*?@description:?登陸次數(shù)限制
          ?*/

          public?class?RetryLimitHashedCredentialsMatcher?extends?HashedCredentialsMatcher?{

          ????private?static?final?Logger?logger?=?Logger.getLogger(RetryLimitHashedCredentialsMatcher.class);

          ????public?static?final?String?DEFAULT_RETRYLIMIT_CACHE_KEY_PREFIX?=?"shiro:cache:retrylimit:";
          ????private?String?keyPrefix?=?DEFAULT_RETRYLIMIT_CACHE_KEY_PREFIX;
          ????@Autowired
          ????private?UserMapper?userMapper;
          ????private?RedisManager?redisManager;

          ????public?void?setRedisManager(RedisManager?redisManager)?{
          ????????this.redisManager?=?redisManager;
          ????}

          ????private?String?getRedisKickoutKey(String?username)?{
          ????????return?this.keyPrefix?+?username;
          ????}

          ????@Override
          ????public?boolean?doCredentialsMatch(AuthenticationToken?token,?AuthenticationInfo?info)?{

          ????????//獲取用戶名
          ????????String?username?=?(String)token.getPrincipal();
          ????????//獲取用戶登錄次數(shù)
          ????????AtomicInteger?retryCount?=?(AtomicInteger)redisManager.get(getRedisKickoutKey(username));
          ????????if?(retryCount?==?null)?{
          ????????????//如果用戶沒(méi)有登陸過(guò),登陸次數(shù)加1?并放入緩存
          ????????????retryCount?=?new?AtomicInteger(0);
          ????????}
          ????????if?(retryCount.incrementAndGet()?>?5)?{
          ????????????//如果用戶登陸失敗次數(shù)大于5次?拋出鎖定用戶異常??并修改數(shù)據(jù)庫(kù)字段
          ????????????User?user?=?userMapper.findByUserName(username);
          ????????????if?(user?!=?null?&&?"0".equals(user.getState())){
          ????????????????//數(shù)據(jù)庫(kù)字段?默認(rèn)為?0??就是正常狀態(tài)?所以?要改為1
          ????????????????//修改數(shù)據(jù)庫(kù)的狀態(tài)字段為鎖定
          ????????????????user.setState("1");
          ????????????????userMapper.update(user);
          ????????????}
          ????????????logger.info("鎖定用戶"?+?user.getUsername());
          ????????????//拋出用戶鎖定異常
          ????????????throw?new?LockedAccountException();
          ????????}
          ????????//判斷用戶賬號(hào)和密碼是否正確
          ????????boolean?matches?=?super.doCredentialsMatch(token,?info);
          ????????if?(matches)?{
          ????????????//如果正確,從緩存中將用戶登錄計(jì)數(shù)?清除
          ????????????redisManager.del(getRedisKickoutKey(username));
          ????????}{
          ????????????redisManager.set(getRedisKickoutKey(username),?retryCount);
          ????????}
          ????????return?matches;
          ????}

          ????/**
          ?????*?根據(jù)用戶名?解鎖用戶
          ?????*?@param?username
          ?????*?@return
          ?????*/

          ????public?void?unlockAccount(String?username){
          ????????User?user?=?userMapper.findByUserName(username);
          ????????if?(user?!=?null){
          ????????????//修改數(shù)據(jù)庫(kù)的狀態(tài)字段為鎖定
          ????????????user.setState("0");
          ????????????userMapper.update(user);
          ????????????redisManager.del(getRedisKickoutKey(username));
          ????????}
          ????}

          }
          4.修改ShiroRealm中doGetAuthenticationInfo方法
          package?com.springboot.shiro.realm;

          import?com.springboot.test.shiro.modules.user.dao.PermissionMapper;
          import?com.springboot.test.shiro.modules.user.dao.RoleMapper;
          import?com.springboot.test.shiro.modules.user.dao.entity.Permission;
          import?com.springboot.test.shiro.modules.user.dao.entity.Role;
          import?com.springboot.test.shiro.modules.user.dao.UserMapper;
          import?com.springboot.test.shiro.modules.user.dao.entity.User;
          import?org.apache.shiro.SecurityUtils;
          import?org.apache.shiro.authc.*;
          import?org.apache.shiro.authz.AuthorizationInfo;
          import?org.apache.shiro.authz.SimpleAuthorizationInfo;
          import?org.apache.shiro.realm.AuthorizingRealm;
          import?org.apache.shiro.subject.PrincipalCollection;
          import?org.springframework.beans.factory.annotation.Autowired;

          import?java.util.Set;

          /**
          ?*?@description:?在Shiro中,最終是通過(guò)Realm來(lái)獲取應(yīng)用程序中的用戶、角色及權(quán)限信息的
          ?*?在Realm中會(huì)直接從我們的數(shù)據(jù)源中獲取Shiro需要的驗(yàn)證信息。可以說(shuō),Realm是專用于安全框架的DAO.
          ?*/

          public?class?ShiroRealm?extends?AuthorizingRealm?{

          ????@Autowired
          ????private?UserMapper?userMapper;

          ????@Autowired
          ????private?RoleMapper?roleMapper;

          ????@Autowired
          ????private?PermissionMapper?permissionMapper;

          ????/**
          ?????*?驗(yàn)證用戶身份
          ?????*?@param?authenticationToken
          ?????*?@return
          ?????*?@throws?AuthenticationException
          ?????*/

          ????@Override
          ????protected?AuthenticationInfo?doGetAuthenticationInfo(AuthenticationToken?authenticationToken)?throws?AuthenticationException?{

          ????????//獲取用戶名密碼?第一種方式
          ????????//String?username?=?(String)?authenticationToken.getPrincipal();
          ????????//String?password?=?new?String((char[])?authenticationToken.getCredentials());

          ????????//獲取用戶名?密碼?第二種方式
          ????????UsernamePasswordToken?usernamePasswordToken?=?(UsernamePasswordToken)?authenticationToken;
          ????????String?username?=?usernamePasswordToken.getUsername();
          ????????String?password?=?new?String(usernamePasswordToken.getPassword());

          ????????//從數(shù)據(jù)庫(kù)查詢用戶信息
          ????????User?user?=?this.userMapper.findByUserName(username);

          ????????//可以在這里直接對(duì)用戶名校驗(yàn),或者調(diào)用?CredentialsMatcher?校驗(yàn)
          ????????if?(user?==?null)?{
          ????????????throw?new?UnknownAccountException("用戶名或密碼錯(cuò)誤!");
          ????????}
          ????????//這里將?密碼對(duì)比?注銷掉,否則?無(wú)法鎖定??要將密碼對(duì)比?交給?密碼比較器
          ????????//if?(!password.equals(user.getPassword()))?{
          ????????//????throw?new?IncorrectCredentialsException("用戶名或密碼錯(cuò)誤!");
          ????????//}
          ????????if?("1".equals(user.getState()))?{
          ????????????throw?new?LockedAccountException("賬號(hào)已被鎖定,請(qǐng)聯(lián)系管理員!");
          ????????}

          ????????SimpleAuthenticationInfo?info?=?new?SimpleAuthenticationInfo(user,?user.getPassword(),new?MyByteSource(user.getUsername()),getName());
          ????????return?info;
          ????}

          ????/**
          ?????*?授權(quán)用戶權(quán)限
          ?????*?授權(quán)的方法是在碰到標(biāo)簽的時(shí)候調(diào)用的
          ?????*?它會(huì)去檢測(cè)shiro框架中的權(quán)限(這里的permissions)是否包含有該標(biāo)簽的name值,如果有,里面的內(nèi)容顯示
          ?????*?如果沒(méi)有,里面的內(nèi)容不予顯示(這就完成了對(duì)于權(quán)限的認(rèn)證.)
          ?????*
          ?????*?shiro的權(quán)限授權(quán)是通過(guò)繼承AuthorizingRealm抽象類,重載doGetAuthorizationInfo();
          ?????*?當(dāng)訪問(wèn)到頁(yè)面的時(shí)候,鏈接配置了相應(yīng)的權(quán)限或者shiro標(biāo)簽才會(huì)執(zhí)行此方法否則不會(huì)執(zhí)行
          ?????*?所以如果只是簡(jiǎn)單的身份認(rèn)證沒(méi)有權(quán)限的控制的話,那么這個(gè)方法可以不進(jìn)行實(shí)現(xiàn),直接返回null即可。
          ?????*
          ?????*?在這個(gè)方法中主要是使用類:SimpleAuthorizationInfo?進(jìn)行角色的添加和權(quán)限的添加。
          ?????*?authorizationInfo.addRole(role.getRole());?authorizationInfo.addStringPermission(p.getPermission());
          ?????*
          ?????*?當(dāng)然也可以添加set集合:roles是從數(shù)據(jù)庫(kù)查詢的當(dāng)前用戶的角色,stringPermissions是從數(shù)據(jù)庫(kù)查詢的當(dāng)前用戶對(duì)應(yīng)的權(quán)限
          ?????*?authorizationInfo.setRoles(roles);?authorizationInfo.setStringPermissions(stringPermissions);
          ?????*
          ?????*?就是說(shuō)如果在shiro配置文件中添加了filterChainDefinitionMap.put("/add",?"perms[權(quán)限添加]");
          ?????*?就說(shuō)明訪問(wèn)/add這個(gè)鏈接必須要有“權(quán)限添加”這個(gè)權(quán)限才可以訪問(wèn)
          ?????*
          ?????*?如果在shiro配置文件中添加了filterChainDefinitionMap.put("/add",?"roles[100002],perms[權(quán)限添加]");
          ?????*?就說(shuō)明訪問(wèn)/add這個(gè)鏈接必須要有?"權(quán)限添加"?這個(gè)權(quán)限和具有?"100002"?這個(gè)角色才可以訪問(wèn)
          ?????*?@param?principalCollection
          ?????*?@return
          ?????*/

          ????@Override
          ????protected?AuthorizationInfo?doGetAuthorizationInfo(PrincipalCollection?principalCollection)?{

          ????????System.out.println("查詢權(quán)限方法調(diào)用了!!!");

          ????????//獲取用戶
          ????????User?user?=?(User)?SecurityUtils.getSubject().getPrincipal();

          ????????//獲取用戶角色
          ????????Set?roles?=this.roleMapper.findRolesByUserId(user.getUid());
          ????????//添加角色
          ????????SimpleAuthorizationInfo?authorizationInfo?=??new?SimpleAuthorizationInfo();
          ????????for?(Role?role?:?roles)?{
          ????????????authorizationInfo.addRole(role.getRole());
          ????????}

          ????????//獲取用戶權(quán)限
          ????????Set?permissions?=?this.permissionMapper.findPermissionsByRoleId(roles);
          ????????//添加權(quán)限
          ????????for?(Permission?permission:permissions)?{
          ????????????authorizationInfo.addStringPermission(permission.getPermission());
          ????????}

          ????????return?authorizationInfo;
          ????}

          ????/**
          ?????*?重寫方法,清除當(dāng)前用戶的的?授權(quán)緩存
          ?????*?@param?principals
          ?????*/

          ????@Override
          ????public?void?clearCachedAuthorizationInfo(PrincipalCollection?principals)?{
          ????????super.clearCachedAuthorizationInfo(principals);
          ????}

          ????/**
          ?????*?重寫方法,清除當(dāng)前用戶的?認(rèn)證緩存
          ?????*?@param?principals
          ?????*/

          ????@Override
          ????public?void?clearCachedAuthenticationInfo(PrincipalCollection?principals)?{
          ????????super.clearCachedAuthenticationInfo(principals);
          ????}

          ????@Override
          ????public?void?clearCache(PrincipalCollection?principals)?{
          ????????super.clearCache(principals);
          ????}

          ????/**
          ?????*?自定義方法:清除所有?授權(quán)緩存
          ?????*/

          ????public?void?clearAllCachedAuthorizationInfo()?{
          ????????getAuthorizationCache().clear();
          ????}

          ????/**
          ?????*?自定義方法:清除所有?認(rèn)證緩存
          ?????*/

          ????public?void?clearAllCachedAuthenticationInfo()?{
          ????????getAuthenticationCache().clear();
          ????}

          ????/**
          ?????*?自定義方法:清除所有的??認(rèn)證緩存??和?授權(quán)緩存
          ?????*/

          ????public?void?clearAllCache()?{
          ????????clearAllCachedAuthenticationInfo();
          ????????clearAllCachedAuthorizationInfo();
          ????}

          }

          跟之前的?ShiroRealm?相比,唯一改變的了
          SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(),new MyByteSource(user.getUsername()),getName());這一行代碼,添加了 加鹽參數(shù)。

          注意:大家可能看到了使用了?MyByteSource?而不是?ByteSource.Util.bytes(user.getUsername())具體原因參考博客:https://blog.csdn.net/qq_34021712/article/details/84567437

          5.下面是生成密碼加密加鹽的方法,可以在注冊(cè)的時(shí)候?qū)γ魑倪M(jìn)行加密 加鹽 入庫(kù)
          package?com.olive.shiro.test;

          import?org.apache.shiro.crypto.hash.SimpleHash;
          import?org.apache.shiro.util.ByteSource;
          import?org.junit.Test;

          /**
          ?*?@description:?給?密碼進(jìn)行?加密加鹽??鹽值默認(rèn)為?用戶名
          ?*/

          public?class?PasswordSaltTest?{

          ????@Test
          ????public?void?test()?throws?Exception?{
          ????????System.out.println(md5("123456","admin"));
          ????}

          ????public?static?final?String?md5(String?password,?String?salt){
          ????????//加密方式
          ????????String?hashAlgorithmName?=?"MD5";
          ????????//鹽:為了即使相同的密碼不同的鹽加密后的結(jié)果也不同
          ????????ByteSource?byteSalt?=?ByteSource.Util.bytes(salt);
          ????????//密碼
          ????????Object?source?=?password;
          ????????//加密次數(shù)
          ????????int?hashIterations?=?2;
          ????????SimpleHash?result?=?new?SimpleHash(hashAlgorithmName,?source,?byteSalt,?hashIterations);
          ????????return?result.toString();
          ????}

          }
          可能出現(xiàn)的問(wèn)題

          可能會(huì)發(fā)生這種情況,測(cè)試發(fā)現(xiàn)密碼不對(duì),具體原因debug都可以發(fā)現(xiàn),這里直接把結(jié)果發(fā)出來(lái):

          第一種:

          debug發(fā)現(xiàn) 傳入的密碼 經(jīng)過(guò)加密加鹽之后是對(duì)的,但是 從數(shù)據(jù)庫(kù)中 獲取的密碼 卻是明文,原因是在ShiroRealm中?doGetAuthenticationInfo方法中,最后返回的SimpleAuthenticationInfo?第二個(gè)參數(shù) 是密碼,這個(gè)密碼 不是從前臺(tái)傳過(guò)來(lái)的密碼,而是從數(shù)據(jù)庫(kù)中查詢出來(lái)的

          第二種:

          debug發(fā)現(xiàn) 傳入的密碼 經(jīng)過(guò)加密加鹽之后是對(duì)的,但是 從數(shù)據(jù)庫(kù)中 獲取的密碼 卻是更長(zhǎng)的一段密文,原因是在ShiroConfig中配置的RetryLimitHashedCredentialsMatcher一個(gè)屬性:

          //是否存儲(chǔ)為16進(jìn)制
          retryLimitHashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);

          默認(rèn)是true,如果改為false,則會(huì)出現(xiàn) 對(duì)比的時(shí)候從數(shù)據(jù)庫(kù)拿出密碼,然后轉(zhuǎn)?base64?變成了另外一個(gè)更長(zhǎng)的字符串,所以怎么對(duì)比都是不通過(guò)的。

          source:?//smniuhe.github.io/2018/12/07/SpringBoot整合shiro-密碼加密

          分享&在看

          瀏覽 33
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  a视频免费看y | 天天日天天操天天插天天射 | 国产伦精品 | 免费一级黄色视频网站 | 理论在线视频 |