五、授权

授权,也叫访问控制,即在应用中控制谁访问哪些资源(如访问页面/编辑数据等)

  • 主体:访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只有授权后才允许访问相应的资源。
  • 资源:在应用中用户可以访问的URL,比如访问JSP页面,查看/编辑某些数据等等。
  • 权限:安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权利;Shiro 支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限,即实例级别的)。
  • 角色:权限的集合,一般情况下会赋予用户角色而不是权限。
授权方式

Shiro支持三种方式的授权:

  1. 编程式: Subject#hasRole()
  2. 注解式:通过在执行的Java方法上放置相应的直接完成,没有权限将抛出相应的异常,如:@RequiresRoles()
  3. JSP/GSP标签:<shiro:hasRole name=""></shiro:hasRole>
默认拦截器

Shiro 内置了很多默认的拦截器,比如身份验证、授权等相关的,默认拦截器可以参考org.apache.shiro.web.filter.mgt.DefaultFilter.class中的枚举拦截器:
  • 身份验证相关的:
默认拦截器名 拦截器类 说明(括号内为默认值)
authc FormAuthenticationFilter 基于表单的拦截;如 "/**=authc",如果没有登录会跳转到响应的登录页面登录;
主要属性:
usernameParam:表单提交的用户名参数名(username);
password:表单提交的密码参数名(rememberMe);
loginUrl:登录页面地址(/login.jsp);
successUrl:登录成功后的默认重定向地址;
failureKeyAttribute:登录失败后操作信息存储key(shiroLoginFailure);
authcBasic BasicHttpAuthenticationFilter Basic HTTP身份验证拦截器,主要属性:
applicationName: 弹出登录提示框的信息(application)
logout LogoutFilter 退出拦截器,主要属性:
redirectUrl:退出成功后重定向的地址(/);
user UserFilter 用户拦截器,用户已经身份验证/记住我登录的都可;
anon AnonymousFilter 匿名拦截器,即不通过登录即可访问;一般用于静态资源过滤
  • 授权相关的:
默认拦截器名 拦截器类 说明(括号内为默认值)
roles RolesAuthorizationFilter 角色授权拦截器,验证用户是否拥有该角色;
主要属性:
loginUrl:登录页面地址(/login.jsp);
unauthorizedUrl:未授权后重定向的地址;
perms PermissionsAuthorizationFilter 权限授权拦截器,验证用户是否拥有所有权限;属性和roles一样
port PortFilter 端口拦截器,主要属性:
port:可以通过的端口(80)
实例:"/pay/1=port[80]",如果用户访问该请求时非80,
将自动将请求端口修改为80并重定向到该80端口,其他路径/参数一致
rest HttpMethodPermissionFilter rest风格拦截器,自动根据请求方法构造权限字符串
(GET=read,POST=cratea,PUT=update,DELETE=delete,HEAD=read,
TRACE=read,OPTIONS=read,MKCOL=create)
ssl SslFilter SSL拦截器,只有请求协议是https才能通过;否则自动跳转到https端口;其他和port拦截器一样
  • 其他:
默认拦截器名 拦截器类 说明(括号内为默认值)
noSessionCreation NoSessionCreationFilter 不创建会话拦截器
Permissions
  • 规则:
    资源标识符:操作:对象实例ID即对哪个资源的哪个实例进行什么操作.其默认支持通配符权限字符串,冒号(:)表示资源/操作/实例的分隔;逗号(,)表示操作的分隔,星号(*)表示任意资源/操作/实例。
  • 多层次管理:
  1. 例如:user:query、user.edit
  2. 冒号是一个特殊字符,它用来分隔权限字符串的下一部件:第一部分是权限被操作的领域(打印机),第二部分是被执行的操作。
  3. 多个值:每个部件能够保护多个值。因此,出了授予用户 user:query 和 user:edit 权限外,也可以简单的授予他们一个:user:quert,edit
  4. 还可以用 * 号代替所有的值,如:user:*,也可以写:*:query,表示某个用户在所有的领域都有query的权限。
Shiro 的 Permissions
  • 实例级访问控制
  1. 这种情况通常会使用三个部件:域、操作、被付诸实施的实例。如:user:edit:manager
  2. 也可以使用通配符来定义,如:user:edit:*、user:*:*、user:*:manager
  3. 部分省略通配符:缺少的部位意味着用户可以访问所有与之匹配的值,比如:user:edit 等价于user:edit:*、user 等价于 user:*:*
  4. 注意:通配符只能从字符串的结尾处省略部位,也就是说 user:edit 并不等价于 user:*:edit
授权流程
  • AuthorizingRealm
  1. 授权需要继承 AuthenticatingRealm 类,并实现其 doGetAuthenticationInfo 方法
  2. AuthorizingRealm 类继承自 AuthenticatingRealm 类,但没有实现 AuthenticatingRealm 中的 doGetAuthenticationInfo 方法,所以认真和授权只需要继承 AuthorizingRealm 就可以了,同时实现它的两个抽象方法 doGetAuthorizationInfo 和 doGetAuthenticationInfo
  • 流程如下
  1. 首先调用 Subject.isPermitted/hasRole接口,其会委托给 SecurityManage,而 SecurityManage 接着会委托给 Authorizer;
  2. Authorizer 是真正的授权者,如果调用如 isPermitted("user:view"),其首先会通过 PermissionResolver 把字符串转换成相应的 Permission 实例;
  3. 在进行授权之前,其会调用相应的 Realm 获取 Subject 相应的角色/权限用户匹配传入的角色/权限;
  4. Authorizer 会判断 Realm 的角色/权限是否和传入的匹配, 如果有多个Realm,会委托给 ModularRealmAuthorizer 进行循环判断,如果匹配如 isPermitted/hasRole 会返回true,否则返回false表示授权失败;
  • ModularRealmAuthorizer :多Realm匹配流程
  1. 首先检查相应的 Realm是否实现了Authorizer;
  2. 如果实现了 Authorizer,那么接着调用其对应的 isPermitted/hasRole 接口进行匹配;
  3. 如果有一个Realm匹配那么将返回true,否则返回false。
    修改 ShiroRealm 类继承 AuthorizingRealm,并实现其对应的授权方法
package org.keyhua.shiro;

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

import java.util.HashSet;
import java.util.Set;

public class ShiroRealm extends AuthorizingRealm {

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("[FirstRealm]  doGetAuthenticationInfo");
        //1.把AuthenticationToken转换为UserNamePasswordToken
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        //2.从UserNamePasswordToken中获取username
        String username = upToken.getUsername();
        //3.调用dao层方法,从数据库中查询username对应的用户记录
        System.out.println("从数据库中获取username:" + username + " 所对应的用户信息。");
        //4.若用户不存在,则可以抛出 UnknownAccountException 异常
        if ("unknown".equals(username)) {
            throw  new UnknownAccountException("用户不存在");
        }

        //5.根据用户信息的情况,觉得是否需要抛出其他的异常.
        if ("monster".equals(username)) {
            throw  new LockedAccountException("用户被锁定");
        }

        //6.根据用户的情况,来构造 AuthenticationInfo 对象并返回
        //principal认证实体,可以是username,也可以是数据表对应的用户的实体类的对象
        Object principal = username;
        //credentials:密码
        Object credentials = null;
        if ("admin".equals(username)){
            credentials = "9aa75c4d70930277f59d117ce19188b0";
        } else if ("user".equals(username)) {
            credentials = "dd957e81b004227af3e0aa4bde869b25";
        }
        //realmName:当前realm对象的name.调用父类的getName()
        String realmName = getName();
        //盐值
        ByteSource credentialsSalt = ByteSource.Util.bytes(username);

        SimpleAuthenticationInfo info = null;
        //info = new SimpleAuthenticationInfo(principal, credentials, realmName);
        info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);

        return info;
    }


    public static void main(String[] args) {
        String hashAlgorithmName = "MD5";
        Object source = "123456";
        Object salt = ByteSource.Util.bytes("user");
        int hashIterations = 3;
        SimpleHash hash = new SimpleHash(hashAlgorithmName, source, salt, hashIterations);
        System.out.println(hash.toString());
    }

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //1.从PrincipalCollection 中获取登录用户的信息
        Object principal = principals.getPrimaryPrincipal();
        //2.利用登录用户的信息来获取当前用户的角色或权限(可能需要查数据库)
        Set<String> roles = new HashSet<>();
        roles.add("user");
        if ("admin".equals(principal)){
            roles.add("admin");
        }
        //3.创建SimpleAuthorizationInfo ,并设置其reles属性.
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
        //4.返回 SimpleAuthorizationInfo 对象
        return info;
    }
}

启动项目测试:当使用admin用户登录时,所有页面均可访问,当使用user用户登录时,admin.jsp页面无法访问。

权限注解
  • @RequiresAuthentication:表示当前Subject已经通过login进行了身份验证;即Subject.isAuthenticated()返回true
  • @RequiresUser:表示当前 Subject 已经身份验证或者通过记住登录的
  • @RequiresGuest:表示当前 Subject 没有身份验证或通过记住我登录过,即是游客身份
  • @RequiresRoles(value={"admin","user"},logical=Logical.AND):表示当前Subject需要角色admin和user
  • @RequiresPermissions(value={"user:a","user:b"},logical=Logical.OR):表示当前Subject需要权限user:a 或 user:b
如何从数据表中初始化资源和权限

applicationContext.xml修改内容:

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" />
        <property name="loginUrl" value="/login.jsp"/>
        <property name="successUrl" value="/list.jsp"/>
        <property name="unauthorizedUrl" value="/unauthorized.jsp" />

        <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"/>

        <!--配置哪些页面需要受保护,以及访问这些页面需要的权限
            a. anon 可以被匿名访问
            b. authc 必须认证(登录)后才可以访问的页面
            c. logout 登出b
            d. roles 角色过滤器
        -->
        <!--
        <property name="filterChainDefinitions">
            <value>
                /login.jsp = anon
                /shiro/shiroLogin = anon
                /shiro/logout = logout

                /user.jsp = roles[user]
                /admin.jsp = roles[admin]

                /** = authc
            </value>
        </property>
        -->
    </bean>

    <!-- 配置一个bean,该bean实际上是一个Map。通过实例工厂方法的方式 -->
    <bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder" factory-method="buildfilterChainDefinitionMap"/>
    <bean id="filterChainDefinitionMapBuilder" class="org.keyhua.shiro.FilterChainDefinitionMapBuilder"></bean>

FilterChainDefinitionMapBuilder:

public class FilterChainDefinitionMapBuilder {

    public LinkedHashMap<String,String> buildfilterChainDefinitionMap(){
        LinkedHashMap<String,String> map = new LinkedHashMap<>();
        //可以从数据表中初始化资源和权限
        map.put("/login.jsp", "anon");
        map.put("/shiro/shiroLogin", "anon");
        map.put("/shiro/logout", "logout");
        map.put("/**", "authc");

        return map;
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,265评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,078评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,852评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,408评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,445评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,772评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,921评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,688评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,130评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,467评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,617评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,276评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,882评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,740评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,967评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,315评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,486评论 2 348

推荐阅读更多精彩内容