在一些場景中,比如某個領導因為一些原因不能進行登錄網站進行一些操作,他想把他網站上的工作委托給他的秘書,但是他不想把帳號 / 密碼告訴他秘書,只是想把工作委托給他;此時和我們可以使用 Shiro 的 RunAs 功能,即允許一個用戶假裝為另一個用戶(如果他們允許)的身份進行訪問。
本章代碼基于《第十六章 綜合實例》,請先了解相關數據模型及基本流程后再學習本章。
**表及數據 SQL**
請運行 shiro-example-chapter21/sql/ shiro-schema.sql 表結構
請運行 shiro-example-chapter21/sql/ shiro-schema.sql 數據
**實體**
具體請參考 com.github.zhangkaitao.shiro.chapter21 包下的實體。
[**]( "復制到剪切板")[**]( "添加到筆記")public class UserRunAs implements Serializable {
private Long fromUserId;//授予身份帳號
private Long toUserId;//被授予身份帳號
}
該實體定義了授予身份帳號(A)與被授予身份帳號(B)的關系,意思是 B 帳號將可以假裝為 A 帳號的身份進行訪問。
**DAO**
具體請參考 com.github.zhangkaitao.shiro.chapter21.dao 包下的 DAO 接口及實現。
**Service**
具體請參考 com.github.zhangkaitao.shiro.chapter21.service 包下的 Service 接口及實現。
[**]( "復制到剪切板")[**]( "添加到筆記")public interface UserRunAsService {
public void grantRunAs(Long fromUserId, Long toUserId);
public void revokeRunAs(Long fromUserId, Long toUserId);
public boolean exists(Long fromUserId, Long toUserId);
public List\<Long\> findFromUserIds(Long toUserId);
public List\<Long\> findToUserIds(Long fromUserId);
}
提供授予身份、回收身份、關系存在判斷及查找 API。
**Web 控制器 RunAsController**
該控制器完成:授予身份 / 回收身份 / 切換身份功能。
**展示當前用戶能切換到身份列表,及授予給其他人的身份列表**:
[**]( "復制到剪切板")[**]( "添加到筆記")@RequestMapping
public String runasList(@CurrentUser User loginUser, Model model) {
model.addAttribute("fromUserIds",
userRunAsService.findFromUserIds(loginUser.getId()));
model.addAttribute("toUserIds", userRunAsService.findToUserIds(loginUser.getId()));
List<User> allUsers = userService.findAll();
allUsers.remove(loginUser);
model.addAttribute("allUsers", allUsers);
Subject subject = SecurityUtils.getSubject();
model.addAttribute("isRunas", subject.isRunAs());
if(subject.isRunAs()) {
String previousUsername =
(String)subject.getPreviousPrincipals().getPrimaryPrincipal();
model.addAttribute("previousUsername", previousUsername);
}
return "runas";
}
1. Subject.isRunAs():表示當前用戶是否是 RunAs 用戶,即已經切換身份了;
2. Subject.getPreviousPrincipals():得到切換身份之前的身份,一個用戶可以切換很多次身份,之前的身份使用棧數據結構來存儲;
**授予身份**
把當前用戶身份授予給另一個用戶,這樣另一個用戶可以切換身份到該用戶。
[**]( "復制到剪切板")[**]( "添加到筆記")@RequestMapping("/grant/{toUserId}")
public String grant( @CurrentUser User loginUser, @PathVariable("toUserId") Long toUserId, RedirectAttributes redirectAttributes) {
if(loginUser.getId().equals(toUserId)) {
redirectAttributes.addFlashAttribute("msg", "自己不能切換到自己的身份");
return "redirect:/runas";
}
userRunAsService.grantRunAs(loginUser.getId(), toUserId);
redirectAttributes.addFlashAttribute("msg", "操作成功");
return "redirect:/runas";
}
1. 自己不能授予身份給自己;
2. 調用 UserRunAsService. grantRunAs 把當前登錄用戶的身份授予給相應的用戶;
**回收身份**
把授予給某個用戶的身份回收回來。
[**]( "復制到剪切板")[**]( "添加到筆記")@RequestMapping("/revoke/{toUserId}")
public String revoke( @CurrentUser User loginUser, @PathVariable("toUserId") Long toUserId, RedirectAttributes redirectAttributes) {
userRunAsService.revokeRunAs(loginUser.getId(), toUserId);
redirectAttributes.addFlashAttribute("msg", "操作成功");
return "redirect:/runas";
}
**切換身份**
[**]( "復制到剪切板")[**]( "添加到筆記")@RequestMapping("/switchTo/{switchToUserId}")
public String switchTo( @CurrentUser User loginUser, @PathVariable("switchToUserId") Long switchToUserId, RedirectAttributes redirectAttributes) {
Subject subject = SecurityUtils.getSubject();
User switchToUser = userService.findOne(switchToUserId);
if(loginUser.equals(switchToUser)) {
redirectAttributes.addFlashAttribute("msg", "自己不能切換到自己的身份");
return "redirect:/runas";
}
if(switchToUser == null || !userRunAsService.exists(switchToUserId, loginUser.getId())) {
redirectAttributes.addFlashAttribute("msg", "對方沒有授予您身份,不能切換");
return "redirect:/runas";
}
subject.runAs(new SimplePrincipalCollection(switchToUser.getUsername(), ""));
redirectAttributes.addFlashAttribute("msg", "操作成功");
redirectAttributes.addFlashAttribute("needRefresh", "true");
return "redirect:/runas";
}
1. 首先根據 switchToUserId 查找到要切換到的身份;
2. 然后通過 UserRunAsService. exists() 判斷當前登錄用戶是否可以切換到該身份;
3. 通過 Subject.runAs() 切換到該身份;
**切換到上一個身份**
[**]( "復制到剪切板")[**]( "添加到筆記")@RequestMapping("/switchBack")
public String switchBack(RedirectAttributes redirectAttributes) {
Subject subject = SecurityUtils.getSubject();
if(subject.isRunAs()) {
subject.releaseRunAs();
}
redirectAttributes.addFlashAttribute("msg", "操作成功");
redirectAttributes.addFlashAttribute("needRefresh", "true");
return "redirect:/runas";
}
1. 通過 Subject.releaseRunAs() 切換會上一個身份;
此處注意的是我們可以切換多次身份,如 A 切換到 B,然后再切換到 C;那么需要調用兩次 Subject. releaseRunAs() 才能切換會 A;即內部使用棧數據結構存儲著切換過的用戶;Subject. getPreviousPrincipals() 得到上一次切換到的身份,比如當前是 C;那么調用該 API 將得到 B 的身份。
其他代碼和配置和《第十六章 綜合實例》一樣,請參考該章。
**測試**
1、首先訪問 [](http://localhost:8080/chapter21/)<http://localhost:8080/chapter21/>,輸入 admin/123456 進行登錄;會看到如下界面:

2、點擊切換身份按鈕,跳到如下界面:

在該界面可以授權身份給其他人(點擊授權身份可以把自己的身份授權給其他人 / 點擊回收身份可以把之前授予的身份撤回)、或切換到其他身份(即假裝為其他身份運行);
3、點擊切換到該身份按鈕,切換到相應的身份運行,如:

此時 zhang 用戶切換到 admin 身份;如果點擊切換回該身份,會把當前身份切換會 zhang。
- 第1章 Shiro簡介
- 1.1 簡介
- 第2章 身份驗證
- 第3章 授權
- 第4章 INI配置
- 第5章 編碼 / 加密
- 第6章 Realm及相關對象
- 第7章 與Web集成
- 第8章 攔截器機制
- 第9章 JSP標簽
- 第10章 會話管理
- 第11章 緩存機制
- 第12章 與Spring集成
- 第13章 RememberMe
- 第14章 SSL
- 第15章 單點登錄
- 第16章 綜合實例
- 第17章 OAuth2集成
- 第18章 并發登錄人數控制
- 第19章 動態URL權限控制
- 第20章 無狀態Web應用集成
- 第21章 授予身份及切換身份
- 第22章 集成驗證碼
- 第23章 多項目集中權限管理及分布式會話
- 第24章 在線會話管理