五、授权

2018-06-04  本文已影响0人  好像身体被掏空

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

授权方式

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
  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
授权流程
  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表示授权失败;
  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页面无法访问。

权限注解
如何从数据表中初始化资源和权限

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;
    }
}
上一篇下一篇

猜你喜欢

热点阅读