spring-shiro權(quán)限控制realm實(shí)戰(zhàn)教程
spring-shiro權(quán)限控制realm
用戶(hù)與角色實(shí)體
Role.java
@Data @Entity public class Role { @Id @GeneratedValue private Integer id; private Long userId; private String role; }
User.java
@Data @Entity public class User { @Id @GeneratedValue private Long id; private String username; private String password; }
Realm類(lèi)
首先建立 Realm 類(lèi),繼承自 AuthorizingRealm,自定義我們自己的授權(quán)和認(rèn)證的方法。Realm 是可以訪(fǎng)問(wèn)特定于應(yīng)用程序的安全性數(shù)據(jù)(如用戶(hù),角色和權(quán)限)的組件。
Realm.java
public class Realm extends AuthorizingRealm { @Autowired private UserService userService; //授權(quán) @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //從憑證中獲得用戶(hù)名 String username = (String) SecurityUtils.getSubject().getPrincipal(); //根據(jù)用戶(hù)名查詢(xún)用戶(hù)對(duì)象 User user = userService.getUserByUserName(username); //查詢(xún)用戶(hù)擁有的角色 List<Role> list = roleService.findByUserId(user.getId()); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); for (Role role : list) { //賦予用戶(hù)角色 info.addStringPermission(role.getRole()); } return info; } //認(rèn)證 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //獲得當(dāng)前用戶(hù)的用戶(hù)名 String username = (String) authenticationToken.getPrincipal(); //從數(shù)據(jù)庫(kù)中根據(jù)用戶(hù)名查找用戶(hù) User user = userService.getUserByUserName(username); if (userService.getUserByUserName(username) == null) { throw new UnknownAccountException( "沒(méi)有在本系統(tǒng)中找到對(duì)應(yīng)的用戶(hù)信息。"); } SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(),getName()); return info; } }
Shiro 配置類(lèi)
ShiroConfig.java
@Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); //以下是過(guò)濾鏈,按順序過(guò)濾,所以/**需要放最后 //開(kāi)放的靜態(tài)資源 filterChainDefinitionMap.put("/favicon.ico", "anon");//網(wǎng)站圖標(biāo) filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean public DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(myRealm()); return defaultWebSecurityManager; } @Bean public MyRealm myRealm() { MyRealm myRealm = new MyRealm(); return myRealm; } }
控制器
UserController.java
@Controller public class UserController { @Autowired private UserService userService; @GetMapping("/") public String index() { return "index"; } @GetMapping("/login") public String toLogin() { return "login"; } @GetMapping("/admin") public String admin() { return "admin"; } @PostMapping("/login") public String doLogin(String username, String password) { UsernamePasswordToken token = new UsernamePasswordToken(username, password); Subject subject = SecurityUtils.getSubject(); try { subject.login(token); } catch (Exception e) { e.printStackTrace(); } return "redirect:admin"; } @GetMapping("/home") public String home() { Subject subject = SecurityUtils.getSubject(); try { subject.checkPermission("admin"); } catch (UnauthorizedException exception) { System.out.println("沒(méi)有足夠的權(quán)限"); } return "home"; } @GetMapping("/logout") public String logout() { return "index"; } }
Service
UserService.java
@Service public class UserService { @Autowired private UserDao userDao; public User getUserByUserName(String username) { return userDao.findByUsername(username); } @RequiresRoles("admin") public void send() { System.out.println("我現(xiàn)在擁有角色admin,可以執(zhí)行本條語(yǔ)句"); } }
shiro權(quán)限不生效原因分析
shiro遇到的坑
-項(xiàng)目中使用shiro做登錄校驗(yàn)和權(quán)限管理,在配置權(quán)限時(shí)遇到小坑,記錄一下。
- 環(huán)境:springboot+freemarker+shiro
- 場(chǎng)景:后臺(tái)管理,配置菜單以及按鈕權(quán)限,分為三個(gè)層級(jí),一二級(jí)暫時(shí)只考慮是否查看權(quán)限,第三層級(jí)為頁(yè)面按鈕權(quán)限,分增刪改查。詳情看圖
- 問(wèn)題:一二層級(jí)正常,第三層級(jí)權(quán)限不起作用!
權(quán)限標(biāo)簽定義如下:
標(biāo)簽定義 | 頁(yè)面一 | 頁(yè)面二 |
---|---|---|
第一層級(jí) | one:view | two:view |
第二層級(jí) | one:page1:view | two:page2:view |
第三層級(jí) | one:page1:view:add | two:page2:view:add |
開(kāi)始懷疑是數(shù)據(jù)庫(kù)沒(méi)有錄入,查看后權(quán)限標(biāo)簽與角色已對(duì)應(yīng),排除。
后面懷疑是頁(yè)面問(wèn)題,后面把第三層級(jí)標(biāo)簽與第一二層級(jí)同一頁(yè)面,依然不起作用,排除。
后面懷疑是權(quán)限標(biāo)簽定義問(wèn)題,把第三層級(jí)標(biāo)簽改為one:page1:data:add,奇跡出現(xiàn),權(quán)限生效。證實(shí)權(quán)限標(biāo)簽定義出了問(wèn)題。
問(wèn)題原因:權(quán)限標(biāo)簽定義問(wèn)題
但是后來(lái)想想為什么會(huì)出現(xiàn)這種問(wèn)題,每個(gè)標(biāo)簽都是獨(dú)一無(wú)二的,對(duì)此我對(duì)shiro對(duì)于權(quán)限標(biāo)簽的校驗(yàn)產(chǎn)生了興趣,查看源碼,一路debug后最終在org.apache.shiro.authz.permission中看到了關(guān)鍵所在,核心代碼如下
//當(dāng)這個(gè)方法返回true時(shí)說(shuō)明有此權(quán)限 //這個(gè)p是代表當(dāng)前循環(huán)匹配到的權(quán)限標(biāo)簽 public boolean implies(Permission p) { // By default only supports comparisons with other WildcardPermissions if (!(p instanceof WildcardPermission)) { return false; } WildcardPermission wp = (WildcardPermission) p; //把當(dāng)前標(biāo)簽轉(zhuǎn)分割成一個(gè)set集合(如one:page1:view:add 會(huì)分割成[[one], [page1], [view], [add]]) List<Set<String>> otherParts = wp.getParts(); int i = 0; //循環(huán)匹配權(quán)限標(biāo)簽 for (Set<String> otherPart : otherParts) { // If this permission has less parts than the other permission, everything after the number of parts contained // in this permission is automatically implied, so return true //當(dāng)全部循環(huán)匹配完沒(méi)有返回false,則返回true,這個(gè)getparts()方法是獲取當(dāng)前角色當(dāng)前循環(huán)的權(quán)限標(biāo)簽([[one], [page1], [view]]) if (getParts().size() - 1 < i) { return true; } else { Set<String> part = getParts().get(i); /*如果包含有‘*'而且不包含當(dāng)前分割后的標(biāo)簽則返回false, *當(dāng)用戶(hù)可以查看頁(yè)面,也就是說(shuō)當(dāng)前角色擁有one:page1:view標(biāo)簽 *這里【!part.contains(WILDCARD_TOKEN)】返回true,第二個(gè)【part.containsAll(otherPart)】one會(huì)跟當(dāng)前標(biāo)簽匹**配one, *也就是說(shuō)這里全部循環(huán)完返回的都是false,所以最后都沒(méi)true,于是在上面返回了一個(gè)true。 if (!part.contains(WILDCARD_TOKEN) && !part.containsAll(otherPart)) { return false; } i++; } }
小結(jié)一下:通過(guò)分析,我們看到了shiro在定義權(quán)限標(biāo)簽時(shí),要主意匹配問(wèn)題,不要存在包含問(wèn)題,類(lèi)似aaa 和aaab ,會(huì)導(dǎo)致后面標(biāo)簽失效。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Spring Boot自定義favicon實(shí)現(xiàn)方法實(shí)例解析
這篇文章主要介紹了Spring Boot自定義favicon實(shí)現(xiàn)方法實(shí)例解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08Apache Commons Math3探索之多項(xiàng)式曲線(xiàn)擬合實(shí)現(xiàn)代碼
這篇文章主要介紹了Apache Commons Math3探索之多項(xiàng)式曲線(xiàn)擬合實(shí)現(xiàn)代碼,小編覺(jué)得挺不錯(cuò)的,這里分享給大家,供需要的朋友參考。2017-10-10SpringBoot集成MyBatis對(duì)管理員的查詢(xún)操作
本文主要介紹了SpringBoot集成MyBatis對(duì)管理員的查詢(xún)操作,實(shí)現(xiàn)增刪改查中的查詢(xún)操作,對(duì)所有的普通管理員進(jìn)行查詢(xún)操作,感興趣的可以了解一下2023-11-11Java和JVM的重載識(shí)別,重寫(xiě)方法是怎樣進(jìn)行的
這篇文章主要介紹了Java和JVM的重載識(shí)別,重寫(xiě)方法是怎樣進(jìn)行的,違章圍繞了Java和JVM的重載識(shí)別,重寫(xiě)方法展開(kāi)相關(guān)資料,需要的小伙伴可以參考一下,希望對(duì)你的工作或?qū)W習(xí)有所幫助2022-01-01Spring動(dòng)態(tài)注冊(cè)多數(shù)據(jù)源的實(shí)現(xiàn)方法
這篇文章主要介紹了Spring動(dòng)態(tài)注冊(cè)多數(shù)據(jù)源的實(shí)現(xiàn)方法,小編覺(jué)的挺不錯(cuò)的,現(xiàn)分享到腳本之家平臺(tái),需要的朋友可以參考下2018-01-01Java編程小實(shí)例—數(shù)字時(shí)鐘的實(shí)現(xiàn)代碼示例
正所謂拳不離手曲不離口,java學(xué)習(xí)的過(guò)程中,練習(xí)還是要多一點(diǎn)比較好。接下來(lái)分享給大家一個(gè)Java編程的小實(shí)例,供朋友們參考。2017-10-10