继续我们shiro系列博客相关的学习笔记,各位看到此博客的小伙伴,如有不对的地方请及时通过私信我或者评论此博客的方式指出,以免误人子弟。多谢!
在Shiro系列的前几篇中提到,Shiro的授权方式有三种,回顾一下, 3 种方式如下:
-
以编程方式 - 您可以使用
if
和else
块等结构在 Java 代码中执行授权检查。 -
注解方式 - 您可以将授权注释附加到您的 Java 方法。
-
JSP/GSP 标签 - 您可以根据角色和权限控制 JSP 或 GSP 页面输出。
下面着重记录一下编程方式和注解方式两种授权实现方式。
目录
编程方式授权
执行授权的最简单和最常见的方法可能是以编程方式直接与当前Subject实例交。
基于角色的授权
如果您只想检查当前Subject是否具有角色,您可以在Subject实例上调用变体hasRole*方法。
如果您想根据更简单/传统的隐式角色名称控制访问,您可以执行角色检查:
角色检查
可以使用Subject的hasRole*相关方法进行角色判断,相关方法如下:
Subject方法 | 说明 |
hasRole(roleName) | 如果Subject有指定角色返回true,否则false |
hasRoles(List<String> roleNames) | 返回与方法参数中的索引对应的hasRole结果数组。 |
hasAllRoles(Collection<String> roleNames | 如果拥有所有指定的角色,则返回true,否则返回false。 |
角色断言
检查布尔值以查看Subject是否拥有某个角色,您可以简单地在执行逻辑之前断言它们具有预期的角色。如果Subject没有预期的角色,将抛出AuthorizationException,如果有预期的角色将继续执行后面的代码。
相关方法如下:
Subject方法 | 说明 |
checkRole(String roleName) | 如果Subject被分配了指定的角色,则继续执行,否则抛出AuthorizationException。 |
checkRoles(String... roleNames) | 如果Subject被分配了所有指定的角色,则继续执行,否则抛出AuthorizationException。 |
checkRoles(Collection<String> roleNames) | 同checkRoles(String... roleNames) |
使用示例如下:
Subject currentUser = SecurityUtils.getSubject();
currentUser.checkRole("admin");
doSomething();
基于权限的授权
如果你想检查一个Subject是否被允许做某事,你可以调用各种isPermitted*方法变体中的任何一个。检查权限的主要方法有两种 - 基于对象的权限实例或表示权限的字符串。
感觉这种授权方式不是很方便,不想记录它了,常用的是基于注解的权限和上面这种,感兴趣的小伙伴可以看下官网介绍。
基于注解的授权
@RequiresAuthentication
要求当前主题在当前会话期间已经过身份验证,这个注解基本上保证了subject . isAuthenticated() === true subject . isAuthenticated() === true。
示例:
@RequiresAuthentication
public void updateAccount(Account userAccount) {
//this method will only be invoked by a
//Subject that is guaranteed authenticated
...
}
等效于:
public void updateAccount(Account userAccount) {
if (!SecurityUtils.getSubject().isAuthenticated()) {
throw new AuthorizationException(...);
}
//Subject is guaranteed authenticated here
...
}
@RequiresPermissions
需要当前执行者的 Subject 拥有特定的权限才能执行带注释的方法。
示例:
@RequiresPermissions("account:create")
public void createAccount(Account account) {
//this method will only be invoked by a Subject
//that is permitted to create an account
...
}
等效于:
public void createAccount(Account account) {
Subject currentUser = SecurityUtils.getSubject();
if (!subject.isPermitted("account:create")) {
throw new AuthorizationException(...);
}
//Subject is guaranteed to be permitted here
...
}
@RequiresRoles
要求当前执行的Subject具有所有指定的角色。 如果他们没有角色,则不会执行该方法并抛出AuthorizationException 。
示例:
@RequiresRoles("administrator")
public void deleteUser(User user) {
//this method will only be invoked by an administrator
...
}
等效于:
public void deleteUser(User user) {
Subject currentUser = SecurityUtils.getSubject();
if (!subject.hasRole("administrator")) {
throw new AuthorizationException(...);
}
//Subject is guaranteed to be an 'administrator' here
...
}
注意:如果您的应用程序具有动态安全模型,可以在运行时添加和删除角色,请小心使用此注释。 如果您的应用程序允许在运行时删除带注释的角色,则任何人都无法执行该方法(至少在再次创建具有相同名称的新角色之前)。
比如上面示例中,加入你在程序运行期间,通过后台权限管理删除了administrator角色,那么将不会有任何人拥有administrator角色,那么也必将产生任何人都无法执行deleteUser这个方法。
代码实现
我们以基于注解的方式实现权限控制,那么首先不要忘了在Shiro的配置文件中开启对注解的支持,开启方式如下:
开启注解支持
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
@Qualifier("securityManager") SecurityManager securityManager)
{
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
完善测试代码
UserController
@Controller
@RequestMapping("/users")
public class UserController {
@Autowired
public UserManager userManager;
@RequiresRoles("common")
@RequestMapping("/list")
@ResponseBody
public List<User> list(){
List<User> userList = userManager.findUsers();
return userList;
}
@RequiresRoles("admin")
@RequestMapping("/reset")
@ResponseBody
public String reset(){
return " --- reset --- ";
}
@RequiresPermissions("system:user:delete")
@RequestMapping("/delete")
@ResponseBody
public String delete(){
return " --- delete --- ";
}
@RequiresPermissions("system:user:put")
@RequestMapping("/put")
@ResponseBody
public String put(){
return " --- put --- ";
}
@RequiresPermissions("system:user:view")
@RequestMapping("/view")
@ResponseBody
public String view(){
return " --- view --- ";
}
}
如上:我们新增一个UserController类,前两个方法我们使用@RequiresRoles注解测试基于注解的角色验证,后面三个方法我们使用@RequiresPermissions注解测试基于注解的资源的权限验证,为了方便测试,我们需要完善一下index.html,同时我们在Realm中模拟下对用户的权限设置,先看下index.html:
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Login Page</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body >
<span color="red">登录成功!</span>
<a href="/users/list" > list </a> <br>
<a href="/users/reset" > reset </a> <br>
<a href="/users/delete" > delete </a> <br>
<a href="/users/put" > put </a> <br>
<a href="/users/view" > view </a> <br><br>
</body>
</html>
一般我们验证用户身份和权限的时候,需要查询数据库,这里为了方便,并没有建表存储用户对应的角色和权限,只是模拟设置了一下,如下我们在UserManager中新增两个方法模拟下给用户赋权:
/**
* 查询用户角色
*/
public Set<String> findRoles(String userName){
Set<String> stringSet = new HashSet<>();
if("yjh".equals(userName)){
stringSet.add("admin");
stringSet.add("common");
}else{
stringSet.add("common");
}
return stringSet;
}
/**
* 查询用户权限
*/
public Set<String> findPermissions(String userName){
Set<String> stringSet = new HashSet<>();
if("yjh".equals(userName)){
stringSet.add("*:*:*");
}else{
stringSet.add("system:user:view");
stringSet.add("system:user:delete");
}
return stringSet;
}
完善后的CustomRealm
public class CustomRealm extends AuthorizingRealm {
@Autowired
private UserManager userManager;
@Autowired
private LoginManager loginManager;
/**
* 认证: 登录时调用
* AuthenticationToken 用于收集用户提交的身份(如用户名)及凭据(如密码)
* Object getPrincipal() --- 身份
* Object getCredentials() --- 凭据
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken utoken = (UsernamePasswordToken) token;
// 登录验证逻辑
String username = utoken.getUsername();
String password = new String(utoken.getPassword());
loginManager.login(username,password);
// 如果身份认证验证成功,返回一个AuthenticationInfo实现,保存主体和凭据。
return new SimpleAuthenticationInfo(utoken.getUsername(),new String(utoken.getPassword()),getName());
}
/**
* 授权
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 得到用户
String userName = (String)principals.getPrimaryPrincipal();
// 查询用户角色和权限
Set<String> roles = userManager.findRoles(userName);
Set<String> permissions = userManager.findPermissions(userName);
// 将角色和权限存储为内部属性
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(roles);
authorizationInfo.setStringPermissions(permissions);
return authorizationInfo;
}
}
测试
我们数据库中有两个用户,对应的角色、权限如下
用户角色:yjh:admin、common;lisi:common。
用户权限:yjh:所有权限;lisi:system:user:view、system:user:delete。
我们期望使用lisi登录成功后,可以访问list()、view()、delete()方法,访问其他方法时因权限不足而失败,使用yjh登录的时候可以访问所有方法。
下面使用lisi登录访问下list()、view()、put()方法结果如下:
其他方法可以自行测试。
注意:如果不在shiro的配置文件中开启注解支持,会发现权限验证失效了,使用lisi登录后访问任何方法都可以访问。