百味皆苦 java后端开发攻城狮

shiro权限框架

2019-12-11
百味皆苦

参考致谢

基本概念

  • Apache Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能,对于任何一个应用程序,Shiro都可以提供全面的安全管理服务
  • 权限管理包括用户身份认证授权两个部分,简称认证授权
  • 身份认证 : 为判断用户是否为合法用户的过程,最常用的身份认证方式就是系统通过核对用户输入的用户名和口令,将其与系统中存储的该用户信息相对比,继而来判断用户身份是否正确
  • 授权 : 既访问权限,控制用户访问资源的权限. 主体(subject)进行身份认证后需要分配权限方可访问系统的资源

架构

  • Shiro可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境. Shiro可以帮助我们完成 : 认证、授权、加密、会话管理、与Web集成、缓存等
  • Authentication : 身份认证 / 登录,验证用户是不是拥有相应的身份
  • Authorization : 授权,即权限验证,验证某个已认证的用户是否拥有某个权限. 即判断用户是否能做事情,常见的如 : 验证某个用户是否拥有某个角色,或者细粒度的验证某个用户对某个资源是否具有某个权限
  • Session Manager : 会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中. 会话可以是普通JavaSE环境的,也可以是如Web环境的
  • SessionDAO : DAO大家都用过,数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的 SessionDAO,通过如JDBC写到数据库. 比如想把Session放到Memcached中,可以实现自己的Memcached SessionDAO. 另外SessionDAO中可以使用Cache进行缓存,以提高性能
  • Cryptography : 加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储
  • CacheManager : 缓存控制器,来管理如用户、角色、权限等的缓存的. 因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能
  • Web Support : Web支持,可以非常容易的集成到Web环境中
  • Caching : 缓存,比如用户登录后,其用户信息、拥有的角色 / 权限不必每次去查,这样可以提高效率
  • Concurrency : shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去
  • Testing : 提供测试支持
  • Run As : 允许一个用户假装为另一个用户(在他们允许的情况下)的身份进行访问
  • Remember Me : 记住我,这个是非常常见的功能,即一次登录后,下次不用重复登录了
  • shiro API含义:
  • Subject : 主体,代表了当前”用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等,即为一个抽象概念. 所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager. 可以把Subject认为是一个门面,SecurityManager才是实际的执行者
  • SecurityManager : 安全管理器,即所有与安全有关的操作都会与SecurityManager交互,且它管理着所有Subject. 可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器
  • Realm : 域,Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法,也需要从Realm得到用户相应的角色 / 权限进行验证用户是否能进行操作. 可以把Realm看成 DataSource,即安全数据源。注意 : Shiro不知道你的用户 / 权限存储在哪及以何种格式存储,所以我们一般在应用中都需要实现自己的Realm
  • Shiro不提供维护用户 / 权限,而是通过Realm让开发人员自己注入内部结构

  • 最简单的一个Shiro应用可以基本分为以下两个步骤 :
    • 应用代码通过Subject来进行认证和授权,而Subject又将所有的互交都委托给了SecurityManager
    • 我们需要给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断
  • Authenticator : 认证器,负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现. 其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了
  • Authrizer : 授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作,即控制着用户能访问应用中的哪些功能

登录登出

  • 在shiro中,用户需要提供principals(身份)和credentials(证明)给shiro,继而来验证用户的身份信息,最常见的princpals和 credentials组合就是用户名 / 密码
  • principals : 身份,即主体的标识属性,如用户名、邮箱等,需唯一. 一个主体可以有多个principals,但只有一个Primary principals,一般是用户名 / 手机号
  • credentials : 证明 / 凭证,即只有主体知道的安全值,如密码 / 数字证书等

案例

  • maven依赖
<dependencies>
    <!--Junit 单元测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>
    <!-- Shiro核心包 -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.3.2</version>
    </dependency>
    <!-- slf4j的接口实现 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.12</version>
    </dependency>
</dependencies>
  • shiro文件:shiro.ini(存储用户身份信息(账户=密码))相当于从数据库中查询出的账号密码
[users]
root=yubuntu0109
  • 测试
/**
 * Shiro认证测试:验证用户登录信息
 */
public class ShiroTest {

    @Test
    public void testLogin() {
        //1:加载配置文件,创建SecurityManager工厂对象
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        //2:获得securityManager实例对象
        SecurityManager securityManager = factory.getInstance();
        //3:将securityManger实例绑定到当前运行环境中,便于访问
        SecurityUtils.setSecurityManager(securityManager);
        //4:创建当前登录的主体
        Subject currentUser = SecurityUtils.getSubject();
        //5:绑定主体登录的身份/凭证,既账户及密码(相当于从前台form表单获取到的账号密码)
        UsernamePasswordToken token = new UsernamePasswordToken("root", "yubuntu0109");
        //6:主体登录
        try {
            currentUser.login(token);
            System.out.println("用户身份是否验证成功 :" + currentUser.isAuthenticated());
        } catch (UnknownAccountException e) {
            System.err.println("用户账户信息错误 !");
        } catch (IncorrectCredentialsException e) {
            System.err.println("用户密码信息错误 !");
        } catch (AuthenticationException e) {
            e.printStackTrace();
        }
        //8:注销登录
        currentUser.logout();
        System.out.println("身份身份是否注销成功 :" + !currentUser.isAuthenticated());

    }
}

源码流程

  • 1:调用subject.login(AuthenticationToken token)方法进行用户登录,其会自动委托给securityManager.login(Subject subject, AuthenticationToken token)方法进行登录
  • 2:securityManager(安全管理器)通过Authenticator(认证器)进行认证
  • 3:Authenticator的实现类ModularRealmAuthenticator通过调用realmshiro.ini配置文件中获取用户真实的信息(账户和密码),这里的Realm(域)可以看成DataSource,即安全数据源
  • 4:IniRealm(可通过加载.ini文件生成reaml对象)先根据token中的账号去shiro.ini配置文件中去匹配该账号,如果找不到则ModularRealmAuthenticator返回null,如果找到则继续匹配密码,若匹配成功则认证通过,反之不通过
  • 5:最后可以使用Subject.logout()进行退出操作

自定义realm

  • Realm : 域,Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法. 也需要从Realm得到用户相应的角色 / 权限进行验证用户是否能进行操作,可以把Realm看成 DataSource,即安全数据源. 如我们之前的ini配置方式使用的是org.apache.shiro.realm.text.IniRealm接口
//org.apache.shiro.realm.Realm接口
//返回一个唯一的Realm名字
String getName(); 
//判断此Realm是否支持此Token
boolean supports(AuthenticationToken token); 
//根据Token获取认证信息
AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;

案例

  • 依赖
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>
    <!-- Shiro核心包 -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.3.2</version>
    </dependency>
    <!-- slf4j的接口实现 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.12</version>
    </dependency>
    <!-- java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory -->
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>
</dependencies>
  • 信息文件:shiro.ini
#自定义realm
myRealm = com.xxx.MyRealm
#指定SecurityManager的realms实现
securityManager.realm = $myRealm
  • 自定义realm:MyRealm
public class MyRealm extends AuthorizingRealm {

    @Override
    public String getName() {
        return "myRealm"; //区分不同的Realm
    }

    @Override
    //授权操作
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    @Override
    //认证操作:其token存储着传入的用户登录信息(usernamePasswordToken)
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        //获取并验证用户账号信息(前端传入)
        String username = (String) token.getPrincipal();
        //与数据库中的数据进行比较
        if (!"root".equals(username)) {
            return null;
        }
        //假设数据库中用户密码信息
        String password = "yubuntu0109";

        //SimpleAuthenticationInfo(Object principal, Object credentials, String realmName)
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, getName());
        return info;
    }
}
  • 测试
/**
 * 测试自定义realm
 */
public class loginByMyRealm {

    public static void main(String[] args) {
        //1:加载配置文件,创建SecurityManager工厂对象
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:my-shiro.ini");
        //2:获得securityManager实例对象
        SecurityManager securityManager = factory.getInstance();
        //3:将securityManger实例绑定到当前运行环境中,便于访问
        SecurityUtils.setSecurityManager(securityManager);
        //4:创建当前登录的主体
        Subject currentUser = SecurityUtils.getSubject();
        //5:绑定主体登录的身份/凭证,既账户及密码
        UsernamePasswordToken token = new UsernamePasswordToken("root", "yubuntu0109");
        //6:主体登录
        try {
            currentUser.login(token);
            System.out.println("用户身份是否验证成功 :" + currentUser.isAuthenticated());
        } catch (UnknownAccountException e) {
            System.err.println("用户账户错误 !");
        } catch (IncorrectCredentialsException e) {
            System.err.println("用户密码错误 !");
        } catch (AuthenticationException e) {
            e.printStackTrace();
        }
        //8:注销登录
        currentUser.logout();
        System.out.println("身份身份是否注销成功 :" + !currentUser.isAuthenticated());

    }
}

realm密码加密

  • 散列算法一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据,常见的散列算法如MD5、SHA等.
  • 通过使用MD5加密自定义Realm,
[main]
#定义凭证匹配器
credentialsMatcher = org.apache.shiro.authc.credential.HashedCredentialsMatcher
#散列算法
credentialsMatcher.hashAlgorithmName = md5
#散列次数
credentialsMatcher.hashIterations = 10
#将凭证匹配器设置到realm
myRealm = com.xxx.EncryRealm
myRealm.credentialsMatcher = $credentialsMatcher
securityManager.realms = $myRealm
/**
*加密realm
*/
public class EncryRealm extends AuthorizingRealm {

    @Override
    public String getName() {
        return "myRealm"; //区分不同的Realm
    }

    @Override
    //授权操作
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    @Override
    //认证操作
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        //获取用户账户信息
        String username = token.getPrincipal().toString();
        if (!"root".equals(username)) {
            return null;
        }
        //MD5加密后的用户密码信息
        String password = "f3005f7acf36cec973498845460b0c33";//password + username + 10
        //指定盐值:ByteSource.Util.bytes(username)
        return new SimpleAuthenticationInfo(username, password, ByteSource.Util.bytes(username), getName());
    }
}

用户授权

  • 授权,也叫访问控制,即在应用中控制谁能访问哪些资源(如访问页面/编辑数据/页面操作等). 在授权中需了解的几个关键对象 : 主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)
  • 主体 : 主体,即访问应用的用户,在Shiro中使用Subject代表该用户. 用户只有授权后才允许访问相应的资源
  • 资源 : 在应用中用户可以访问的任何资源,比如访问JSP页面、查看/编辑某些数据、访问某个业务方法、打印文本等等..用户需要授权后方可访问
  • 权限 : 安全策略中的原子授权单位,可用权限控制用户在应用中是否能访问某个资源,如访问用户列表页面,查看/新增/修改/删除用户数据(基本为CRUD式权限控制)
  • 角色 : 角色代表了操作集合,可以理解为权限的集合,一般情况下我们会赋予用户角色而不是权限,即这样用户可以拥有一组权限,不同的角色拥有一组不同的权限
    • 隐式角色 : 即直接通过角色来验证用户有没有操作权限,即粒度是以角色为单位进行访问控制的,粒度较粗. 若进行变更可能需要多处代码的修改
    • 显示角色 : 在程序中通过权限控制谁能访问某个资源,角色聚合一组权限集合. 这样若需要哪个角色不能访问某个资源,只需要从角色代表的权限集合中移除指定的访问权限即可,无须修改多处

授权方式

  • 编程式 : 通过写if/else授权代码块完成
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole("admin")) {
    //有权限
} else {
    //无权限
}
  • 注解式 : 通过在执行的Java方法上放置相应的注解完成
@RequiresRoles("admin")
public void hello() {
    //有权限
}
  • JSP/GSP 标签 : 在JSP/GSP页面通过相应的标签完成
<shiro:hasRole name="admin">
<!— 有权限 >
</shiro:hasRole>

授权流程

  • 首先调用Subject.isPermitted/hasRole接口,其会委托给SecurityManager,而SecurityManager接着会委托给Authorizer
  • Authorizer是真正的授权者,如果我们调用如isPermitted(“user:create”),其首先会通过PermissionResolver把字符串转换成相应的Permission实例
  • 在进行授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限
  • Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,则会委托给ModularRealmAuthorizer进行循环判断,如果匹配如isPermitted/hasRole会返回true,否则返回false以表示授权失败

检查角色

  • 依赖
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>
    <!-- Shiro核心包 -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.3.2</version>
    </dependency>
    <!-- slf4j的接口实现 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.12</version>
    </dependency>
    <!-- java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory -->
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>
</dependencies>
  • 配置信息:shiro.ini
#权限表达式的定义:首先根据用户名查找角色,再根据角色查找权限,角色是权限的集合

[users]
#用户hunagyuhui的密码为loveyourself,且具有student和programmer两个角色
huangyuhui = loveyourself,student,programmer

[roles]
#角色student对资源'user'拥有create,update权限
student = user:create,user:update
#角色programmer对资源'user'read,delete权限
programmer = user:read,user:delete
  • 测试
public class RolesTest {

    public static void main(String[] args) {
        //1:加载配置文件,创建SecurityManager工厂对象
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:per-shiro.ini");
        //2:获得securityManager实例对象
        SecurityManager securityManager = factory.getInstance();
        //3:将securityManger实例绑定到当前运行环境中,便于访问
        SecurityUtils.setSecurityManager(securityManager);
        //4:创建当前登录的主体
        Subject currentUser = SecurityUtils.getSubject();
        //5:绑定主体登录的身份/凭证,既账户及密码
        UsernamePasswordToken token = new UsernamePasswordToken("huangyuhui", "loveyourself");
        //6:主体登录
        try {
            currentUser.login(token);
            System.out.println("用户身份是否验证成功 :" + currentUser.isAuthenticated());
            //7:进行用户角色判断
            System.out.println("判断当前登录用户是否拥有'student'角色 : " + currentUser.hasRole("student"));
            System.out.println("判断当前登录用户是否同时拥有'student'与'programmer'角色 : " + currentUser.hasAllRoles(List.of("student", "programmer")));
            System.out.println("判断当前登录用户是否拥有'student','programmer','singer'角色 : " + Arrays.toString(currentUser.hasRoles(List.of("student", "programmer", "singer"))));
            //判断当前用户是否拥有某个角色,若拥有该角色则不做任何操作(无返回值),反之则抛出:UnauthorizedException
            currentUser.checkRole("singer");

        } catch (UnknownAccountException e) {
            System.err.println("用户账户错误 !");
        } catch (IncorrectCredentialsException e) {
            System.err.println("用户密码错误 !");
        } catch (UnauthorizedException e) {
            System.err.println("用户并不拥有'singer'角色 !");
        } catch (AuthenticationException e) {
            e.printStackTrace();
        }
        //8:注销登录
        currentUser.logout();
        System.out.println("用户信息是否注销成功 :" + !currentUser.isAuthenticated());

    }
}
/*
// ······
用户身份是否验证成功 :true
// ······
判断当前登录用户是否拥有'student'角色 : true
// ······
判断当前登录用户是否同时拥有'student'与'programmer'角色 : true
// ······
判断当前登录用户是否拥有'student','programmer','singer'角色 : [true, true, false]
// ······
用户并不拥有'singer'角色 !
// ······
用户信息是否注销成功 :true
*/

检查权限

  • 依赖和配置同检查角色

  • 测试

public class PermissionsTest {

    public static void main(String[] args) {
        //1:加载配置文件,创建SecurityManager工厂对象
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:per-shiro.ini");
        //2:获得securityManager实例对象
        SecurityManager securityManager = factory.getInstance();
        //3:将securityManger实例绑定到当前运行环境中,便于访问
        SecurityUtils.setSecurityManager(securityManager);
        //4:创建当前登录的主体
        Subject currentUser = SecurityUtils.getSubject();
        //5:绑定主体登录的身份/凭证,既账户及密码
        UsernamePasswordToken token = new UsernamePasswordToken("huangyuhui", "loveyourself");
        //6:主体登录
        try {
            currentUser.login(token);
            System.out.println("用户身份是否验证成功 :" + currentUser.isAuthenticated());
            //7:进行用户权限判断
            System.out.println("当前用户是否拥有对资源'user'的'create'与'read'权限 : " + Arrays.toString(currentUser.isPermitted("user:create", "user:read")));
            System.out.println("当前用户是否拥有对资源'user'的'update'与'delete'权限 : " + Arrays.toString(currentUser.isPermitted("user:update", "user:delete")));
            System.out.println("当前用户是否拥有对资源'user'的'import'与'export'权限 : " + Arrays.toString(currentUser.isPermitted("user:import", "user:export")));
            //判断当前用户是否拥有某个权限,若有则不做任何操作(无返回值),反之抛出:UnauthorizedException
            currentUser.checkPermission("user:getUserList");

        } catch (UnknownAccountException e) {
            System.err.println("用户账户错误 !");
        } catch (IncorrectCredentialsException e) {
            System.err.println("用户密码错误 !");
        } catch (UnauthorizedException e) {
            System.err.println("当前用户并未拥有对资源'user'的'getUserList'权限 !");
        } catch (AuthenticationException e) {
            e.printStackTrace();
        }
        //8:注销登录
        currentUser.logout();
        System.out.println("用户信息是否注销成功 :" + !currentUser.isAuthenticated());

    }
}
/*
// ······
用户身份是否验证成功 :true
// ······
当前用户是否拥有对资源'user'的'create'与'read'权限 : [true, true]
// ······
当前用户是否拥有对资源'user'的'update'与'delete'权限 : [true, true]
// ······
当前用户是否拥有对资源'user'的'import'与'export'权限 : [false, false]
// ······
当前用户并未拥有对资源'user'的'getUserList'权限 !
// ······
用户信息是否注销成功 :true
*/
  • 自定义Realm检查用户拥有的权限
#指定自定义realm
myRealm = com.xxx.PermissRealm
#指定SecurityManager的realms实现
securityManager.realm = $myRealm
public class PermissRealm extends AuthorizingRealm {

    @Override
    public String getName() {
        return "permissRealm";
    }

    @Override
    //授权:其principals存储着用户认证的凭证信息
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        //获取当前登录的用户名信息,既用户凭证
        //Object username = principals.getPrimaryPrincipal();

        //模拟查询数据库操作:查询用户所拥有的的角色及权限信息
        List<String> roles = new ArrayList<>();
        List<String> permiss = new ArrayList<>();
        //假设该用户拥有'student'与'programmer'角色
        roles.add("student");
        roles.add("programmer");
        //假设该用户拥有对资源'user'的'read'与'update'权限
        permiss.add("user:create");
        permiss.add("user:update");

        //封装用户的角色与权限信息并返回
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addRoles(roles);
        info.addStringPermissions(permiss);
        return info;
    }

    @Override
    //认证:其token存储着传入的用户身份信息(usernamePasswordToken)
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获取用户的账户信息并验证
        String username = (String) token.getPrincipal();
        if (!"github".equals(username)) {
            return null;
        }
        //模拟用户密码信息
        String password = "yubuntu0109";

        //SimpleAuthenticationInfo(Object principal, Object credentials, String realmName)
        return new SimpleAuthenticationInfo(username, password, getName());
    }
}
public class PermissTest {

    public static void main(String[] args) {
        //1:加载配置文件,创建SecurityManager工厂对象
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:my-per-shiro.ini");
        //2:获得securityManager实例对象
        SecurityManager securityManager = factory.getInstance();
        //3:将securityManger实例绑定到当前运行环境中,便于访问
        SecurityUtils.setSecurityManager(securityManager);
        //4:创建当前登录的主体
        Subject currentUser = SecurityUtils.getSubject();
        //5:绑定主体登录的身份/凭证,既账户及密码
        UsernamePasswordToken token = new UsernamePasswordToken("github", "yubuntu0109");
        //6:主体登录
        try {
            currentUser.login(token);
            System.out.println("用户身份是否验证成功 :" + currentUser.isAuthenticated());
            //7:验证用户角色
            System.out.println("判断当前登录用户是否拥有'student','programmer','singer'角色 : "
                    + Arrays.toString(currentUser.hasRoles(List.of("student", "programmer", "singer"))));
            //8:验证用户权限
            System.out.println("当前用户是否拥有对'user'资源的'create','update','read','delete'权限 : "
                    + Arrays.toString(currentUser.isPermitted("user:create", "user:update", "user:read", "user:delete")));
        } catch (UnknownAccountException e) {
            System.err.println("用户账户错误 !");
        } catch (IncorrectCredentialsException e) {
            System.err.println("用户密码错误 !");
        } catch (AuthenticationException e) {
            e.printStackTrace();
        }
        //8:注销登录
        currentUser.logout();
        System.out.println("用户信息是否注销成功 :" + !currentUser.isAuthenticated());

    }
}
/*
// ······
用户身份是否验证成功 :true
// ······
判断当前登录用户是否拥有'student','programmer','singer'角色 : [true, true, false]
// ······
当前用户是否拥有对'user'资源的'create','update','read','delete'权限 : [true, true, false, false]
// ······
用户信息是否注销成功 :true
*/

权限注解

  • 一般把注解加在controller层的方法上进行权限控制

  • @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

拦截器

  • Shiro内置了很多默认的拦截器,比如身份验证、授权等相关的.更多默认拦截器可以参考 org.apache.shiro.web.filter.mgt.DefaultFilter中的枚举拦截器
过滤器简称 对应的java类
anon org.apache.shiro.web.filter.authc.AnonymousFilter
authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
user org.apache.shiro.web.filter.authc.UserFilter
logout org.apache.shiro.web.filter.authc.LogoutFilter
port org.apache.shiro.web.filter.authz.PortFilter
rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
ssl org.apache.shiro.web.filter.authz.SslFilter
  • anon : 匿名拦截器,即不需要登录即可访问. 一般用于静态资源过滤,示例”/static/**=anon”

  • authc : 表示需要认证(登录)才能使用,示例”/**=authc”,主要属性有 :

    • usernameParam : 表单提交的用户名参数名(username)
    • passwordParam : 表单提交的密码参数名(password)
    • rememberMeParam : 表单提交的密码参数名(rememberMe)
    • loginUrl : 登录请求地址(/login.jsp)
    • successUrl : 登录成功后的默认重定向地址
    • failureKeyAttribute : 登录失败后错误信息存储key(shiroLoginFailure)
  • authcBasic : Basic HTTP身份验证拦截器,主要属性 : applicationName:弹出登录框显示的信息(application)

  • roles : 角色授权拦截器,验证用户是否拥有资源角色. 示例”/admin/**=roles[admin]”

  • perms : 权限授权拦截器,验证用户是否拥有资源权限. 示例”/user/create=perms[“user:create”]”

  • user : 用户拦截器,用户已经身份验证/记住我的登录. 示例”/index=user”

  • logout : 退出拦截器,主要属性 : redirectUrl:退出成功后重定向的地址(/). 示例”/logout=logout”

  • port : 端口拦截器,主要属性 : port(80):可以通过的端口. 示例”/test= port[80]”,如果用户访问该页面是非80,将自动将请求端口改为80并重定向到该80端口,其他路径/参数等相同

  • rest : rest风格拦截器,自动根据请求方法构建权限字符串。

    (GET=read,POST=create,PUT=update,DELETE=delete,HEAD=read,TRACE=read,OPTIONS=read, MKCOL=create)构建权限字符串. 示例”/users=rest[user]”,会自动拼出”user:read,user:create,user:update,user:delete”权限字符串进行权限匹配(所有都得匹配:isPermittedAll)

  • ssl : SSL拦截器,只有请求协议是https才能通过. 否则自动跳转会https端口(443). 其他和port拦截器一样

  • 注 : anon,authcBasic,auchc,user是认证过滤器. perms,roles,ssl,rest,port是授权过滤器

整合WEB

shiro整合WEB

整合SSM

整合SSM

  • 数据库表设计
CREATE TABLE `tb_user` (
  `uid` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户id',
  `username` varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户名',
  `password` varchar(45) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密码',
  `rid` int(11) DEFAULT NULL COMMENT '角色id',
  PRIMARY KEY (`uid`),
  KEY `user_rid_tb_role_key_idx` (`rid`),
  CONSTRAINT `user_rid_role_key` FOREIGN KEY (`rid`) REFERENCES `tb_role` (`rid`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `tb_role` (
  `rid` int(11) NOT NULL COMMENT '角色id',
  `role_name` varchar(45) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '角色名称',
  PRIMARY KEY (`rid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `tb_permission` (
  `pid` int(11) NOT NULL COMMENT '权限id',
  `permission_name` varchar(45) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '权限名称',
  `rid` int(11) DEFAULT NULL COMMENT '角色id',
  PRIMARY KEY (`pid`),
  KEY `permission_rid_role_key_idx` (`rid`),
  CONSTRAINT `permission_rid_role_key` FOREIGN KEY (`rid`) REFERENCES `tb_role` (`rid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
  • 1:添加依赖
  • 2:配置web.xml
  • 3:配置applicationContext-shiro.xml
  • 4:自定义Realm实现授权认证和登录认证

整合springboot

  • Spring集成Shiro一般通过的 xml 配置,比较繁琐,而Spring Boot集成Shiro相对简单,只需要配置两个类 : ShiroConfiguration类及继承AuthorizingRealm的Realm类
  • ShiroConfig : 顾名思义就是对Shiro的一些配置,相对于Spring中的xml配置. 包括 : 包括过滤器(ShiroFilter)、安全事务管理器(SecurityManager)、密码凭证匹配器(CredentialsMatcher)、缓冲管理器(EhCacheManager)、aop注解支持(authorizationAttributeSourceAdvisor)、等等
  • CustomRealm : 自定义的CustomRealm继承自AuthorizingRealm,重写了父类中的doGetAuthorizationInfo(授权认证)、doGetAuthenticationInfo(登陆认证)这两个方法
@Configuration
public class ShiroConfig {

    /**
     * @description: 配置过滤器
     * @param: securityManager
     * @date: 2019-08-05 7:59 AM
     * @return: org.apache.shiro.spring.web.ShiroFilterFactoryBean
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //设置自定义过滤器
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap());
        //设置用户登录页,默认: http://localhost:8080/login.jsp
        shiroFilterFactoryBean.setLoginUrl("/loginView");
        //设置用户未授权操作提示页
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorizedView");
        return shiroFilterFactoryBean;
    }

    /**
     * @description: 初始化自定义Realm
     * @param: credentialsMatcher
     * @date: 2019-08-05 7:50 AM
     * @return: pers.huangyuhui.ss.shiro.UserRealm
     */
    @Bean
    public UserRealm userRealm(HashedCredentialsMatcher hashedCredentialsMatcher) {
        UserRealm userRealm = new UserRealm();
        //设置凭证匹配器
        userRealm.setCredentialsMatcher(hashedCredentialsMatcher);
        return userRealm;
    }

    /**
     * @description: 安全事务管理器
     * @param: credentialsMatcher
     * @date: 2019-08-05 7:53 AM
     * @return: org.apache.shiro.web.mgt.DefaultWebSecurityManager
     */
    @Bean
    public SecurityManager securityManager(UserRealm userRealm, EhCacheManager ehCacheManager) {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        //关联自定义realm
        defaultWebSecurityManager.setRealm(userRealm);
        //关联缓存管理
        defaultWebSecurityManager.setCacheManager(ehCacheManager);
        return defaultWebSecurityManager;
    }

    /**
     * @description: 哈希密码匹配器:比较用户登录时输入的密码,跟数据库密码配合盐值salt解密后是否一致
     * @date: 2019-08-05 9:01 PM
     * @return: org.apache.shiro.authc.credential.HashedCredentialsMatcher
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5"); //散列算法
        hashedCredentialsMatcher.setHashIterations(3); //散列的次数
        hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true); //默认是true:Hex编码.false:Base64编码
        return hashedCredentialsMatcher;
    }

    /**
     * @description: 设置缓存管理, 缓存用户及其权限信息
     * @date: 2019-08-07 7:51 AM
     * @return: org.apache.shiro.cache.ehcache.EhCacheManager
     */
    @Bean
    public EhCacheManager ehCacheManager() {
        //注意:myEhcache对应ehcache-shiro.xml中的'<ehcache name="myEhcache">'
        CacheManager cacheManager = CacheManager.getCacheManager("myEhcache");
        if (cacheManager == null) {
            cacheManager = CacheManager.create();
        }
        EhCacheManager ehCacheManager = new EhCacheManager();
        ehCacheManager.setCacheManager(cacheManager);
        return ehCacheManager;
    }


    /**
     * @description: 设置资源的权限控制
     * @date: 2019-08-05 8:31 AM
     * @return: java.util.Map
     */
    private Map<String, String> filterChainDefinitionMap() {
        Map<String, String> filterMap = new LinkedHashMap<>();
        //需身份认证
        filterMap.put("/stuListView", "authc");
        filterMap.put("/teaListView", "authc");
        //无需身份认证:防止验证用户登录信息操作被'filterMap.put("/**", "authc")'拦截
        filterMap.put("/login", "anon");
        //注销过滤器:其具体的注销逻辑代码Shiro已经替我们实现了哟
        filterMap.put("/logout", "logout");
        //角色过滤:需要用户拥有'admin'角色
        filterMap.put("/teaListView", "roles[admin]");
        //权限过滤:除身份认证外,还需要用户拥有对stuListView资源的view权限
        filterMap.put("/stuListView", "perms[stuListView:view]");
        filterMap.put("/teaListView", "perms[teaListView:view]");
        //拦截需要登录(用户认证)方可访问的资源(一般将/**放在最下边,不然会导致所有url都被拦截哟)
        filterMap.put("/**", "authc");
        return filterMap;
    }


    /**
     * @description: 配置ShiroDialect, 用于thymeleaf和shiro标签配合使用
     * @date: 2019-08-05 6:37 PM
     * @return: at.pollux.thymeleaf.shiro.dialect.ShiroDialect
     */
    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }

}
  • ehcache-shiro.xml : EhCache缓存框架的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false"
         name="myEhcache">
    <diskStore path="java.io.tmpdir"/>
    <!-- 授权信息缓存 -->
    <cache name="authorizationCache"
           maxEntriesLocalHeap="2000"
           timeToIdleSeconds="1800"
           timeToLiveSeconds="1800"
           overflowToDisk="false"
           statistics="true">
    </cache>
    <!-- 身份信息缓存 -->
    <cache name="authenticationCache"
           maxEntriesLocalHeap="2000"
           timeToIdleSeconds="1800"
           timeToLiveSeconds="1800"
           overflowToDisk="false"
           statistics="true">
    </cache>
    <!-- Session缓存 -->
    <cache name="activeSessionCache"
           maxEntriesLocalHeap="2000"
           timeToIdleSeconds="1800"
           timeToLiveSeconds="1800"
           overflowToDisk="false"
           statistics="true">
    </cache>
    <!-- 缓存半小时 -->
    <cache name="halfHour"
           maxElementsInMemory="10000"
           maxElementsOnDisk="100000"
           timeToIdleSeconds="1800"
           timeToLiveSeconds="1800"
           overflowToDisk="false"
           diskPersistent="false"/>
    <!-- 缓存一小时 -->
    <cache name="hour"
           maxElementsInMemory="10000"
           maxElementsOnDisk="100000"
           timeToIdleSeconds="3600"
           timeToLiveSeconds="3600"
           overflowToDisk="false"
           diskPersistent="false"/>
    <!-- 缓存一天 -->
    <cache name="oneDay"
           maxElementsInMemory="10000"
           maxElementsOnDisk="100000"
           timeToIdleSeconds="86400"
           timeToLiveSeconds="86400"
           overflowToDisk="false"
           diskPersistent="false"/>

    <!--
         name: 缓存名称。
         maxElementsInMemory: 缓存最大个数
         eternal: 对象是否永久有效,一但设置了,timeout将不起作用
         timeToIdleSeconds: 设置对象在失效前的允许闲置时间(单位:秒). 仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大
         timeToLiveSeconds: 设置对象在失效前允许存活时间(单位:秒). 最大时间介于创建时间和失效时间之间. 仅当eternal=false对象不是永久有效时使用,默认是0,也就是对象存活时间无穷大
         overflowToDisk: 当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中
         diskSpoolBufferSizeMB: 这个参数设置DiskStore(磁盘缓存)的缓存区大小,默认是30MB. 每个Cache都应该有自己的一个缓冲区
         maxElementsOnDisk: 硬盘最大缓存个数
         diskPersistent: 是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
         diskExpiryThreadIntervalSeconds: 磁盘失效线程运行时间间隔,默认是120秒
         memoryStoreEvictionPolicy: 当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存. 默认策略是LRU(最近最少使用). 你可以设置为FIFO(先进先出)或是LFU(较少使用)
         clearOnFlush: 内存数量最大时是否清除
     -->
    <defaultCache name="defaultCache"
                  maxElementsInMemory="10000"
                  timeToIdleSeconds="600"
                  timeToLiveSeconds="600"
                  overflowToDisk="false"
                  maxElementsOnDisk="100000"
                  diskPersistent="false"
                  diskExpiryThreadIntervalSeconds="120"
                  memoryStoreEvictionPolicy="LRU">
    </defaultCache>
</ehcache>
public class UserRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    /**
     * @description: 授权认证:提供用户信息,返回权限信息
     * @param: principalCollection
     * @date: 2019-08-06 6:02 PM
     * @return: org.apache.shiro.authz.AuthorizationInfo
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.err.println("------------> 授权认证 ------------>");
        //获取当前登录的用户信息
        Subject currentUser = SecurityUtils.getSubject();
        User u = (User) currentUser.getPrincipal();
        //从数据库中获取用户所拥有的角色及权限信息
        User user = userService.findByName(u.getUsername());
        if (user != null) {
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            //用于存储用户的角色及权限信息
            Collection<String> rolesCollection = new HashSet<>();
            Collection<String> permissionsCollection = new HashSet<>();
            Set<Role> roles = user.getRoles(); //获取用户Role的Set集合
            //通过遍历用户所拥有的角色,来获取其对应的权限信息
            for (Role role : roles) {
                rolesCollection.add(role.getName()); //将每一个role的name封装到集合中
                Set<Permission> permissionSet = role.getPermissions(); //获取每一个role所对应的permission的set集合
                //遍历用户所拥有的权限信息
                for (Permission permission : permissionSet) {
                    permissionsCollection.add(permission.getName()); //将每一个permission的name封装到集合中
                }
                info.addStringPermissions(permissionsCollection); //为用户授权
            }
            info.addRoles(rolesCollection); //为用户授予角色
            System.out.println("[roles]------------>" + rolesCollection.toString());
            System.out.println("[permissions]------------>" + permissionsCollection.toString());
            return info;
        }
        return null;
    }

    /**
     * @description: 登录认证:提供帐户信息,返回认证信息
     * @param: authenticationToken
     * @date: 2019-08-06 6:12 PM
     * @return: org.apache.shiro.authc.AuthenticationInfo
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.err.println("------------> 开始认证 ------------>");
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        //从数据库中获取用户信息
        User user = userService.findByName(token.getUsername());
        //验证账户信息
        if (user == null) {
            return null; //it's will be throw a UnknownAccountException
        }
        //验证密码信息
        return new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getUsername()), this.getName());
    }
}
  • 加密逻辑
public class SecurityUtils {

    public static void main(String[] args) {
        //admin-pwd [source:demo+salt:admin+hashIterations:3] :  257e3b15d67a9127d230175e43118e40
        //tea-pwd [source:demo+salt:tea+hashIterations:3] : f97ccdcf125073d5f19bd3de0b67eb40
        //stu-pwd [source:demo+salt:stu+hashIterations:3] : e1a1c9a1340d179077086b5dbee621b4
        String md5Pwd = new SimpleHash("md5", "demo", ByteSource.Util.bytes("stu"), 3).toHex();
        System.out.println(md5Pwd);
    }
}

整合jsp

  • 引入shiro标签
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>

认证通过

  • 表示认证已通过,但不包括remember me登录的
  • 说明 : 只有已通过用户认证,但不是通过记住我(remember me)浏览才会看到标签内的内容
<shiro:authenticated>
    <label>用户身份验证已通过</label>
</shiro:authenticated>

认证不通过

  • 表示没有通过验证
  • 说明 : 只有没有通过验证的才可以看到标签内的内容,包括通过记住我(remember me)登录的
<shiro:notAuthenticated>
    <label>用户身份验证没有通过</label>
</shiro:notAuthenticated>

游客身份

  • 表示是游客身份,没有登录
  • 说明 : 只有是没有登录过,以游客的身份浏览才会看到标签内的内容
<shiro:guest>
    <label>您当前是游客,</label><a href="/login.jsp" >请登录</a>
</shiro:guest>

已登录

  • 表示已登录
  • 说明 : 只有已经登录(包含通过记住我(remember me)登录的)的用户才可以看到标签内的内容. 一般和标签shiro : principal一起用,来做显示用户的名称
<shiro:user>
    <label>欢迎[<shiro:principal/>],</label><a href="/logout.jsp">退出</a>
</shiro:user>

有任一角色

  • 表示拥有这些角色中其中一个
  • 说明 : 只有成功登录后,且具有admin或者user角色的用户才会看到标签内的内容. name属性中可以填写多个角色名称,以逗号( , )分隔
<shiro:hasAnyRoles name="admin,user">
    <label>这是拥有admin或者是user角色的用户</label>
</shiro:hasAnyRoles>

有某一角色

  • 表示拥有某一角色
  • 说明 : 只有成功登录后,且具有admin角色的用户才可以看到标签内的内容,name属性中只能填写一个角色的名称
<shiro:hasRole name="admin">
    <label>这个用户拥有的角色是admin</label>
</shiro:hasRole>

无某一角色

  • 表示不拥有某一角色
  • 说明 : 只有成功登录后,且不具有admin:delete权限的用户才可以看到标签内的内容,name属性中只能填写一个权限的名称
<shiro:lacksRole name="admin">
    <label>这个用户不拥有admin的角色</label>
</shiro:lacksRole>

有某一权限

  • 表示拥有某一权限
  • 说明 : 只有成功登录后,且具有admin:add权限的用户才可以看到标签内的内容,name属性中只能填写一个权限的名称
<shiro:hasPermission name="admin:add">
    <label>这个用户拥有admin:add的权限</label>
</shiro:hasPermission>

无某一权限

  • 表示不拥有某一权限
  • 说明 : 只有成功登录后,且不具有admin:delete权限的用户才可以看到标签内的内容,name属性中只能填写一个权限的名称
<shiro:lacksPermission name="admin:delete">
    <label>这个用户不拥有admin:delete的权限</label>
</shiro:lacksPermission>

principal

  • 表示用户的身份 : 取值取的是你登录的时候,在Realm实现类中的SimpleAuthenticationInfo(Object principal, Object hashedCredentials, ByteSource credentialsSalt, String realmName)放的第一个参数
return new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getUsername()), this.getName());
  • 如果第一个放的是username或者是一个值,那么就可以直接用
<!--取到username-->
<shiro: principal/>
  • 如果第一个参数放的是对象,比如放User对象. 那么如果要取其中某一个值,可以通过property属性来指定
<!--需要指定property-->
<shiro:principal property="username"/>

标签嵌套

  • shiro的jsp标签可以嵌套使用,可以根据业务的具体场景进行使用. 例如一个按钮需要排除不是admin或user角色的用户才可以显示,则可以像如下这样实现 :
<shiro:lacksRole name="admin">
    <shiro:lacksRole name="user"> 
        <label>这个用户不拥有admin或user的角色</label>
    </shiro:lacksRole>
</shiro:lacksRole>

参考感谢

整合thymeleaf

  • 添加依赖
<!-- Thymeleaf -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Thymeleaf对shiro的扩展 -->
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>
  • 添加配置
/**
     * @description: 配置ShiroDialect, 用于thymeleaf和shiro标签配合使用
     * @date: 2019-08-05 6:37 PM
     * @return: at.pollux.thymeleaf.shiro.dialect.ShiroDialect
     */
    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
}
  • 命名空间
<html lang="zh_CN" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
  • 标签
<!-- guest标签:用户没有身份验证时显示相应信息,即游客访问信息 -->
<shiro:guest></shiro:guest>

<!-- user标签:用户已经身份验证/记住我登录后显示相应的信息 -->
<shiro:user></shiro:user>

<!-- authenticated标签:用户已经身份验证通过,即Subject.login登录成功,不是记住我登录的 -->
<shiro:authenticated></shiro:authenticated>

<!-- notAuthenticated标签:用户已经身份验证通过,即没有调用Subject.login进行登录,包括记住我,自动登录的也属于未进行身份验证 -->
<shiro:notAuthenticated></shiro:notAuthenticated>

<!-- principal标签:相当于((User)Subject.getPrincipals()).getUsername()  -->
<shiro: principal/>
<shiro:principal property="username"/>
  
<!-- lacksPermission标签:如果当前Subject没有权限将显示body体内容 -->
<shiro:lacksPermission name="org:create"></shiro:lacksPermission>

<!-- hasRole标签:如果当前Subject有角色将显示body体内容 -->
<shiro:hasRole name="admin"></shiro:hasRole>

<!-- hasAnyRoles标签:如果当前Subject有任意一个角色(或的关系)将显示body体内容 -->
<shiro:hasAnyRoles name="admin,user"></shiro:hasAnyRoles>

<!-- lacksRole标签:如果当前Subject没有角色将显示body体内容 -->
<shiro:lacksRole name="abc"></shiro:lacksRole> 

<!-- hasPermission标签:如果当前Subject有权限将显示body体内容 -->
<shiro:hasPermission name="user:create"></shiro:hasPermission>

参考感谢

缓存

  • 当权限信息存放在数据库中时,对于每次前端的访问请求都需要进行一次数据库查询. 特别是在大量使用shiro的jsp标签的场景下,对应前端的一个页面访问请求会同时出现很多的权限查询操作,这对于权限信息变化不是很频繁的场景,每次前端页面访问都进行大量的权限数据库查询是非常不经济的! 因此非常有必要对权限数据使用缓存方案
  • 关于shiro权限数据的缓存方式,可以分为以下两类 :
    • 将权限数据缓存到集中式存储中间件中,比如redis或者memcached
    • 将权限数据缓存到本地
  • Shiro只提供了一个可以支持具体缓存实现(如 : Hazelcast, Ehcache, OSCache, Terracotta, Coherence, GigaSpaces, JBossCache 等)的抽象API接口,这样就允许Shiro用户根据自己的需求灵活地选择具体的CacheManager,当然,其实Shiro也自带了一个本地内存CacheManager(org.apache.shiro.cache.MemoryConstrainedCacheManager)
  • 从以上分析我们知道Shiro支持在2个地方定义缓存管理器,既可以在SecurityManager中定义,也可以在Realm中定义,任选其一即可. 通常我们都会自定义Realm实现,例如将权限数据存放在数据库中,那么在Realm实现中定义缓存管理器再合适不过了

上一篇 Oracle-11g

Comments

Content