Java中Shiro安全框架的權(quán)限管理
前言
Apache Shiro是Java的一個安全框架。
目前,使用Apache Shiro的人越來越多,相比Spring Security而言相當簡單, 可能沒有Spring Security做的功能強大,但是在實際工作時可能并不需要那么復(fù)雜的東西, 所以使用小而簡單的Shiro就足夠了。
對于它倆到底哪個好,這個不必糾結(jié),能更簡單的解決項目問題就好了。
本文只介紹基本的Shiro使用,不會過多分析源碼等,重在使用。
Shiro架構(gòu)
Shiro可以非常容易的開發(fā)出足夠好的應(yīng)用,其不僅可以用在JavaSE環(huán)境,也可以用在JavaEE環(huán)境。
Shiro可以幫助我們完成:認證、授權(quán)、加密、會話管理、與Web集成、緩存等。
Shiro的API非常簡單,其基本功能點如下圖所示:

- Authentication:身份認證/登錄,驗證用戶是不是擁有相應(yīng)的身份;
- Authorization:授權(quán),即權(quán)限驗證,驗證某個已認證的用戶是否擁有某個權(quán)限;即判斷用戶是否能做事情,常見的如:驗證某個用戶是否擁有某個角色。或者細粒度的驗證某個用戶對某個資源是否具有某個權(quán)限;
- Session Manager:會話管理,即用戶登錄后就是一次會話,在沒有退出之前,它的所有信息都在會話中;會話可以是普通JavaSE環(huán)境的,也可以是如Web環(huán)境的;
- Cryptography:加密,保護數(shù)據(jù)的安全性,如密碼加密存儲到數(shù)據(jù)庫,而不是明文存儲;
- Web Support:Web支持,可以非常容易的集成到Web環(huán)境;
- Caching:緩存,比如用戶登錄后,其用戶信息、擁有的角色/權(quán)限不必每次去查,這樣可以提高效率;
- Concurrency:shiro支持多線程應(yīng)用的并發(fā)驗證,即如在一個線程中開啟另一個線程,能把權(quán)限自動傳播過去;
- Testing:提供測試支持;
- Run As:允許一個用戶假裝為另一個用戶(如果他們允許)的身份進行訪問;
- Remember Me:記住我,這個是非常常見的功能,即一次登錄后,下次再來的話不用登錄了。
記住一點,Shiro不會去維護用戶、維護權(quán)限,這些需要我們自己去設(shè)計/提供,然后通過相應(yīng)的接口注入給Shiro即可。
接下來我們分別從外部和內(nèi)部來看看Shiro的架構(gòu),對于一個好的框架,從外部來看應(yīng)該具有非常簡單易于使用的API, 且API契約明確;從內(nèi)部來看的話,其應(yīng)該有一個可擴展的架構(gòu),即非常容易插入用戶自定義實現(xiàn),因為任何框架都不能滿足所有需求。

可以看到:應(yīng)用代碼直接交互的對象是Subject,也就是說Shiro的對外API核心就是 Subject。
- Subject:主體,代表了當前”用戶”,這個用戶不一定是一個具體的人,與當前應(yīng)用交互的任何東西都是Subject,如網(wǎng)絡(luò)爬蟲,機器人等;即一個抽象概念;所有Subject都綁定到SecurityManager,與Subject的所有交互都會委托給SecurityManager;可以把Subject認為是一個門面;SecurityManager才是實際的執(zhí)行者;
- SecurityManager:安全管理器;即所有與安全有關(guān)的操作都會與SecurityManager交互;且它管理著所有Subject;可以看出它是Shiro的核心,它負責與后邊介紹的其他組件進行交互,如果學習過SpringMVC,你可以把它看成DispatcherServlet前端控制器;
- Realm:域,Shiro從從Realm獲取安全數(shù)據(jù)(如用戶、角色、權(quán)限),就是說SecurityManager要驗證用戶身份,那么它需要從Realm獲取相應(yīng)的用戶進行比較以確定用戶身份是否合法;也需要從Realm得到用戶相應(yīng)的角色/權(quán)限進行驗證用戶是否能進行操作;可以把Realm看成DataSource,即安全數(shù)據(jù)源。
也就是說對于我們而言,最簡單的一個Shiro應(yīng)用:
1、應(yīng)用代碼通過Subject 來進行認證和授權(quán),而 Subject 又委托給 SecurityManager;
2、我們需要給Shiro的 SecurityManager 注入 Realm,從而讓 SecurityManager 能得到合法的用戶及其權(quán)限進行判斷。
從以上也可以看出,Shiro不提供維護用戶/權(quán)限,而是通過Realm讓開發(fā)人員自己注入。
接下來我們來從Shiro內(nèi)部來看下Shiro的架構(gòu),如下圖所示:

- Subject:主體,可以看到主體可以是任何可以與應(yīng)用交互的”用戶”;
- SecurityManager:相當于SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher;是Shiro的心臟;所有具體的交互都通過SecurityManager進行控制;它管理著所有Subject、且負責進行認證和授權(quán)、及會話、緩存的管理。
- Authenticator:認證器,負責主體認證的,這是一個擴展點,如果用戶覺得Shiro默認的不好,可以自定義實現(xiàn);其需要認證策略(Authentication Strategy),即什么情況下算用戶認證通過了;
- Authorizer:授權(quán)器,或者訪問控制器,用來決定主體是否有權(quán)限進行相應(yīng)的操作;即控制著用戶能訪問應(yīng)用中的哪些功能;
- Realm:可以有1個或多個Realm,可以認為是安全實體數(shù)據(jù)源,即用于獲取安全實體的;可以是JDBC實現(xiàn),也可以是LDAP實現(xiàn),或者內(nèi)存實現(xiàn)等等;由用戶提供;注意:Shiro不知道你的用戶/權(quán)限存儲在哪及以何種格式存儲;所以我們一般在應(yīng)用中都需要實現(xiàn)自己的Realm;
- SessionManager:如果寫過Servlet就應(yīng)該知道Session的概念,Session呢需要有人去管理它的生命周期,這個組件就是SessionManager;而Shiro并不僅僅可以用在Web環(huán)境,也可以用在如普通的JavaSE環(huán)境、EJB等環(huán)境;所有呢,Shiro就抽象了一個自己的Session來管理主體與應(yīng)用之間交互的數(shù)據(jù);這樣的話,比如我們在Web環(huán)境用,剛開始是一臺Web服務(wù)器;接著又上了臺EJB服務(wù)器;這時想把兩臺服務(wù)器的會話數(shù)據(jù)放到一個地方,這個時候就可以實現(xiàn)自己的分布式會話(如把數(shù)據(jù)放到Memcached服務(wù)器);
- SessionDAO:DAO大家都用過,數(shù)據(jù)訪問對象,用于會話的CRUD,比如我們想把Session保存到數(shù)據(jù)庫,那么可以實現(xiàn)自己的SessionDAO,通過如JDBC寫到數(shù)據(jù)庫;比如想把Session放到Memcached中,可以實現(xiàn)自己的Memcached SessionDAO,另外SessionDAO中可以使用Cache進行緩存,以提高性能;
- CacheManager:緩存控制器,來管理如用戶、角色、權(quán)限等的緩存的;因為這些數(shù)據(jù)基本上很少去改變,放到緩存中后可以提高訪問的性能
- Cryptography:密碼模塊,Shiro提高了一些常見的加密組件用于如密碼加密/解密的。
SpringBoot 集成
實現(xiàn)一個最常見的驗證碼登錄、記住我、權(quán)限自定義(or),緩存功能的SpringBoot應(yīng)用,模板使用Thymeleaf3
Maven依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- thymeleaf模板中shiro標簽-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<!-- shiro 權(quán)限控制 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- shiro ehcache (shiro緩存)-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<!--驗證碼框架-->
<dependency>
<groupId>com.github.axet</groupId>
<artifactId>kaptcha</artifactId>
<version>0.0.9</version>
</dependency>新建一個配置類ShiroConfig.java,內(nèi)容如下:
/**
* Description : Apache Shiro 核心通過 Filter 來實現(xiàn),就好像SpringMvc 通過DispachServlet 來主控制一樣。
* 既然是使用 Filter 一般也就能猜到,是通過URL規(guī)則來進行過濾和權(quán)限校驗,所以我們需要定義一系列關(guān)于URL的規(guī)則和訪問權(quán)限。
*/
@Configuration
@Order(1)
public class ShiroConfig {
//配置kaptcha圖片驗證碼框架提供的Servlet,,這是個坑,很多人忘記注冊(注意)
@Bean
public ServletRegistrationBean kaptchaServlet() {
ServletRegistrationBean servlet = new ServletRegistrationBean(new KaptchaServlet(), "/kaptcha.jpg");
servlet.addInitParameter(Constants.KAPTCHA_SESSION_CONFIG_KEY, Constants.KAPTCHA_SESSION_KEY);//session key
servlet.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, "50");//字體大小
servlet.addInitParameter(Constants.KAPTCHA_BORDER, "no");
servlet.addInitParameter(Constants.KAPTCHA_BORDER_COLOR, "105,179,90");
servlet.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, "45");
servlet.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
servlet.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_FONT_NAMES, "宋體,楷體,微軟雅黑");
servlet.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
servlet.addInitParameter(Constants.KAPTCHA_IMAGE_WIDTH, "125");
servlet.addInitParameter(Constants.KAPTCHA_IMAGE_HEIGHT, "60");
//可以設(shè)置很多屬性,具體看com.google.code.kaptcha.Constants
// kaptcha.border 是否有邊框 默認為true 我們可以自己設(shè)置yes,no
// kaptcha.border.color 邊框顏色 默認為Color.BLACK
// kaptcha.border.thickness 邊框粗細度 默認為1
// kaptcha.producer.impl 驗證碼生成器 默認為DefaultKaptcha
// kaptcha.textproducer.impl 驗證碼文本生成器 默認為DefaultTextCreator
// kaptcha.textproducer.char.string 驗證碼文本字符內(nèi)容范圍 默認為abcde2345678gfynmnpwx
// kaptcha.textproducer.char.length 驗證碼文本字符長度 默認為5
// kaptcha.textproducer.font.names 驗證碼文本字體樣式 默認為new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
// kaptcha.textproducer.font.size 驗證碼文本字符大小 默認為40
// kaptcha.textproducer.font.color 驗證碼文本字符顏色 默認為Color.BLACK
// kaptcha.textproducer.char.space 驗證碼文本字符間距 默認為2
// kaptcha.noise.impl 驗證碼噪點生成對象 默認為DefaultNoise
// kaptcha.noise.color 驗證碼噪點顏色 默認為Color.BLACK
// kaptcha.obscurificator.impl 驗證碼樣式引擎 默認為WaterRipple
// kaptcha.word.impl 驗證碼文本字符渲染 默認為DefaultWordRenderer
// kaptcha.background.impl 驗證碼背景生成器 默認為DefaultBackground
// kaptcha.background.clear.from 驗證碼背景顏色漸進 默認為Color.LIGHT_GRAY
// kaptcha.background.clear.to 驗證碼背景顏色漸進 默認為Color.WHITE
// kaptcha.image.width 驗證碼圖片寬度 默認為200
// kaptcha.image.height 驗證碼圖片高度 默認為50
return servlet;
}
//注入異常處理類
@Bean
public MyExceptionResolver myExceptionResolver() {
return new MyExceptionResolver();
}
/**
* ShiroFilterFactoryBean 處理攔截資源文件問題。
* 注意:單獨一個ShiroFilterFactoryBean配置是或報錯的,以為在
* 初始化ShiroFilterFactoryBean的時候需要注入:SecurityManager Filter Chain定義說明
* 1、一個URL可以配置多個Filter,使用逗號分隔
* 2、當設(shè)置多個過濾器時,全部驗證通過,才視為通過
* 3、部分過濾器可指定參數(shù),如perms,roles
*/
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必須設(shè)置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
//驗證碼過濾器
Map<String, Filter> filtersMap = shiroFilterFactoryBean.getFilters();
KaptchaFilter kaptchaFilter = new KaptchaFilter();
filtersMap.put("kaptchaFilter", kaptchaFilter);
//實現(xiàn)自己規(guī)則roles,這是為了實現(xiàn)or的效果
//RoleFilter roleFilter = new RoleFilter();
//filtersMap.put("roles", roleFilter);
shiroFilterFactoryBean.setFilters(filtersMap);
// 攔截器
//rest:比如/admins/user/**=rest[user],根據(jù)請求的方法,相當于/admins/user/**=perms[user:method] ,其中method為post,get,delete等。
//port:比如/admins/user/**=port[8081],當請求的url的端口不是8081是跳轉(zhuǎn)到schemal://serverName:8081?queryString,其中schmal是協(xié)議http或https等,serverName是你訪問的host,8081是url配置里port的端口,queryString是你訪問的url里的?后面的參數(shù)。
//perms:比如/admins/user/**=perms[user:add:*],perms參數(shù)可以寫多個,多個時必須加上引號,并且參數(shù)之間用逗號分割,比如/admins/user/**=perms["user:add:*,user:modify:*"],當有多個參數(shù)時必須每個參數(shù)都通過才通過,想當于isPermitedAll()方法。
//roles:比如/admins/user/**=roles[admin],參數(shù)可以寫多個,多個時必須加上引號,并且參數(shù)之間用逗號分割,當有多個參數(shù)時,比如/admins/user/**=roles["admin,guest"],每個參數(shù)通過才算通過,相當于hasAllRoles()方法。//要實現(xiàn)or的效果看http://zgzty.blog.163.com/blog/static/83831226201302983358670/
//anon:比如/admins/**=anon 沒有參數(shù),表示可以匿名使用。
//authc:比如/admins/user/**=authc表示需要認證才能使用,沒有參數(shù)
//authcBasic:比如/admins/user/**=authcBasic沒有參數(shù)表示httpBasic認證
//ssl:比如/admins/user/**=ssl沒有參數(shù),表示安全的url請求,協(xié)議為https
//user:比如/admins/user/**=user沒有參數(shù)表示必須存在用戶,當?shù)侨氩僮鲿r不做檢查
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 配置退出過濾器,其中的具體的退出代碼Shiro已經(jīng)替我們實現(xiàn)了
filterChainDefinitionMap.put("/logout", "logout");
//配置記住我或認證通過可以訪問的地址
filterChainDefinitionMap.put("/index", "user");
filterChainDefinitionMap.put("/", "user");
filterChainDefinitionMap.put("/login", "kaptchaFilter");
// <!-- 過濾鏈定義,從上向下順序執(zhí)行,一般將 /**放在最為下邊 -->:這是一個坑呢,一不小心代碼就不好使了;
//這段是配合 actuator框架使用的,配置相應(yīng)的角色才能訪問
// filterChainDefinitionMap.put("/health", "roles[aix]");//服務(wù)器健康狀況頁面
// filterChainDefinitionMap.put("/info", "roles[aix]");//服務(wù)器信息頁面
// filterChainDefinitionMap.put("/env", "roles[aix]");//應(yīng)用程序的環(huán)境變量
// filterChainDefinitionMap.put("/metrics", "roles[aix]");
// filterChainDefinitionMap.put("/configprops", "roles[aix]");
//開放的靜態(tài)資源
filterChainDefinitionMap.put("/favicon.ico", "anon");//網(wǎng)站圖標
filterChainDefinitionMap.put("/static/**", "anon");//配置static文件下資源能被訪問的,這是個例子
filterChainDefinitionMap.put("/kaptcha.jpg", "anon");//圖片驗證碼(kaptcha框架)
filterChainDefinitionMap.put("/api/v1/**", "anon");//API接口
// swagger接口文檔
filterChainDefinitionMap.put("/v2/api-docs", "anon");
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/swagger-resources/**", "anon");
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/doc.html", "anon");
// 其他的
filterChainDefinitionMap.put("/**", "authc");
// 如果不設(shè)置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面
shiroFilterFactoryBean.setLoginUrl("/login");
// 登錄成功后要跳轉(zhuǎn)的鏈接
shiroFilterFactoryBean.setSuccessUrl("/index");
// 未授權(quán)界面,不生效(詳情原因看MyExceptionResolver)
shiroFilterFactoryBean.setUnauthorizedUrl("/errorView/403_error.html");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 設(shè)置realm.
securityManager.setRealm(myShiroRealm());
//注入緩存管理器
//這個如果執(zhí)行多次,也是同樣的一個對象;
securityManager.setCacheManager(ehCacheManager());
//注入記住我管理器;
securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}
/**
* 身份認證realm; (這個需要自己寫,賬號密碼校驗;權(quán)限等)
*/
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
}
/**
* 憑證匹配器 (由于我們的密碼校驗交給Shiro的SimpleAuthenticationInfo進行處理了
* 所以我們需要修改下doGetAuthenticationInfo中的代碼; @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 散列算法:這里使用MD5算法;
hashedCredentialsMatcher.setHashAlgorithmName("md5");
// 散列的次數(shù),比如散列兩次,相當于md5(md5(""));
hashedCredentialsMatcher.setHashIterations(2);
//表示是否存儲散列后的密碼為16進制,需要和生成密碼時的一樣,默認是base64;
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
return hashedCredentialsMatcher;
}
/**
* 開啟shiro aop注解支持. 使用代理方式; 所以需要開啟代碼支持;
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* shiro緩存管理器;
* 需要注入對應(yīng)的其它的實體類中:
* 安全管理器:securityManager
* 可見securityManager是整個shiro的核心;
* @return
*/
@Bean
public EhCacheManager ehCacheManager() {
EhCacheManager cacheManager = new EhCacheManager();
cacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
return cacheManager;
}
/**
* cookie對象;
* @return
*/
@Bean
public SimpleCookie rememberMeCookie() {
//System.out.println("ShiroConfiguration.rememberMeCookie()");
//這個參數(shù)是cookie的名稱,對應(yīng)前端的checkbox的name = rememberMe
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
//<!-- 記住我cookie生效時間30天 ,單位秒;-->
simpleCookie.setMaxAge(259200);
return simpleCookie;
}
/**
* cookie管理對象;
* @return
*/
@Bean
public CookieRememberMeManager rememberMeManager() {
//System.out.println("ShiroConfiguration.rememberMeManager()");
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
return cookieRememberMeManager;
}
@Bean(name = "sessionManager")
public DefaultWebSessionManager defaultWebSessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setGlobalSessionTimeout(18000000);
// url中是否顯示session Id
sessionManager.setSessionIdUrlRewritingEnabled(false);
// 刪除失效的session
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionValidationSchedulerEnabled(true);
sessionManager.setSessionValidationInterval(18000000);
sessionManager.setSessionValidationScheduler(getExecutorServiceSessionValidationScheduler());
//設(shè)置SessionIdCookie 導(dǎo)致認證不成功,不從新設(shè)置新的cookie,從sessionManager獲取sessionIdCookie
//sessionManager.setSessionIdCookie(simpleIdCookie());
sessionManager.getSessionIdCookie().setName("session-z-id");
sessionManager.getSessionIdCookie().setPath("/");
sessionManager.getSessionIdCookie().setMaxAge(60 * 60 * 24 * 7);
return sessionManager;
}
@Bean(name = "sessionValidationScheduler")
public ExecutorServiceSessionValidationScheduler getExecutorServiceSessionValidationScheduler() {
ExecutorServiceSessionValidationScheduler scheduler = new ExecutorServiceSessionValidationScheduler();
scheduler.setInterval(900000);
return scheduler;
}
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}代碼中解釋都非常清楚。
MyShiroRealm.java的內(nèi)容如下:
/**
* Description : 身份校驗核心類
*/
public class MyShiroRealm extends AuthorizingRealm {
private static final Logger _logger = LoggerFactory.getLogger(MyShiroRealm.class);
@Autowired
ManagerInfoService managerInfoService;
/**
* 認證信息.(身份驗證)
* Authentication 是用來驗證用戶身份
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
_logger.info("MyShiroRealm.doGetAuthenticationInfo()");
//獲取用戶的輸入的賬號.
String username = (String) token.getPrincipal();
//_logger.info("用戶的賬號:"+username);
//通過username從數(shù)據(jù)庫中查找 ManagerInfo對象
//實際項目中,這里可以根據(jù)實際情況做緩存,如果不做,Shiro自己也是有時間間隔機制,2分鐘內(nèi)不會重復(fù)執(zhí)行該方法
ManagerInfo managerInfo = managerInfoService.findByUsername(username);
if (managerInfo == null) {
return null;
}
//交給AuthenticatingRealm使用CredentialsMatcher進行密碼匹配,如果覺得人家的不好可以自定義實現(xiàn)
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
managerInfo, //用戶
managerInfo.getPassword(), //密碼
ByteSource.Util.bytes(managerInfo.getCredentialsSalt()),//salt=username+salt
getName() //realm name
);
//明文: 若存在,將此用戶存放到登錄認證info中,無需自己做密碼對比,Shiro會為我們進行密碼對比校驗
// SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
// managerInfo, //用戶名
// managerInfo.getPassword(), //密碼
// getName() //realm name
// );
return authenticationInfo;
}
/**
* 此方法調(diào)用hasRole,hasPermission的時候才會進行回調(diào).
* <p>
* 權(quán)限信息.(授權(quán)):
* 1、如果用戶正常退出,緩存自動清空;
* 2、如果用戶非正常退出,緩存自動清空;
* 3、如果我們修改了用戶的權(quán)限,而用戶不退出系統(tǒng),修改的權(quán)限無法立即生效。
* (需要手動編程進行實現(xiàn);放在service進行調(diào)用)
* 在權(quán)限修改后調(diào)用realm中的方法,realm已經(jīng)由spring管理,所以從spring中獲取realm實例,調(diào)用clearCached方法;
* :Authorization 是授權(quán)訪問控制,用于對用戶進行的操作授權(quán),證明該用戶是否允許進行當前操作,如訪問某個鏈接,某個資源文件等。
*
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
/*
* 當沒有使用緩存的時候,不斷刷新頁面的話,這個代碼會不斷執(zhí)行,
* 當其實沒有必要每次都重新設(shè)置權(quán)限信息,所以我們需要放到緩存中進行管理;
* 當放到緩存中時,這樣的話,doGetAuthorizationInfo就只會執(zhí)行一次了,
* 緩存過期之后會再次執(zhí)行。
*/
_logger.info("權(quán)限配置-->MyShiroRealm.doGetAuthorizationInfo()");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
ManagerInfo managerInfo = (ManagerInfo) principals.getPrimaryPrincipal();
//設(shè)置相應(yīng)角色的權(quán)限信息
for (SysRole role : managerInfo.getRoles()) {
//設(shè)置角色
authorizationInfo.addRole(role.getRole());
for (Permission p : role.getPermissions()) {
//設(shè)置權(quán)限
authorizationInfo.addStringPermission(p.getPermission());
}
}
return authorizationInfo;
}
/**
* 設(shè)置認證加密方式
*/
@Override
public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
HashedCredentialsMatcher md5CredentialsMatcher = new HashedCredentialsMatcher();
md5CredentialsMatcher.setHashAlgorithmName(ShiroKit.HASH_ALGORITHM_NAME);
md5CredentialsMatcher.setHashIterations(ShiroKit.HASH_ITERATIONS);
super.setCredentialsMatcher(md5CredentialsMatcher);
}
}自定義異常處理類MyExceptionResolver.java :
public class MyExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
//如果是shiro無權(quán)操作,因為shiro 在操作auno等一部分不進行轉(zhuǎn)發(fā)至無權(quán)限url
if (ex instanceof UnauthorizedException) {
return new ModelAndView("error/shiro_403");
}
return null;
}
}接口控制
配置好了Shiro后就可以通過注解方式來限制某些接口調(diào)用需要相應(yīng)的角色或權(quán)限了:
@RequestMapping(value = "/index")
@RequiresRoles("admin")
public String index(HttpServletRequest request, Model model) {
_logger.info("進入項目管理首頁...");
}其他的注解請參考官網(wǎng)的 Shiro注解
Thymeleaf的shiro標簽
可以在Thymeleaf模板中使用shiro的權(quán)限標簽來控制某些菜單或按鈕是否顯示。
maven中添加依賴,這個前面已經(jīng)有了:
<!-- thymeleaf模板中shiro標簽-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>在 ShiroConfig 中添加一個Bean配置:
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}在html頁面添加如下內(nèi)容:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">添加完后在html頁面調(diào)用如下:
<!-- 認證通過或已記住的用戶。 -->
<p shiro:user="">
Welcome back John! Not John? Click <a href="login.html">here</a> to login.
</p>
<p shiro:notAuthenticated="">
未身份驗證(包括記住我)
</p>
<p shiro:guest=""><span style="white-space:pre;"> </span>Please <a href="login.html">login5555</a> </p>第二種:
<shiro:guest>
<a>登錄</a> <a>注冊</a>
</shiro:guest>
<shiro:user>
歡迎<shiro:principal property="name"/>
</shiro:user>權(quán)限數(shù)據(jù)庫設(shè)計
一般來講都會講用戶的角色和權(quán)限保存到數(shù)據(jù)庫,這里設(shè)計一種最通用的模型, 使用RBAC(Role-Based Access Control,基于角色的訪問控制)模型設(shè)計用戶,角色和權(quán)限間的關(guān)系。
簡單地說, 一個用戶擁有若干角色,每一個角色擁有若干權(quán)限。這樣,就構(gòu)造成”用戶-角色-權(quán)限”的授權(quán)模型。
在這種模型中,用戶與角色之間,角色與權(quán)限之間,一般者是多對多的關(guān)系。如下圖所示:

我們通過MyBatis實現(xiàn)ManagerInfoService,
/**
* 后臺用戶管理
*/
@Service
public class ManagerInfoService {
@Resource
private ManagerInfoDao managerInfoDao;
public ManagerInfo findByUsername(String username) {
return managerInfoDao.findByUsername(username);
}
}然后對應(yīng)的ManagerInfoDao.xml如下:
<resultMap id="ManagerInfoMap" type="managerInfo">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="name" column="name"/>
<result property="password" column="password"/>
<result property="salt" column="salt"/>
<result property="state" column="state"/>
<collection property="roles" ofType="sysRole">
<id property="id" column="role_id"/>
<result property="role" column="role_role"/>
<collection property="permissions" ofType="permission">
<id property="id" column="perm_id"/>
<result property="permission" column="perm_permission"/>
</collection>
</collection>
</resultMap>
<select id="findByUsername" resultMap="ManagerInfoMap">
SELECT DISTINCT
A.id AS id,
A.username AS username,
A.name AS name,
A.password AS password,
A.salt AS salt,
A.state AS state,
C.id AS role_id,
C.role AS role_role,
E.id AS perm_id,
E.permission AS perm_permission
FROM t_manager A
LEFT JOIN t_manager_role B ON A.id=B.manager_id
LEFT JOIN t_role C ON B.role_id=C.id
LEFT JOIN t_role_permission D ON C.id=D.role_id
LEFT JOIN t_permission E ON D.permission_Id=E.id
WHERE username=#{username}
LIMIT 1
</select>ManagerInfo.java如下:
public class ManagerInfo extends Manager implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 一個管理員具有多個角色
*/
private List<SysRole> roles;// 一個用戶具有多個角色
}到此這篇關(guān)于Java中Shiro安全框架的權(quán)限管理的文章就介紹到這了,更多相關(guān)Shiro框架的權(quán)限管理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java 中的FileReader和FileWriter源碼分析_動力節(jié)點Java學院整理
本文給大家分享一段示例程序,通過示例代碼可以看出FileReader是基于InputStreamReader實現(xiàn)的,FileWriter是基于OutputStreamWriter實現(xiàn)的,具體程序代碼大家通過本文了解下吧2017-05-05
SpringBoot3實戰(zhàn)教程之實現(xiàn)接口簽名驗證功能
接口簽名是一種重要的安全機制,用于確保 API 請求的真實性、數(shù)據(jù)的完整性以及防止重放攻擊,這篇文章主要介紹了SpringBoot3實戰(zhàn)教程之實現(xiàn)接口簽名驗證功能,需要的朋友可以參考下2025-04-04
深入理解Mybatis中的resultType和resultMap
這篇文章給大家介紹了mybatis中的resultType和resultMap的用法實例講解,MyBatis中在查詢進行select映射的時候,返回類型可以用resultType,也可以用resultMap,至于兩種用法區(qū)別,通過本文一起學習吧2016-09-09
淺析Java設(shè)計模式編程中的單例模式和簡單工廠模式
這篇文章主要介紹了淺析Java設(shè)計模式編程中的單例模式和簡單工廠模式,使用設(shè)計模式編寫代碼有利于團隊協(xié)作時程序的維護,需要的朋友可以參考下2016-01-01

