【Shiro 自学笔记三】Shiro 的授权机制

这一期来了解一下 Shiro 的授权。

授权的概念

主体

主体,即访问应用的用户,在 Shiro 中使用 Subject 代表用户。用户只有授权后才允许访问相应的资源。

资源

在应用中用户可以访问的任何东西都称为资源。用户只有授权后才能访问。

权限

安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权利。即权限表示在应用中用户能不能访问某个资源。
Shiro 支持粗颗粒度权限(如用户模块的所有权限)和细颗粒度权限(操作某个用户的权限,即实例级别的)。

角色

角色代表了操作集合,可以理解为权限的集合,一般情况下我们会赋予用户角色而不是权限,即这样用户可以拥有一组权限。不同的角色拥有一组不同的权限。

举一个例子,如果我们有两种角色,admin 和 guest。

接着,我们需要给不同的角色不同的权限,例如 admin 可以访问所有页面,guest 只能访问首页。

最后,在用户登录时,我们就可以通过给用户赋予不同的角色,来规定其可以访问的页面。

授权的类型

Shiro 的授权类型有两种:基于角色的访问控制、基于资源的访问控制。

基于角色的访问控制

通过判断用户是否拥有某种角色,来判断是否拥有权限,如:

if (subject.hasRole("admin"))
  System.out.println("用户是 admin");

if (subject.hasAllRoles(Arrays.asList("admin", "guest")))
  System.out.println("用户是 admin 也是 guest");
      
boolean[] booleans = subject.hasRoles(Arrays.asList("admin", "guest"));
for (boolean aBoolean : booleans) {
  System.out.println(aBoolean);
}

基于资源的访问控制

Shiro 中使用权限字符串实现基于资源的访问控制,权限字符串的结构是:

  • 资源标识符 : 操作 : 资源实例标识符

其中,可以使用 * 通配所有内容。

事实上,上面的结构是一种通用的做法,只要用 : 分割的字符串都可以作为权限字符串。

例:

if (subject.isPermitted("user:find:*"))
  System.out.println("用户拥有查找所有用户的权限");

if (subject.isPermitted("user:*:10001"))
  System.out.println("用户拥有对10001用户的所有权限");

判断是否授权

Shiro 中有三种判断授权的方式:编程式、注解式、标签式。

编程式

即上面的例子,使用 subject.hasrole()subject.isPermitted() 判断。

注解式

@RequiresAuthenthentication: 表示当前 Subject 已经通过 login 进行身份验证
@RequiresUser: 表示当前Subject已经身份验证或者通过记住我登录的
@RequiresGuest: 表示当前Subject没有身份验证或者通过记住我登录过,即是游客身份
@RequiresRoles(value = {"admin","user"},logical = Logical.AND): 表示当前 Subject 需要角色 admin 和 user
@RequiresPermissions(value = {"user:delete","user:b"},logical = Logical.OR): 表示当前Subject需要权限 user:delete 或者 user:b

使用 @RequiresRoles 注解,如:

@RequiresRoles(value = "admin")
public void hello() {
  System.out.println("hello");
}

@RequiresRoles(value = {"admin", "guest"})
public void hello2() {
  System.out.println("hello");
}

注:该方法似乎只能在 Web 环境生效,笔者不明其原因

标签式

在 JSP 等页面中使用,如:

<shiro:hasRole name="admin">
    <h2> Hello Admin! </h2>
</shiro:hasRole>

实现授权和判断

注意在上一期中,AuthorizingRealm 要求我们重写两个方法:

  • doGetAuthenticationInfo 用户登录验证
  • doGetAuthorizationInfo 用户授权

接下来我们就来重写用户授权方法。

首先 doGetAuthorizationInfo 有一个 principalCollection 参数,这个参数有一个 getPrimaryPrincipal 方法,用于获取用户的主身份信息,在本例中其实就是用户名。

然后,与登录的 SimpleAuthenticationInfo 相对应,授权同样有一个 SimpleAuthorizationInfo 类。

这个类可以通过 addRole 方法添加角色权限,也可以通过 addStringPermission 添加权限字符串。

在这个例子中,我们为名为 koorye 的用户添加了 admin 角色权限和 user 资源权限:

package org.koorye.helloshiro;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

public class UserRealm extends AuthorizingRealm {
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    String principal = (String) principalCollection.getPrimaryPrincipal();
    System.out.println("用户名:" + principal);
    SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
    if ("koorye".equals(principal)) {
      simpleAuthorizationInfo.addRole("admin");
      simpleAuthorizationInfo.addStringPermission("user");  // 等同于 user:*:*
    }
    return simpleAuthorizationInfo;
  }

  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    if ("koorye".equals(authenticationToken.getPrincipal())) {  // 用户存在
      return new SimpleAuthenticationInfo(authenticationToken.getPrincipal(),
          "e9261b98c415bee7eaf191f89bee80c9",
          ByteSource.Util.bytes("Koorye_Love_MD5"),
          this.getName());
    } else {
      return null;  // 用户不存在
    }
  }
}

测试方法:

package org.koorye.test;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.Test;
import org.koorye.helloshiro.UserRealm;


public class TestShiro {
  @Test
  public void showMd5() {
    Md5Hash md5Hash = new Md5Hash("123456", "Koorye_Love_MD5", 1024);
    System.out.println(md5Hash.toHex());
  }

  @Test
  public void testLogin() {
    DefaultSecurityManager manager = new DefaultSecurityManager();

    HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
    matcher.setHashAlgorithmName("md5");
    matcher.setHashIterations(1024);

    UserRealm userRealm = new UserRealm();
    userRealm.setCredentialsMatcher(matcher);

    manager.setRealm(userRealm);
    SecurityUtils.setSecurityManager(manager);
    Subject subject = SecurityUtils.getSubject();

    UsernamePasswordToken token = new UsernamePasswordToken("koorye", "123456");
    try {
      subject.login(token);
      System.out.println("登录成功");

      if (subject.isAuthenticated()) {
        if (subject.hasRole("admin"))
          System.out.println("用户是 admin");

        if (subject.isPermitted("user:find:*"))
          System.out.println("用户拥有查找所有用户的权限");
      }
    } catch (UnknownAccountException e) {
      e.printStackTrace();
      System.out.println("用户名不存在");
    } catch (AuthenticationException e) {
      e.printStackTrace();
      System.out.println("密码错误");
    }
  }
}

运行:

登录成功
用户名:koorye
用户是 admin
用户名:koorye
用户拥有查找所有用户的权限

Process finished with exit code 0

注意 用户名:koorye 是认证过程中调用的,这也意味着每次认证,都会调用一次授权方法。

我们将授权改为 guest 再次尝试:

if ("koorye".equals(principal)) {
  simpleAuthorizationInfo.addRole("guest");
  simpleAuthorizationInfo.addStringPermission("user");  // 等同于 user:*:*
}

运行:

登录成功
用户名:koorye
用户名:koorye
用户拥有查找所有用户的权限

Process finished with exit code 0

注意到 用户是 admin 语句不再返回。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值