美文网首页
五、授权

五、授权

作者: 好像身体被掏空 | 来源:发表于2018-06-04 14:26 被阅读0次

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

    • 主体:访问应用的用户,在 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;
        }
    }
    

    相关文章

      网友评论

          本文标题:五、授权

          本文链接:https://www.haomeiwen.com/subject/vuqdjftx.html