SpringBoot-安全框架Shiro使用及總結(jié)

Apache Shiro是Java的一個(gè)安全框架。相比于Spring Security,功能沒(méi)那么強(qiáng)大抽米,但是簡(jiǎn)單許多,下面我們了解的是在SpringBoot中集成Shiro框架理肺。

一.Shiro框架介紹:
本次我pom.xml用的Shiro版本是
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>

1.認(rèn)證與授權(quán)相關(guān)基本概念

兩個(gè)基本的概念

安全實(shí)體:系統(tǒng)需要保護(hù)的具體對(duì)象數(shù)據(jù)

權(quán)限:系統(tǒng)相關(guān)的功能操作摄闸,例如基本的CRUD

Authentication(認(rèn)證):身份認(rèn)證/登錄,驗(yàn)證用戶(hù)是不是擁有相應(yīng)的身份-即登錄妹萨;

Authorization(授權(quán)):授權(quán)年枕,即權(quán)限驗(yàn)證,驗(yàn)證某個(gè)已認(rèn)證的用戶(hù)是否擁有某個(gè)權(quán)限乎完;即判斷用戶(hù)是否能做事情熏兄,常見(jiàn)的如:驗(yàn)證某個(gè)用戶(hù)是否擁有某個(gè)角色。或者細(xì)粒度的驗(yàn)證某個(gè)用戶(hù)對(duì)某個(gè)資源是否具有某個(gè)權(quán)限摩桶;

Session Manager:會(huì)話(huà)管理桥状,即用戶(hù)登錄后就是一次會(huì)話(huà),在沒(méi)有退出之前硝清,它的所有信息都在會(huì)話(huà)中辅斟;會(huì)話(huà)可以是普通JavaSE環(huán)境的,也可以是如Web環(huán)境的芦拿;

Cryptography:加密士飒,保護(hù)數(shù)據(jù)的安全性,如密碼加密存儲(chǔ)到數(shù)據(jù)庫(kù)蔗崎,而不是明文存儲(chǔ)酵幕;

Web Support:Web支持,可以非常容易的集成到Web環(huán)境缓苛;

Caching:緩存芳撒,比如用戶(hù)登錄后,其用戶(hù)信息他嫡、擁有的角色/權(quán)限不必每次去查番官,這樣可以提高效率;

Concurrency:shiro支持多線(xiàn)程應(yīng)用的并發(fā)驗(yàn)證钢属,即如在一個(gè)線(xiàn)程中開(kāi)啟另一個(gè)線(xiàn)程徘熔,能把權(quán)限自動(dòng)傳播過(guò)去;

Testing:提供測(cè)試支持淆党;

Run As:允許一個(gè)用戶(hù)假裝為另一個(gè)用戶(hù)(如果他們?cè)试S)的身份進(jìn)行訪(fǎng)問(wèn)酷师;

Remember Me:記住我,這個(gè)是非常常見(jiàn)的功能染乌,即一次登錄后山孔,下次再來(lái)的話(huà)不用登錄了。

二.核心組件核心類(lèi)
1.Subject:當(dāng)前用戶(hù)荷憋,SUbject可以是一個(gè)人台颠,也可以是第三方服務(wù),與當(dāng)前應(yīng)用交互的任何東西都是Subject勒庄,如網(wǎng)絡(luò)爬蟲(chóng)串前,機(jī)器人等;即一個(gè)抽象概念实蔽;所有Subject都綁定到SecurityManager荡碾,與Subject的所有交互都會(huì)委托給SecurityManager;可以把Subject認(rèn)為是一個(gè)門(mén)面局装;SecurityManager才是實(shí)際的執(zhí)行者坛吁;
2.SecurityManager:安全管理器劳殖,管理所有Subject,可以配合內(nèi)部安全組件拨脉,你可以把它看成DispatcherServlet前端控制器哆姻。

3.principals:身份,即主體的標(biāo)識(shí)屬性女坑,可以是任何東西填具,如用戶(hù)名、郵箱等匆骗,唯一即可劳景。一個(gè)主體可以有多個(gè)principals,但只有一個(gè)Primary principals碉就,一般是用戶(hù)名/密碼/手機(jī)號(hào)盟广。
4.credentials:證明/憑證,即只有主體知道的安全值瓮钥,如密碼/數(shù)字證書(shū)等筋量。
最常見(jiàn)的principals和credentials組合就是用戶(hù)名/密碼了。

5.Realms:用于進(jìn)行權(quán)限信息的驗(yàn)證碉熄,需要自己實(shí)現(xiàn)桨武。
6.Realm 本質(zhì)上是一個(gè)特定的安全 DAO:它封裝與數(shù)據(jù)源連接的細(xì)節(jié),得到Shiro 所需的相關(guān)的數(shù)據(jù)锈津。
域呀酸,Shiro從從Realm獲取安全數(shù)據(jù)(如用戶(hù)、角色琼梆、權(quán)限)性誉,就是說(shuō)SecurityManager要驗(yàn)證用戶(hù)身份,那么它需要從Realm獲取相應(yīng)的用戶(hù)進(jìn)行比較以確定用戶(hù)身份是否合法茎杂;也需要從Realm得到用戶(hù)相應(yīng)的角色/權(quán)限進(jìn)行驗(yàn)證用戶(hù)是否能進(jìn)行操作错览;可以把Realm看成DataSource,即安全數(shù)據(jù)源煌往。
在配置 Shiro 的時(shí)候倾哺,你必須指定至少一個(gè)Realm 來(lái)實(shí)現(xiàn)認(rèn)證(authentication)和/或授權(quán)(authorization)。
我們需要實(shí)現(xiàn)Realms的Authentication 和 Authorization刽脖。其中 Authentication 是用來(lái)驗(yàn)證用戶(hù)身份悼粮,Authorization 是授權(quán)訪(fǎng)問(wèn)控制,用于對(duì)用戶(hù)進(jìn)行的操作授權(quán)曾棕,證明該用戶(hù)是否允許進(jìn)行當(dāng)前操作,如訪(fǎng)問(wèn)某個(gè)鏈接菜循,某個(gè)資源文件等翘地。

7.SimpleHash,可以通過(guò)特定算法(比如md5)配合鹽值salt,對(duì)密碼進(jìn)行多次加密衙耕。

三昧穿、Shiro配置

1.SpringBoot集成Shiro一般通過(guò)java代碼配合@Configuration和@Bean配置。
2.Shiro的核心是通過(guò)Filter實(shí)現(xiàn)的橙喘,Shiro中的Filter是通過(guò)URL規(guī)則來(lái)進(jìn)行過(guò)濾和權(quán)限校驗(yàn)时鸵,因此我們需要定義一些關(guān)于URL的規(guī)則喝訪(fǎng)問(wèn)權(quán)限。
3.SpringBoot集成Shiro厅瞎,我們需要寫(xiě)來(lái)給你個(gè)類(lèi)饰潜,ShiroConfiguration-用來(lái)配置Shiro,注入各種Bean(包括過(guò)濾器(shiroFilter)和簸、安全事務(wù)管理器(SecurityManager)彭雾、密碼憑證(CredentialsMatcher)、aop注解支持(authorizationAttributeSourceAdvisor)等等) 和繼承了AuthorizingRealm的Realm類(lèi)锁保,包括登陸認(rèn)證(doGetAuthenticationInfo)薯酝、授權(quán)認(rèn)證(doGetAuthorizationInfo)。

四.代碼實(shí)現(xiàn)
ShiroConfiguration類(lèi):

package com.example.demo.config;

import cn.licoy.wdog.core.config.mybatis.UserRealm;
import cn.licoy.wdog.core.config.shiro.CredentialsMatcher;
import org.apache.log4j.Logger;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfiguration {
private Logger logger=Logger.getLogger(ShiroConfiguration.class);

@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
    ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
    //設(shè)置安全管理器
    shiroFilterFactoryBean.setSecurityManager(securityManager);
    //默認(rèn)跳轉(zhuǎn)到登陸頁(yè)面
    shiroFilterFactoryBean.setLoginUrl("/login");
    //登陸成功后的頁(yè)面
    shiroFilterFactoryBean.setSuccessUrl("/index");
    shiroFilterFactoryBean.setUnauthorizedUrl("/403");

    //自定義過(guò)濾器
    Map<String,Filter> filterMap=new LinkedHashMap<>();
    shiroFilterFactoryBean.setFilters(filterMap);
    //權(quán)限控制map
    Map<String,String> filterChainDefinitionMap=new LinkedHashMap<>();
    // 配置不會(huì)被攔截的鏈接 順序判斷
    filterChainDefinitionMap.put("/static/**", "anon");
    //配置退出 過(guò)濾器,其中的具體的退出代碼Shiro已經(jīng)替我們實(shí)現(xiàn)了
    filterChainDefinitionMap.put("/logout", "logout");

// //:這是一個(gè)坑呢爽柒,一不小心代碼就不好使了;
// //
// filterChainDefinitionMap.put("/**", "anon");

    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    return shiroFilterFactoryBean;
}

/**
 * 核心的安全事務(wù)管理器
 * 設(shè)置realm吴菠、cacheManager等
 * @return
 */
@Bean
public SecurityManager securityManager(){
    DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager ();
    //設(shè)置realm
    securityManager.setRealm( myShiroRealm(  )  );
    securityManager.setRememberMeManager(rememberMeManager());
    securityManager.setCacheManager( ehCacheManager() );
    return securityManager;
}


/**
 * 身份認(rèn)證Realm,此處的注入不可以缺少浩村。否則會(huì)在UserRealm中注入對(duì)象會(huì)報(bào)空指針.
 * @return
 */
@Bean
public UserRealm myShiroRealm(  ){
    UserRealm myShiroRealm = new UserRealm();
    myShiroRealm.setCredentialsMatcher(  hashedCredentialsMatcher() );
    return myShiroRealm;
}

/**
 * 配置自定義的密碼比較器
 * @return
 */
@Bean
public CredentialsMatcher credentialsMatcher(){
    return  new CredentialsMatcher();
}


/**
 * 哈希密碼比較器做葵。在myShiroRealm中作用參數(shù)使用
 * 登陸時(shí)會(huì)比較用戶(hù)輸入的密碼,跟數(shù)據(jù)庫(kù)密碼配合鹽值salt解密后是否一致穴亏。
 * @return
 */
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
    HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
    hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:這里使用md5算法;
    hashedCredentialsMatcher.setHashIterations(2);//散列的次數(shù)蜂挪,比如散列兩次,相當(dāng)于 md5( md5(""));
    return hashedCredentialsMatcher;
}

/**
 *  shiro緩存管理器;
 * 需要注入對(duì)應(yīng)的其它的實(shí)體類(lèi)中: 安全管理器:securityManager
 * 可見(jiàn)securityManager是整個(gè)shiro的核心嗓化;
 * @return
 */
@Bean
public EhCacheManager ehCacheManager(){
    logger.info("------------->ShiroConfiguration.getEhCacheManager()執(zhí)行");
    EhCacheManager cacheManager=new EhCacheManager();
    cacheManager.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
    return cacheManager;
}

/**
 * 記住我管理器
 * @return
 */
@Bean
public CookieRememberMeManager rememberMeManager() {
    CookieRememberMeManager cookieRememberMeManager=new CookieRememberMeManager();
    cookieRememberMeManager.setCookie(rememberMeCookie());
    //rememberMe cookie加密的密鑰  默認(rèn)AES算法

// cookieRememberMeManager.setCipherKey();
return cookieRememberMeManager;
}

/**
 * cookie對(duì)象
 * @return
 */
@Bean
public Cookie rememberMeCookie() {
    SimpleCookie simpleCookie=new SimpleCookie("rememberMe");
    //記住我cookie生效時(shí)間棠涮,單位秒
    simpleCookie.setMaxAge(3600);
    return simpleCookie;
}


/**
 *  開(kāi)啟shiro aop注解支持.
 *  使用代理方式;所以需要開(kāi)啟代碼支持;否則@RequiresRoles等注解無(wú)法生效
 * @param securityManager
 * @return
 */
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
    AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
    authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
    return authorizationAttributeSourceAdvisor;
}

/**
 * Shiro生命周期處理器
 * @return
 */
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
    return new LifecycleBeanPostProcessor();
}

/**
 * 自動(dòng)創(chuàng)建代理
 * @return
 */
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
    DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
    advisorAutoProxyCreator.setProxyTargetClass(true);
    return advisorAutoProxyCreator;
}

}


自定義UserRealm-(MyRealm)類(lèi):

package cn.licoy.wdog.core.config.mybatis;
import com.example.demo.pojo.SysUserRole;
import com.example.demo.pojo.User;
import com.example.demo.service.UserSerevice;
import com.example.demo.utils.State;
import org.apache.log4j.Logger;
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.apache.shiro.util.ByteSource;

import javax.annotation.Resource;
import java.util.HashSet;
import java.util.Set;

public class UserRealm extends AuthorizingRealm {
@Resource(name = "userServiceImpl")
private UserSerevice userService;

private Logger logger=Logger.getLogger(UserRealm.class);

/**
 * 提供用戶(hù)信息,返回權(quán)限信息
 * @param principals
 * @return
 */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    logger.info("---------------------------->授權(quán)認(rèn)證:");
    SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();
    String userName=(String) principals.getPrimaryPrincipal();
    String userId=userService.findUserIdByName(userName);
    Set<SysUserRole> roleIdSet=userService.findRoleIdByUid( Integer.parseInt(userId) );
    Set<String> roleSet=new HashSet<>();
    Set<Integer>  pemissionIdSet=new HashSet<>();
    Set<String>  pemissionSet=new HashSet<>();
    for(SysUserRole roleInfo : roleIdSet) {
        int roleId=roleInfo.getRoleId();
        roleSet.add( userService.findRoleByRoleId( roleId  ) );
        //將擁有角色的所有權(quán)限放進(jìn)Set里面刺覆,也就是求Set集合的并集
        pemissionIdSet.addAll( userService.findPermissionIdByRoleId(  roleId ));
    }
    for(int permissionId : pemissionIdSet) {
        String permission= userService.findPermissionById( permissionId ).getPermission() ;
        pemissionSet.add(  permission );
    }
    // 將角色名稱(chēng)提供給授權(quán)info
    authorizationInfo.setRoles( roleSet );
    // 將權(quán)限名稱(chēng)提供給info
    authorizationInfo.setStringPermissions(pemissionSet);

    return authorizationInfo;
}

/**
 * 提供帳戶(hù)信息严肪,返回認(rèn)證信息
 * @param authenticationToken
 * @return
 * @throws AuthenticationException
 */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    logger.info("---------------------------->登陸驗(yàn)證:");
    String userName=(String)authenticationToken.getPrincipal();
    User user=userService.findUserByName(userName);
    if(user==null) {
        //用戶(hù)不存在就拋出異常
        throw new UnknownAccountException();
    }

//State.LOCKED="2" 用戶(hù)被鎖定狀態(tài),自定義
if( State.LOCKED.equals( user.getState() ) ) {
//用戶(hù)被鎖定就拋異常
throw new LockedAccountException();
}
//密碼可以通過(guò)SimpleHash加密谦屑,然后保存進(jìn)數(shù)據(jù)庫(kù)驳糯。
//此處是獲取數(shù)據(jù)庫(kù)內(nèi)的賬號(hào)、密碼氢橙、鹽值酝枢,保存到登陸信息info中
SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(user.getUserName(),
user.getPassword(),
ByteSource.Util.bytes(user.getSalt()) ,
getName()); //realm name

    return authenticationInfo;
}

}

更具體的代碼,參見(jiàn)碼云:
https://gitee.com/lufff1458/Shiro-Demo

可以參考watchdog整個(gè)框架中的shiro部分:
https://gitee.com/licoy/watchdog-framework/tree/master/src/main/java/cn/licoy/wdog/core/config

參考博客:

http://www.ityouknow.com/springboot/2017/06/26/springboot-shiro.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末悍手,一起剝皮案震驚了整個(gè)濱河市帘睦,隨后出現(xiàn)的幾起案子袍患,更是在濱河造成了極大的恐慌,老刑警劉巖竣付,帶你破解...
    沈念sama閱讀 212,686評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诡延,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡古胆,警方通過(guò)查閱死者的電腦和手機(jī)肆良,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,668評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)逸绎,“玉大人惹恃,你說(shuō)我怎么就攤上這事⊥傲迹” “怎么了座舍?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,160評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)陨帆。 經(jīng)常有香客問(wèn)我曲秉,道長(zhǎng),這世上最難降的妖魔是什么疲牵? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,736評(píng)論 1 284
  • 正文 為了忘掉前任承二,我火速辦了婚禮,結(jié)果婚禮上纲爸,老公的妹妹穿的比我還像新娘亥鸠。我一直安慰自己,他們只是感情好识啦,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,847評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布负蚊。 她就那樣靜靜地躺著,像睡著了一般颓哮。 火紅的嫁衣襯著肌膚如雪家妆。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 50,043評(píng)論 1 291
  • 那天冕茅,我揣著相機(jī)與錄音伤极,去河邊找鬼。 笑死姨伤,一個(gè)胖子當(dāng)著我的面吹牛哨坪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播乍楚,決...
    沈念sama閱讀 39,129評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼当编,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了徒溪?” 一聲冷哼從身側(cè)響起凌箕,我...
    開(kāi)封第一講書(shū)人閱讀 37,872評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤拧篮,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后牵舱,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,318評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缺虐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,645評(píng)論 2 327
  • 正文 我和宋清朗相戀三年芜壁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片高氮。...
    茶點(diǎn)故事閱讀 38,777評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡慧妄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出剪芍,到底是詐尸還是另有隱情塞淹,我是刑警寧澤,帶...
    沈念sama閱讀 34,470評(píng)論 4 333
  • 正文 年R本政府宣布罪裹,位于F島的核電站饱普,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏状共。R本人自食惡果不足惜套耕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,126評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望峡继。 院中可真熱鬧冯袍,春花似錦、人聲如沸碾牌。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,861評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)舶吗。三九已至征冷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間裤翩,已是汗流浹背资盅。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,095評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留踊赠,地道東北人呵扛。 一個(gè)月前我還...
    沈念sama閱讀 46,589評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像筐带,于是被迫代替她去往敵國(guó)和親今穿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,687評(píng)論 2 351