Spring MVC整合Shiro權(quán)限控制的方法
Apache Shiro 是一個(gè)功能強(qiáng)大且靈活的開(kāi)放源代碼安全框架,可以細(xì)粒度地處理認(rèn)證 (Authentication),授權(quán) (Authorization),會(huì)話 (Session) 管理和加密 (cryptography) 等企業(yè)級(jí)應(yīng)用中常見(jiàn)的安全控制流程。 Apache Shiro 的首要目標(biāo)是易于使用和理解。 有時(shí)候安全性的流程控制會(huì)非常復(fù)雜,對(duì)開(kāi)發(fā)人員來(lái)說(shuō)是件很頭疼的事情,但并不一定如此。 框架就應(yīng)該盡可能地掩蓋復(fù)雜性,并公開(kāi)一個(gè)簡(jiǎn)潔而直觀的 API,從而簡(jiǎn)化開(kāi)發(fā)人員的工作,確保其應(yīng)用程序安全性。這次我們聊一聊如何在 Spring Web 應(yīng)用中使用 Shiro 實(shí)現(xiàn)權(quán)限控制。
功能
Apache Shiro 是一個(gè)具有許多功能的綜合型應(yīng)用程序安全框架。 下圖為 Shiro 中的最主要的幾個(gè)功能:

Shiro 的主要目標(biāo)是“應(yīng)用安全的四大基石” - 認(rèn)證,授權(quán),會(huì)話管理和加密:
- 身份驗(yàn)證:也就是通常所說(shuō)的 “登錄”,為了證明用戶的行為所有者。
- 授權(quán):訪問(wèn)控制的過(guò)程,即確定什么用戶可以訪問(wèn)哪些內(nèi)容。
- 會(huì)話管理:即使在非 Web 應(yīng)用程序中,也可以管理用戶特定的會(huì)話,這也是 Shiro 的一大亮點(diǎn)。
- 加密技術(shù):使用加密算法保證數(shù)據(jù)的安全,非常易于使用。
架構(gòu)
從整體概念上理解,Shiro 的體系架構(gòu)有三個(gè)主要的概念:Subject (主體,也就是用戶),Security Manager (安全管理器)和 Realms (領(lǐng)域)。 下圖描述了這些組件之間的關(guān)系:

這幾大組件可以這樣理解:
- Subject (主體):主體是當(dāng)前正在操作的用戶的特定數(shù)據(jù)集合。主體可以是一個(gè)人,也可以代表第三方服務(wù),守護(hù)進(jìn)程,定時(shí)任務(wù)或類似的東西,也就是幾乎所有與該應(yīng)用進(jìn)行交互的事物。
- Security Manager (安全管理器):它是 Shiro 的體系結(jié)構(gòu)的核心,扮演了類似于一把 “傘” 的角色,它主要負(fù)責(zé)協(xié)調(diào)內(nèi)部的各個(gè)組件,形成一張安全網(wǎng)。
- Realms (領(lǐng)域):Shiro 與應(yīng)用程序安全數(shù)據(jù)之間的 “橋梁”。當(dāng)需要實(shí)際與用戶帳戶等安全相關(guān)數(shù)據(jù)進(jìn)行交互以執(zhí)行認(rèn)證和授權(quán)時(shí),Shiro 將從 Realms 中獲取這些數(shù)據(jù)。
數(shù)據(jù)準(zhǔn)備
在 Web 應(yīng)用中,對(duì)安全的控制主要有角色、資源、權(quán)限(什么角色能訪問(wèn)什么資源)幾個(gè)概念,一個(gè)用戶可以有多個(gè)角色,一個(gè)角色也可以訪問(wèn)多個(gè)資源,也就是角色可以對(duì)應(yīng)多個(gè)權(quán)限。落實(shí)到數(shù)據(jù)庫(kù)設(shè)計(jì)上,我們至少需要建 5 張表:用戶表、角色表、資源表、角色-資源表、用戶-角色表,這 5 張表的結(jié)構(gòu)如下:
用戶表:
| id | username | password |
|---|---|---|
| 1 | 張三 | 123456 |
| 2 | 李四 | 666666 |
| 3 | 王五 | 000000 |
角色表:
| id | rolename |
|---|---|
| 1 | 管理員 |
| 2 | 經(jīng)理 |
| 3 | 員工 |
資源表:
| id | resname |
|---|---|
| 1 | /user/add |
| 2 | /user/delete |
| 3 | /compony/info |
角色-資源表:
| id | roleid | resid |
|---|---|---|
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 3 |
用戶-角色表:
| id | userid | roleid |
|---|---|---|
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 3 |
對(duì)應(yīng)的 POJO 類如下:
/**
* 用戶
*/
public class User {
private Integer id;
private String username;
private String password;
//getter & setter...
}
/**
* 角色
*/
public class Role {
private String id;
private String rolename;
}
/**
* 資源
*/
public class Resource {
private String id;
private String resname;
}
/**
* 角色-資源
*/
public class RoleRes {
private String id;
private String roleid;
private String resid;
}
/**
* 用戶-角色
*/
public class UserRole {
private String id;
private String userid;
private String roleid;
}
Spring 與 Shiro 整合的詳細(xì)步驟,請(qǐng)參閱我的博客 《 Spring 應(yīng)用中整合 Apache Shiro 》 。 這里補(bǔ)充一下:需要提前引入 Shiro 的依賴,打開(kāi)mvnrepository.com,搜索 Shiro,我們需要前三個(gè)依賴,也就是 Shiro-Core、Shiro-Web 以及 Shiro-Spring,以 Maven 項(xiàng)目為例,在 pom.xml 中的 <dependencies> 節(jié)點(diǎn)下添加如下依賴:
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency>
在 application-context.xml 中需要這樣配置 shiroFilter bean:
<!-- 配置shiro的過(guò)濾器工廠類,id- shiroFilter要和我們?cè)趙eb.xml中配置的過(guò)濾器一致 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <!-- 登錄頁(yè)面 --> <property name="loginUrl" value="/login"/> <!-- 登錄成功后的頁(yè)面 --> <property name="successUrl" value="/index"/> <!-- 非法訪問(wèn)跳轉(zhuǎn)的頁(yè)面 --> <property name="unauthorizedUrl" value="/403"/> <!-- 權(quán)限配置 --> <property name="filterChainDefinitions"> <value> <!-- 無(wú)需認(rèn)證即可訪問(wèn)的靜態(tài)資源,還可以添加其他 url --> /static/** = anon <!-- 除了上述忽略的資源,其他所有資源都需要認(rèn)證后才能訪問(wèn) --> /** = authc </value> </property> </bean>
接下來(lái)就需要定義 Realm 了,自定義的 Realm 集成自 AuthorizingRealm 類:
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
/**
* 驗(yàn)證權(quán)限
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String loginName = SecurityUtils.getSubject().getPrincipal().toString();
if (loginName != null) {
String userId = SecurityUtils.getSubject().getSession().getAttribute("userSessionId").toString();
// 權(quán)限信息對(duì)象,用來(lái)存放查出的用戶的所有的角色及權(quán)限
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 用戶的角色集合
ShiroUser shiroUser = (ShiroUser) principalCollection.getPrimaryPrincipal();
info.setRoles(shiroUser.getRoles());
info.addStringPermissions(shiroUser.getUrlSet());
return info;
}
return null;
}
/**
* 認(rèn)證回調(diào)函數(shù),登錄時(shí)調(diào)用
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
String username = (String) token.getPrincipal();
User user = new User();
sysuser.setUsername(username);
try {
List<SysUser> users = userService.findByNames(user);
List<String> roleList= userService.selectRoleNameListByUserId(users.get(0).getId());
if (users.size() != 0) {
String pwd = users.get(0).getPassword();
// 當(dāng)驗(yàn)證都通過(guò)后,把用戶信息放在 session 里
Session session = SecurityUtils.getSubject().getSession();
session.setAttribute("userSession", users.get(0));
session.setAttribute("userSessionId", users.get(0).getId());
session.setAttribute("userRoles", org.apache.commons.lang.StringUtils.join(roleList,","));
return new SimpleAuthenticationInfo(username,users.get(0).getPassword());
} else {
// 沒(méi)找到該用戶
throw new UnknownAccountException();
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
return null;
}
/**
* 更新用戶授權(quán)信息緩存.
*/
public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
super.clearCachedAuthorizationInfo(principals);
}
/**
* 更新用戶信息緩存.
*/
public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
super.clearCachedAuthenticationInfo(principals);
}
/**
* 清除用戶授權(quán)信息緩存.
*/
public void clearAllCachedAuthorizationInfo() {
getAuthorizationCache().clear();
}
/**
* 清除用戶信息緩存.
*/
public void clearAllCachedAuthenticationInfo() {
getAuthenticationCache().clear();
}
/**
* 清空所有緩存
*/
public void clearCache(PrincipalCollection principals) {
super.clearCache(principals);
}
/**
* 清空所有認(rèn)證緩存
*/
public void clearAllCache() {
clearAllCachedAuthenticationInfo();
clearAllCachedAuthorizationInfo();
}
}
最后定義一個(gè)用戶登錄的控制器,接受用戶的登錄請(qǐng)求:
@Controller
public class UserController {
/**
* 用戶登錄
*/
@PostMapping("/login")
public String login(@Valid User user,BindingResult bindingResult,RedirectAttributes redirectAttributes){
try {
if(bindingResult.hasErrors()){
return "login";
}
//使用權(quán)限工具進(jìn)行認(rèn)證,登錄成功后跳到 shiroFilter bean 中定義的 successUrl
SecurityUtils.getSubject().login(new UsernamePasswordToken(user.getUsername(), user.getPassword()));
return "redirect:index";
} catch (AuthenticationException e) {
redirectAttributes.addFlashAttribute("message","用戶名或密碼錯(cuò)誤");
return "redirect:login";
}
}
/**
* 注銷登錄
*/
@GetMapping("/logout")
public String logout(RedirectAttributes redirectAttributes ){
SecurityUtils.getSubject().logout();
return "redirect:login";
}
}
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- spring boot中controller的使用及url參數(shù)的獲取方法
- 淺談SpringBoot處理url中的參數(shù)的注解
- Spring根據(jù)URL參數(shù)進(jìn)行路由的方法詳解
- Spring Boot 定制URL匹配規(guī)則的方法
- spring mvc中的@PathVariable獲得請(qǐng)求url中的動(dòng)態(tài)參數(shù)
- SpringBoot+Spring Security+JWT實(shí)現(xiàn)RESTful Api權(quán)限控制的方法
- SpringBoot集成Shiro進(jìn)行權(quán)限控制和管理的示例
- Spring security實(shí)現(xiàn)登陸和權(quán)限角色控制
- Spring Security如何使用URL地址進(jìn)行權(quán)限控制
相關(guān)文章
java 實(shí)現(xiàn)微信服務(wù)器下載圖片到自己服務(wù)器
這篇文章主要介紹了 java 實(shí)現(xiàn)微信服務(wù)器下載圖片到自己服務(wù)器的相關(guān)資料,需要的朋友可以參考下2017-05-05
SpringCloud Config配置中心原理以及環(huán)境切換方式
這篇文章主要介紹了SpringCloud Config配置中心原理以及環(huán)境切換方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
idea中Java實(shí)體類怎樣生成序列化的版本號(hào)的方法
這篇文章主要介紹了idea中Java實(shí)體類怎樣生成序列化的版本號(hào)的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11
springboot bean掃描路徑的實(shí)現(xiàn)
這篇文章主要介紹了springboot bean掃描路徑的實(shí)現(xiàn),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-01-01
Java 互相關(guān)聯(lián)的實(shí)體無(wú)限遞歸問(wèn)題的解決
這篇文章主要介紹了Java 互相關(guān)聯(lián)的實(shí)體無(wú)限遞歸問(wèn)題的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
關(guān)于pytorch相關(guān)部分矩陣變換函數(shù)的問(wèn)題分析
這篇文章主要介紹了pytorch相關(guān)部分矩陣變換函數(shù),包括tensor維度順序變換BCHW順序的調(diào)整,矩陣乘法相關(guān)函數(shù),矩陣乘,點(diǎn)乘,求取矩陣對(duì)角線元素或非對(duì)角線元素的問(wèn)題,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-03-03
詳解java封裝實(shí)現(xiàn)Excel建表讀寫操作
這篇文章給大家分享了java封裝實(shí)現(xiàn)Excel建表讀寫操作的相關(guān)知識(shí)點(diǎn)內(nèi)容,有需要的朋友們可以學(xué)習(xí)下。2018-08-08
Java多線程并發(fā)的指令重排序問(wèn)題及volatile寫屏障原理詳解
這篇文章主要介紹了Java多線程并發(fā)的指令重排序問(wèn)題及volatile寫屏障原理詳解,指令重排序是編譯器或處理器為了提高性能而對(duì)指令執(zhí)行順序進(jìn)行重新排列的優(yōu)化技術(shù),需要的朋友可以參考下2024-01-01

