Shiro 安全框架(三)

135 阅读2分钟

Shiro 安全框架(三)

前面介绍了自定义 Realm 的两个方法的作用和简单写法。

这里稍微详细介绍授权方法联动前端页面。

前端页面

采用的是 JSP,需要引入 shiro 标签。

<%
    String path = request.getContextPath();
    String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/";
%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<html>
<head>
    <base href="<%=basePath%>"/>
    <title>Document</title>
</head>
<body>
    <h1>系统主页v1.0</h1>
    <a href="user/logout">用户退出</a>
    <ul>
        <shiro:hasAnyRoles name="user,root">
            <li><a href="">用户管理</a></li>
        </shiro:hasAnyRoles>
        
        <shiro:hasRole name="root">
            <li><a href="">商品管理</a></li>
            <li><a href="">订单管理</a></li>
            <li><a href="">物流管理</a></li>
        </shiro:hasRole>
    </ul>
</body>
</html>

自定义 Realm

public class CustomerRealm extends AuthorizingRealm {

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
        if (primaryPrincipal.equals("root")){ // 假数据,一般根据数据库中登录的用户分配对应的权限
            // 添加授权信息
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            // 设置 info 的授权,通过 addRole 实现给当前登录的主体授予角色
            info.addRole("user");
            // info.addRoles(new ArrayList<String>());
            return info;
        }
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("认证触发");
        String principal = (String) authenticationToken.getPrincipal();
        // 默认是类名首字母小写,也可以指定name
        //UserService userService = (UserService) SpringBeanFactoryUtils.getBean("userService");
        UserService service = SpringBeanFactoryUtils.getBean(UserService.class);
        User user = service.findUserByName(principal);
        // 从数据库查询用户进行认证
        if (!ObjectUtils.isEmpty(user)){
            return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),ByteSource.Util.bytes(user.getSalt()),this.getName());
        }
        return null;
    }
}

登录 root 用户(具有 root 授权)查看:

image-20221126223047191

登录 tom 用户(具有 user 授权)查看:

image-20221126223035084

实现了前端页面与后端实际用户授权进行联动,在前端完成权限控制。

除了角色(role),还有对应资源的控制(permission),info.addStringPermission("资源标识符:操作:资源实例标识符")。进而对特定角色的指定资源也进行权限控制。

下面就介绍在代码中完成权限控制,这个权限控制应该发生在 Controller 层,对于个别需要权限判断的请求,可以对其进行权限判断,来做出相应的处理:

这里就用之前第二篇的代码演示:

// 认证通过
if (subject.isAuthenticated()){
    // 1. 基于角色权限控制
    System.out.println(subject.hasRole("admin")); // 查询主体是否有 admin 这个角色

    // 2. 多角色权限控制
    boolean b = subject.hasAllRoles(Arrays.asList("admin","user")); // 查询主体是否有 admin、user 这两个角色,如果没有返回false
    System.out.println(b);

    // 3. 查看主体分别有那些角色

    boolean[] booleans = subject.hasRoles(Arrays.asList("admin", "super", "user"));
    for (boolean aBoolean : booleans) {
        System.out.println(aBoolean);
    }

    System.out.println("================基于权限字符串的访问控制================");

    // 4. 基于权限字符串的访问控制  资源标识符:操作:资源类型

    // 查看主体是否具有 对 admin 下的 001 资源具有 create 操作的权限
    System.out.println(subject.isPermitted("admin:create:001"));

    // 查询主体分别对 "admin:create:*"、"user:*:001" 是否具有权限
    boolean[] permitted = subject.isPermitted("admin:create:*", "user:*:001","user:create:002");
    for (boolean b1 : permitted) {
        System.out.println(b1);
    }

    // 查看主体是否同时具有权限
    System.out.println(subject.isPermittedAll("admin:create:*", "user:update:001","user:create:002"));
}

shiro 权限控制有三种,剩下的一种就是走注解方式:

@RequiresRoles(value={"user","admin","root"}) // 用于判断角色,参数是 String[],如果只有一个角色可以不加 value 直接写字符串,如果主体没有对应的 role 则报错,反之可以调用该方法。(需要注意的是如果多个 role 必须是同时具有上述的角色)
@RequiresPermissions(value={"user:update:01","admin*","root:*:02"}) // 用来判断权限字符串