在一些場景中,比如某個(gè)領(lǐng)導(dǎo)因?yàn)橐恍┰虿荒苓M(jìn)行登錄網(wǎng)站進(jìn)行一些操作,他想把他網(wǎng)站上的工作委托給他的秘書,但是他不想把帳號 / 密碼告訴他秘書,只是想把工作委托給他;此時(shí)和我們可以使用 Shiro 的 RunAs 功能,即允許一個(gè)用戶假裝為另一個(gè)用戶(如果他們允許)的身份進(jìn)行訪問。
本章代碼基于《第十六章 綜合實(shí)例》,請先了解相關(guān)數(shù)據(jù)模型及基本流程后再學(xué)習(xí)本章。
表及數(shù)據(jù) SQL
請運(yùn)行 shiro-example-chapter21/sql/ shiro-schema.sql 表結(jié)構(gòu)
請運(yùn)行 shiro-example-chapter21/sql/ shiro-schema.sql 數(shù)據(jù)
實(shí)體
具體請參考 com.github.zhangkaitao.shiro.chapter21 包下的實(shí)體。
public class UserRunAs implements Serializable {
private Long fromUserId;//授予身份帳號
private Long toUserId;//被授予身份帳號
}
該實(shí)體定義了授予身份帳號(A)與被授予身份帳號(B)的關(guān)系,意思是 B 帳號將可以假裝為 A 帳號的身份進(jìn)行訪問。
DAO
具體請參考 com.github.zhangkaitao.shiro.chapter21.dao 包下的 DAO 接口及實(shí)現(xiàn)。
Service
具體請參考 com.github.zhangkaitao.shiro.chapter21.service 包下的 Service 接口及實(shí)現(xiàn)。
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);
}
提供授予身份、回收身份、關(guān)系存在判斷及查找 API。
Web 控制器 RunAsController
該控制器完成:授予身份 / 回收身份 / 切換身份功能。
展示當(dāng)前用戶能切換到身份列表,及授予給其他人的身份列表:
@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";
}
授予身份
把當(dāng)前用戶身份授予給另一個(gè)用戶,這樣另一個(gè)用戶可以切換身份到該用戶。
@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";
}
回收身份
把授予給某個(gè)用戶的身份回收回來。
@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", "對方?jīng)]有授予您身份,不能切換");
return "redirect:/runas";
}
subject.runAs(new SimplePrincipalCollection(switchToUser.getUsername(), ""));
redirectAttributes.addFlashAttribute("msg", "操作成功");
redirectAttributes.addFlashAttribute("needRefresh", "true");
return "redirect:/runas";
}
切換到上一個(gè)身份
@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";
}
此處注意的是我們可以切換多次身份,如 A 切換到 B,然后再切換到 C;那么需要調(diào)用兩次 Subject. releaseRunAs() 才能切換會 A;即內(nèi)部使用棧數(shù)據(jù)結(jié)構(gòu)存儲著切換過的用戶;Subject. getPreviousPrincipals() 得到上一次切換到的身份,比如當(dāng)前是 C;那么調(diào)用該 API 將得到 B 的身份。
其他代碼和配置和《第十六章 綜合實(shí)例》一樣,請參考該章。
測試
1、首先訪問 http://localhost:8080/chapter21/,輸入 admin/123456 進(jìn)行登錄;會看到如下界面:
2、點(diǎn)擊切換身份按鈕,跳到如下界面:
在該界面可以授權(quán)身份給其他人(點(diǎn)擊授權(quán)身份可以把自己的身份授權(quán)給其他人 / 點(diǎn)擊回收身份可以把之前授予的身份撤回)、或切換到其他身份(即假裝為其他身份運(yùn)行);
3、點(diǎn)擊切換到該身份按鈕,切換到相應(yīng)的身份運(yùn)行,如:
此時(shí) zhang 用戶切換到 admin 身份;如果點(diǎn)擊切換回該身份,會把當(dāng)前身份切換會 zhang。
更多建議: