当前位置:首页 » 《关注互联网》 » 正文

⭐图例结合超硬核讲解shiro⭐_白大锅的博客

13 人参与  2021年09月12日 15:03  分类 : 《关注互联网》  评论

点击全文阅读


shiro目录

  • 前言
  • 一、shiro简介
  • 二、shiro简单使用
  • 三、身份验证
    • 3.1.环境准备
    • 3.2.登录/退出
  • 四、shiro授权
    • 4.1.shiro授权的三种方式:
    • 4.2.实现
    • 4.3.自定义Realm进行授权
  • 五、与SpringBoot集成案例
  • 六、shiro各个拦截器对比及使用配置
    • 6.1.各种拦截器的特点
    • 6.2.INI文件配置拦截器
    • 6.3.代码形式配置拦截器
    • 6.4.简单使用
  • 七、集成Shiro之Shiro标签(.jsp和.ftl)
    • 7.1.JSP的Shiro标签
    • 7.2.Freemark的Shiro标签
  • 八、会话管理
    • 8.1.会话相关API
    • 8.2.会话监听器(SessionListener接口)
    • 8.3.SessionDao(会话持久化、提供CRUD操作)
    • 8.4.会话验证
  • 九、缓存
    • 9.1. 集中式缓存
    • 9.2. 本地缓存
  • 十、shiro修仙进阶


前言

目前,使用 Apache Shiro 的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。对于它俩到底哪个好,这个不必纠结,能更简单的解决项目问题就好了


一、shiro简介

Apache Shiro 是 Java 的一个安全框架。可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在JavaEE 环境。Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与 Web 集成、缓存等。这不就是我们想要的嘛,而且 Shiro 的 API 也是非常简单;其基本功能点如下图所示:
在这里插入图片描述

Authentication :身份认证/登录,验证用户是不是拥有相应的身份;
Authorization: :授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用
户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用
户对某个资源是否具有某个权限;
Session Manager: :会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信
息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;
Cryptography :加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
Web Support :Web 支持,可以非常容易的集成到 Web 环境;
Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以
提高效率;
Concurrency :shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能
把权限自动传播过去;
Testing :提供测试支持;
Run As :允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
Remember Me: :记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录
了。

注意:Shiro 不会去维护用户、维护权限;这 些需要我们 自己去 设计/ 提供 ; 然后通过
相应的 接口注入给 给 Shiro 即可。

shiro外部及内部结构:
在这里插入图片描述
在这里插入图片描述
具体参数配置什么意思可查看此文:Shiro完整教程, 附带各种配置

二、shiro简单使用

1.Idea穿件一个Maven项目
引入pom依赖:

 <dependencies>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.2.3</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.6.1</version>
        </dependency>
    </dependencies>

2.任意创建一个包,在里面创建一个Tutorial类

package me.aihe;import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

public class Tutorial {
    private static final transient Logger log = LoggerFactory.getLogger(Tutorial.class);   

    public static void main(String[] args) {    
        log.info("My First Apache Shiro Application");       
        System.exit(0);    
    }
}

3.使用Shiro
Shiro提供了一个通用的方案通过 INI 进行配置 ,当然也可以通过XML,YMAL,JSON等进行配置。
在resource目录下面,创建一个shiro.ini的文件。内容如下:

# -----------------------------------------------------------------------------
# Users and their (optional) assigned roles
# username = password, role1, role2, ..., roleN
# -----------------------------------------------------------------------------
[users]
root = secret, admin
guest = guest, guest
presidentskroob = 12345, president
darkhelmet = ludicrousspeed, darklord, schwartz
aihe = aihe, goodguy, client

# -----------------------------------------------------------------------------
# Roles with assigned permissions
# roleName = perm1, perm2, ..., permN
# -----------------------------------------------------------------------------
[roles]
admin = *
client = look:*
goodguy = winnebago:drive:eagle5

4.引用Shiro.ini配置进行测试
现在改变我们的Tutorial类文件,内容如下

package me.aihe;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Created by aihe on 2017/6/14.
 */
public class Tutorial {

    private static final transient Logger log = LoggerFactory.getLogger(Tutorial.class);
    public static void main(String[] args) {
        log.info("My First Apache Shiro Application");

        //1. 这里的SecurityManager是org.apache.shiro.mgt.SecurityManager,而不是java.lang.SecurityManager
        // 加载配置文件
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");

        //2.解析配置文件,并且返回一些SecurityManger实例
        SecurityManager securityManager = factory.getInstance();

        //3.设置SecurityManager到静态内存区,单例模式
        SecurityUtils.setSecurityManager(securityManager);


        // 安全操作
        Subject currentUser = SecurityUtils.getSubject();

        // 在应用的当前会话中设置属性
        Session session =  currentUser.getSession();
        session.setAttribute("key","value");

        //当前我们的用户是匿名的用户,我们尝试进行登录,
        if (!currentUser.isAuthenticated()){
            UsernamePasswordToken token = new UsernamePasswordToken("aihe", "aihe");

            //this is all you have to do to support 'remember me' (no config - built in!):
            token.setRememberMe(true);

            //尝试进行登录用户,如果登录失败了,我们进行一些处理

            try{
                currentUser.login(token);

                //当我们获登录用户之后
                log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");


                // 查看用户是否有指定的角色
                if ( currentUser.hasRole( "client" ) ) {
                    log.info("Look is in your role" );
                } else {
                    log.info( "....." );
                }

                // 查看用户是否有某个权限
                if ( currentUser.isPermitted( "look:desk" ) ) {
                    log.info("You can look.  Use it wisely.");
                } else {
                    log.info("Sorry, you can't look.");
                }

                if ( currentUser.isPermitted( "winnebago:drive:eagle5" ) ) {
                    log.info("You are permitted to 'drive' the 'winnebago' with license plate (id) 'eagle5'.  " +
                            "Here are the keys - have fun!");
                } else {
                    log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
                }

                //登出

                currentUser.logout();

            }
            catch ( UnknownAccountException uae ) {
                //账户不存在的操作
            } catch ( IncorrectCredentialsException ice ) {
                //密码不正确
            } catch ( LockedAccountException lae ) {
                //用户被锁定了
            } catch ( AuthenticationException ae ) {
                //无法判断的情形
            }

        }


        System.exit(0);
    }
}

这个相对来说是一个简单的程序,但也证明了一些shiro的基本用法,我们可以通过shiro进行认证,权限控制等

三、身份验证

身份验证,即在应用中谁能证明他就是他本人。一般提供如他们的身份 ID 一些标识信息来
表明他就是他本人,如提供身份证,用户名/密码来证明。
在 shiro 中,用户需要提供 principals (身份)和 credentials(证明)给 shiro,从而应用能
验证用户身份:
principals身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。
一个主体可以有多个 principals,但只有一个 Primary principals,一般是用户名/密码/手机号。
credentials证明/凭证,即只有主体知道的安全值,如密码/数字证书等。
最常见的 principals 和 credentials 组合就是用户名/密码了。接下来先进行一个基本的身份认
证。
另外两个相关的概念是之前提到的 Subject 及 Realm,分别是主体及验证主体的数据源。

3.1.环境准备

1.引入pom依赖( junit、common-logging 及 shiro-core 依赖)

 <dependencies>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.9</version>
            </dependency>
            <dependency>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
                <version>1.1.3</version>
            </dependency>
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-core</artifactId>
                <version>1.2.2</version>
            </dependency>
        </dependencies>

3.2.登录/退出

1.原理:

使用** FormAuthenticationFilter过滤器实现, 原理如下:
1.用户没有认证时, 就会请求 loginurl进行认证, 用户身份和用户密码提交数据到loginurl地址.
2.数据提交到 loginurl 地址后, 由
FormAuthenticationFilter进行拦截, 并取出 request 中的username 和 password.
3.然后
FormAuthenticationFilter** 会调用 realm, 在进行调用时会传入一个 token, 也就是会传入username 和 password.
4.最后 realm 认真时根据 username 查询用户信息.例如我们之前查询了用户菜单和 url.
如果查询不到, realm 返回 null, ** FormAuthenticationFilter**向 request 域填充一个参数, 这个参数记录了异常信息.

2.实现

    //登陆提交地址,和applicationContext-shiro.xml中配置的loginurl一致
    @RequestMapping("login")
    public String login(HttpServletRequest request)throws Exception{
        
        //如果登陆失败从request中获取认证异常信息,shiroLoginFailure就是shiro异常类的全限定名
        String exceptionClassName = (String) request.getAttribute("shiroLoginFailure");
        //根据shiro返回的异常类路径判断,抛出指定异常信息
        if(exceptionClassName!=null){
            if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
                //最终会抛给异常处理器
                throw new CustomException("账号不存在");
            } else if (IncorrectCredentialsException.class.getName().equals(
                    exceptionClassName)) {
                throw new CustomException("用户名/密码错误");
            } else if("randomCodeError".equals(exceptionClassName)){
                throw new CustomException("验证码错误 ");
            }else {
                throw new Exception();//最终在异常处理器生成未知错误
            }
        }
        //此方法不处理登陆成功(认证成功),shiro认证成功会自动跳转到上一个请求路径
        //登陆失败还到login页面
        return "login";
    }

这里一定要注意我们写的这个 login() 方法, 只是负责处理认证失败的结果, 如果用户认证成功那么就会跳转到上一个请求路径.

什么是上一个请求路径呢?
答: 如果你要访问 xxxx.jsp 但是 shiro 发现你还没有登录, 就会进行拦截, 并跳转到登录界面例如login.jsp, 当认证成功后会跳转到xxxx.jsp

3.退出
不用我们实现退出, 只要访问一个退出 url, 由LogoutFilter拦截住, 清除 session.

/logout.action = logout

4.从 Shiro 的 session 中获取认证信息

//因为我们用过 Shiro 用户认证后是存放在 Shiro 的 session 中.
Subject subject = SecurityUtils.getSubject();
//取出身份信息
subject.getPrincipal();

四、shiro授权

4.1.shiro授权的三种方式:

1.编程式:通过写if/else授权代码块完成:

Subject subject = SecurityUtils.getSubject();  
if(subject.hasRole(“admin”)) {  
    //有权限  
} else {  
    //无权限  
}

2.注解式:通过在执行的Java方法上放置相应的注解完成(没有权限将抛出相应的异常):

@RequiresRoles("admin")  
public void hello() {  
    //有权限  
}

3.JSP/GSP标签:在JSP/GSP页面通过相应的标签完成:

<shiro:hasRole name="admin">  
<!— 有权限 —>  
</shiro:hasRole> 

4.2.实现

1.在src包下创建一个shiro-permission.ini配置文件,用于模拟数据库中的权限数据,内容如下:

#基于用户的访问控制
[users]
#用户zhangsan的密码是123,此用户具有role1和role2两个角色;用户wang具有role2一个角色
zhangsan=123,role1,role2
wang=123,role2

#基于权限的访问控制
[roles]
#角色role1对资源user拥有create、update权限
role1=user:create,user:update
#角色role2对资源user拥有create、delete权限
role2=user:create,user:delete
#角色role3对资源user拥有create权限
role3=user:create

对配置文件中的解释如下:

权限标识符号规则:资源:操作:实例(中间使用半角:分隔)。如下:

user:create:01,表示对用户资源的01实例进行create操作。
user:create,表示对用户资源进行create操作,相当于user:create:,对所有用户资源实例进行create操作。
user:
:01,表示对用户资源实例01进行所有操作。

然后写一个测试类Authorization.java,代码如下:

public class AuthorizationTest
{
    //角色授权测试和资源授权测试
    @Test
    public void testAuthorization()
    {
        //第一步,创建SecurityManager工厂
        Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:shiro-permission.ini");

        //第二步:创建SecurityManager
        SecurityManager securityManager=factory.getInstance();

        //第三步,将SecurityManager设置到系统运行环境,和spring整合后会将SecurityManager配置到spring容器中,一般单例管理
        SecurityUtils.setSecurityManager(securityManager);

        //第四步,创建subject
        Subject subject=SecurityUtils.getSubject();

        //创建token令牌,这里的用户名和密码以后由用户输入
        UsernamePasswordToken token=new UsernamePasswordToken("zhangsan","123");

        try {
            //执行认证,将用户输入的信息同数据库(即.ini配置文件)中信息进行对比
            subject.login(token);
        }catch (AuthenticationException e)
        {
            e.printStackTrace();
        }

        System.out.println("认证状态:"+subject.isAuthenticated());

        //认证通过后才能执行授权
        //第一种授权方式是基于角色的授权,hasRole传入角色的标识
        boolean ishasRole=subject.hasRole("role1");//该用户是否有role1这个角色
        System.out.println("单个角色判断"+ishasRole);

        //hasAllRoles是否拥有多个角色
        boolean hasAllRoles=subject.hasAllRoles(Arrays.asList("role1","role2"));
        System.out.println("多个角色判断"+hasAllRoles);//角色的就讲到这里了,后面我们都是通过资源进行权限讲解


        //使用check方法进行授权,如果授权不通过会抛出异常
        subject.checkRole("role3");

        //第二种授权方式是基于资源的授权,isPermitted传入权限标识符
        boolean isPermitted=subject.isPermitted("user:create");//该用户是否有对user资源进行创建的权限
        System.out.println("单个权限判断"+isPermitted);

        //多个权限判断
        boolean isPermittedAll=subject.isPermittedAll("user:create:1","user:update");
        System.out.println("多个权限判断:"+isPermittedAll);

    }
}

从测试代码中我们可以知道,只有当用户信息得到认证后才能对用户进行授权操作。

上面只是一个简单的入门程序,从代码中直接读取数据库(即配置文件)中的权限数据,然后将其与代码中的给定权限进行判断看该用户是否具有该权限,而实际操作中我们是通过Realm进行读取数据库中的数据的。所以接下来讲自定义Realm进行授权。

4.3.自定义Realm进行授权

1.需求
上边的程序通过shiro-permission.ini对权限信息进行静态配置,实际开发中从数据库中获取权限数据。就需要自定义realm,由realm从数据库查询权限数据。

realm根据用户身份查询权限数据,将权限数据返回给authorizer(授权器)。

2.自定义Realm
在上篇文章自定义的realm中即CustomRealm.java,修改doGetAuthorizationInfo()方法,代码如下:


 //上边是进行认证的方法,在上篇文章中已完成
 
 
 
 //用于授权,当然首先要实现认证
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        //从principals获取主身份信息
        //将getPrimaryPrincipal方法返回值转为真实身份类型(在上边的goGetAuthenticationInfo认证通过填充到SimpleAuthenticationInfo)
        String userCode= (String) principals.getPrimaryPrincipal();

        //根据身份信息获取权限信息,
        //模拟从数据库中获取到的动态权限数据
        List<String> permissions=new ArrayList<>();
        permissions.add("user:create");//模拟user的创建权限
        permissions.add("items:add");//模拟商品的添加权限

        //查到权限数据,返回授权信息(包括上边的permissions)
        SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();

        //将上边查询到授权信息填充到simpleAuthorizationInfo对象中
        simpleAuthorizationInfo.addStringPermissions(permissions);
        return simpleAuthorizationInfo;
    }

然后要对该自定义Realm进行配置,即将自定义Realm放入SecurityManager中,上篇文章中的shiro-realm.ini配置文件中我们已实现。

然后便可以进行测试了,测试代码如下:

 //自定义realm进行资源授权测试
    @Test
    public void testAuthorizationCustomRealm()
    {
        //第一步,创建SecurityManager工厂
        Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:config/shiro-realm.ini");

        //第二步:创建SecurityManager
        SecurityManager securityManager=factory.getInstance();

        //第三步,将SecurityManager设置到系统运行环境,和spring整合后会将SecurityManager配置到spring容器中,一般单例管理
        SecurityUtils.setSecurityManager(securityManager);

        //第四步,创建subject
        Subject subject=SecurityUtils.getSubject();

        //创建token令牌,这里的用户名和密码以后由用户输入
        UsernamePasswordToken token=new UsernamePasswordToken("zhangsan","111111");

        try {
            //执行认证,将用户输入的信息同数据库(即.ini配置文件)中信息进行对比
            subject.login(token);
        }catch (AuthenticationException e)
        {
            e.printStackTrace();
        }

        System.out.println("认证状态:"+subject.isAuthenticated());



        //基于资源的授权,调用isPermitted方法会调用CustomRealm从数据库查询正确的权限数据
        // isPermitted传入权限标识符,判断user:create:1是否在CustomRealm查询到的权限数据之内
        boolean isPermitted=subject.isPermitted("user:create:1");//该用户是否有对user的1资源进行创建的权限
        System.out.println("单个权限判断"+isPermitted);

        //多个权限判断
        boolean isPermittedAll=subject.isPermittedAll("user:create:1","user:create");
        System.out.println("多个权限判断:"+isPermittedAll);
    }

授权流程讲解:

1.对subject进行授权,调用方法isPermitted(“permission串”)。
2.SecurityManager执行授权,通过ModularRealmAuthorizer执行授权。3.
3.ModularRealmAuthorizer执行realm(自定义的CustomRealm)从数据库查询权限数据。即调用realm的授权方法:doGetAuthorizationInfo()
4.realm从数据库查询权限数据,返回ModularRealmAuthorizer
5.ModularRealmAuthorizer调用PermissionResolver进行权限串比对。
6.如果比对后,isPermitted中"permission串"在realm查询到权限数据中,说明用户访问permission串有权限,否则 没有权限,抛出异常。

五、与SpringBoot集成案例

SpringBoot整合Shiro(Java安全框架)案例(含源码)

六、shiro各个拦截器对比及使用配置

6.1.各种拦截器的特点

简写(加粗为常用)名称优先级(1为最高)说明对应Java类
anon匿名拦截器1不需要登录就能访问,一般用于静态资源,或者移动端接口org.apache.shiro.web.filter.authc.AnonymousFilter
authc登录拦截器2需要登录认证才能访问的资源org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasicHttp拦截器3Http身份验证拦截器,非常用类型,不太了解org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
logout登出拦截器4用户登出拦截器,主要属性:redirectURL退出登录后重定向的地址org.apache.shiro.web.filter.authc.LogoutFilter
noSessionCreation不创建会话拦截器5调用 subject.getSession(false) 不会有什么问题,但是如果 subject.getSession(true) 将抛出 DisabledSessionException 异常org.apache.shiro.web.filter.authc.NoSessionCreationFilter
prems权限拦截器6验证用户是否拥有资源权限org.apache.shiro.web.filter.authc.PermissionsAuthorizationFilter
port端口拦截器7其主要属性: port(80) 如果用户访问该页面是非 80,将自动将请求端口改为 80 并重定向到该 80 端口org.apache.shiro.web.filter.authc.PortFilter
restrest风格拦截器8rest 风格拦截器,自动根据请求方法构建权限字符串构建权限字符串;非常用类型拦截器org.apache.shiro.web.filter.authc.HttpMethodPermissionFilter
roles角色拦截器9验证用户是否拥有资源角色org.apache.shiro.web.filter.authc.RolesAuthorizationFilter
sslSSL拦截器10只有请求协议是https才能通过,否则你会自动跳转到https端口(443)org.apache.shiro.web.filter.authc.SslFilter
user用户拦截器11用户拦截器,用户已经身份验证 / 记住我登录的都可;org.apache.shiro.web.filter.authc.UserFilter

6.2.INI文件配置拦截器

1.配置之前,我们还需要讲一下 拦截器的通配符的写法,如下所示:

?:匹配一个字符
*:匹配零个或多个字符
**:匹配零个或多个路径

2.然后,我们看一下[urls]模块的示例.这里我只用到了一些常用的拦截器:

[urls]
#不需要登录
/login=anon
/static/**=anon
#需要登录
/home=authc
#需要角色
/deleteUser = roles["superAdmin"]
#需要用户权限
/addUser = perms["user:create"]

3.但是,我们发现一个问题,假设用户没有该权限和没有该角色,或者没有登录的时候,还有登出的时候,我们都需要给他们配置对应的重定向路径.这时候,我们就不能在[urls]模块中使用了,我们需要在[main]模块中进行各个情况路径的重定向设置了.示例代码如下所示:

[main]
#用户登录的地址
authc.loginUrl = /login
#用户没有对应角色的跳转重指向
roles.unauthorizedUrl = /login
#用户没有对应权限的跳转重指向
perms.unauthorizedUrl = /login
#用户登出的跳转重指向
logout.redirectUrl = /login

注意:INI文件中 [urls] 模块拦截顺序是从上往下依次执行.

6.3.代码形式配置拦截器

使用INI文件配置拦截器是较为简洁的配置形式,其实质就是通过Bean注入的形式配置拦截器 接下来我们看一下我们如何通过代码形式配置拦截器.

配置了核心安全事务管理器 和自定义的权限登录器.代码如下所示:

@Configuration
public class ShiroConfiguration {
    //配置核心安全事务管理器
    @Bean(name="securityManager")
    public DefaultWebSecurityManager securityManager(@Qualifier("myShiroRealm") MyShiroRealm myShiroRealm) {
        DefaultWebSecurityManager manager=new DefaultWebSecurityManager();
        manager.setRealm(myShiroRealm);
        return manager;
    }
    //配置自定义的权限登录器
    @Bean(name="myShiroRealm")
    public MyRealm authRealm() {
        MyRealm myShiroRealm=new MyRealm();
        return myShiroRealm;
    }
}

我们继续配置拦截器模块的代码.整体代码如下所示:

    @Bean(name="shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") DefaultWebSecurityManager manager) {
        ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
        bean.setSecurityManager(manager);
        //配置登录的url和登录成功的url以及验证失败的url
        bean.setLoginUrl("/login");
        bean.setSuccessUrl("/home");
        bean.setUnauthorizedUrl("/login");
        //配置访问权限
        LinkedHashMap<String, String> filterChainDefinitionMap=new LinkedHashMap<>();
        filterChainDefinitionMap.put("/loginUser", "anon");
        filterChainDefinitionMap.put("/static/*", "anon");
        filterChainDefinitionMap.put("/logout*","anon");
        bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return bean;
    }

比较过来,依然发现,使用INI文件形式配置拦截器要比代码形式更加简洁.

6.4.简单使用

现在我们来拿/html/login = anon和/html/home = authc以及设置重定向的authc.loginUrl = /html/login来简单说明一下,具体应该如何使用.

我们想让SpringBoot项目支持Html格式的访问,那么我们需要在pom.xml文件中加入如下的Maven依赖.

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

同时,我们要在application.properties文件中配置如下信息.设置HTML的存放路径

spring.thymeleaf.prefix=classpath:/templates/
spring.mvc.view.suffix=.html

目录结构如下:
在这里插入图片描述

然后,我们把ShiroConfiguration配置文件进行如下的配置:

@Configuration
public class ShiroConfiguration {

    @Bean(name="shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") DefaultWebSecurityManager manager) {
        ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
        bean.setSecurityManager(manager);
        //配置登录的url和登录成功的url以及验证失败的url
        bean.setLoginUrl("/index");
        bean.setSuccessUrl("/home");
        bean.setUnauthorizedUrl("/index");
        //配置访问权限
        LinkedHashMap<String, String> filterChainDefinitionMap=new LinkedHashMap<>();
        filterChainDefinitionMap.put("/index", "anon");
        filterChainDefinitionMap.put("/home", "authc");
        filterChainDefinitionMap.put("/**","anon");
        bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return bean;
    }

    //配置核心安全事务管理器
    @Bean(name="securityManager")
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager manager=new DefaultWebSecurityManager();
        return manager;
    }

}

我们在resources目录下创建两个html页面,一个login.html,一个home.html,其中代码如下所示.

login:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户登录</title>
</head>
<body>

<div style="text-align: center;margin-top: 100px; font-size: 20px;">用户登录界面</div>

</body>
</html>

home:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>主页面</title>
</head>
<body>

<div style="text-align: center;margin-top: 100px; font-size: 20px;">Home界面</div>

</body>
</html>

然后我们需要编写两个接口来访问login.html和home.html.这里我创建了一个名为HtmlController的控制器.整体代码较为简单,这里就直接黏贴出所有代码了.如下所示:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.Map;

@Controller
public class HtmlController {

    @RequestMapping(value = "/index",method = RequestMethod.GET)
    public String userLoginHtmlAction (){
        return "index";
    }

    @RequestMapping(value = "/home",method = RequestMethod.GET)
    public String userHomeHtmlAction (){
        return "home";
    }

}

然后我们就会猜想有如下情况出现,假设我们可以直接通过浏览器访问/html/login,那么我们会直接访问到login.html ,但是当我们访问/html/home的时候,由于我们没有进行用户登录认证(根本就没有做登录接口.😁),所以我们不可能通过认证,通过登录重指向,浏览器仍然会展示login.html 页面,下面我们就启动项目来验证一下.如下所示,拦截成功.
在这里插入图片描述
在这里插入图片描述

七、集成Shiro之Shiro标签(.jsp和.ftl)

7.1.JSP的Shiro标签

在JSP中使用Shiro标签比较简单,我们只需要注意用法即可.下面我就把所以Shiro标签放在下面了,各位看官自行查考:

<shiro:guest>
    游客访问 <a href = "login.jsp"></a>
</shiro:guest>
 
user 标签:用户已经通过认证\记住我 登录后显示响应的内容
<shiro:user>
    欢迎[<shiro:principal/>]登录 <a href = "logout">退出</a>
</shiro:user>
 
authenticated标签:用户身份验证通过,即 Subjec.login 登录成功 不是记住我登录的
<shiro:authenticted>
    用户[<shiro:principal/>] 已身份验证通过
</shiro:authenticted>
 
notAuthenticated标签:用户未进行身份验证,即没有调用Subject.login进行登录,包括"记住我"也属于未进行身份验证
<shiro:notAuthenticated>
    未身份验证(包括"记住我")
</shiro:notAuthenticated>
 
 
principal 标签:显示用户身份信息,默认调用
Subjec.getPrincipal()获取,即Primary Principal
<shiro:principal property = "username"/>
 
hasRole标签:如果当前Subject有角色将显示body体内的内容
<shiro:hashRole name = "admin">
    用户[<shiro:principal/>]拥有角色admin
</shiro:hashRole>
 
hasAnyRoles标签:如果Subject有任意一个角色(或的关系)将显示body体里的内容
<shiro:hasAnyRoles name = "admin,user">
    用户[<shiro:pricipal/>]拥有角色admin 或者 user
</shiro:hasAnyRoles>
 
lacksRole:如果当前 Subjec没有角色将显示body体内的内容
<shiro:lacksRole name = "admin">
    用户[<shiro:pricipal/>]没有角色admin
</shiro:lacksRole>
 
hashPermission:如果当前Subject有权限将显示body体内容
<shiro:hashPermission name = "user:create">
    用户[<shiro:pricipal/>] 拥有权限user:create
</shiro:hashPermission>
 
lacksPermission:如果当前Subject没有权限将显示body体内容
<shiro:lacksPermission name = "org:create">
    用户[<shiro:pricipal/>] 没有权限org:create
</shiro:lacksPermission>

7.2.Freemark的Shiro标签

1.在SpringBoot里面并不是直接支持JSP文件的,然后我就在项目中使用了Freemark,但是Shiro标签并不能直接支持.ftl文件,所以我们需要先引入一个Maven依赖:


<dependency>
    <groupId>net.mingsoft</groupId>
    <artifactId>shiro-freemarker-tags</artifactId>
    <version>0.1</version>
</dependency>

2.然后,我们写一个名为ShiroTagsFreeMarkerCfg配置类来对Freemark使用Shiro标签进行配置.当然了,要确定配置类能被正确注入到Bean中,代码如下所示:

import com.jagregory.shiro.freemarker.ShiroTags;
import freemarker.template.TemplateModelException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;

import javax.annotation.PostConstruct;

@Component
public class ShiroTagsFreeMarkerCfg {

    @Autowired
    private FreeMarkerConfigurer freeMarkerConfigurer;

    @PostConstruct
    public void setSharedVariable() throws TemplateModelException {
        freeMarkerConfigurer.getConfiguration().setSharedVariable("shiro", new ShiroTags());
    }
}

3.配置类搞好之后,我们就可以在.ftl文件中使用Shiro的权限标签了,标签格式如下所示:

<@shiro.guest>  
游客访问 <a href = "login.jsp"></a>
</@shiro.guest> 

user 标签:用户已经通过认证\记住我 登录后显示响应的内容
<@shiro.user>  
欢迎[<@shiro.principal/>]登录,<a href="/logout.html">退出</a>  
</@shiro.user>   

 authenticated标签:用户身份验证通过,即 Subjec.login 登录成功 不是记住我登录的
<@shiro.authenticated>  
    用户[<@shiro.principal/>]已身份验证通过  
</@shiro.authenticated>   
 
notAuthenticated标签:用户未进行身份验证,即没有调用Subject.login进行登录,包括"记住我"也属于未进行身份验证
<@shiro.notAuthenticated>
    当前身份未认证(包括记住我登录的)
</@shiro.notAuthenticated> 
 
 
principal 标签:显示用户身份信息,默认调用
Subjec.getPrincipal()获取,即Primary Principal
<@shiro.principal property="username"/>

 
hasRole标签:如果当前Subject有角色将显示body体内的内容
<@shiro.hasRole name="admin">  
    用户[<@shiro.principal/>]拥有角色admin<br/>  
</@shiro.hasRole> 
 
hasAnyRoles标签:如果Subject有任意一个角色(或的关系)将显示body体里的内容
<@shiro.hasAnyRoles name="admin,user,member">  
用户[<@shiro.principal/>]拥有角色admin或user或member<br/>  
</@shiro.hasAnyRoles>   
 
lacksRole:如果当前 Subjec没有角色将显示body体内的内容
<@shiro.lacksRole name="admin">  
用户[<@shiro.principal/>]不拥有admin角色
</@shiro.lacksRole>   
 
hashPermission:如果当前Subject有权限将显示body体内容
<@shiro.hasPermission name="user:add">  
    用户[<@shiro.principal/>]拥有user:add权限
</@shiro.hasPermission>   

lacksPermission:如果当前Subject没有权限将显示body体内容
<@shiro.lacksPermission name="user:add">  
    用户[<@shiro.principal/>]不拥有user:add权限
</@shiro.lacksPermission> 

八、会话管理

会话管理器管理着应用中所有 Subject 的会话的创建、维护、删除、失效、验证等工作。
Shiro 的核心组件
,顶层组件 SecurityManager 直接继承了 SessionManager,且提供了
SessionsSecurityManager 实 现 直 接 把 会 话 管 理 委 托 给 相 应 的 SessionManager
DefaultSecurityManagerDefaultWebSecurityManager 默认 SecurityManager 都继承了
SessionsSecurityManager

8.1.会话相关API

Subject.getSession()
获取会话,等价于Subject.getSession(true),即如果当前没有创建session对象会创建一个;Subject.getSession(false),如果当前没有创建session对象则返回null。
Subject.getSession(true)
session.getId()
获取当前会话的唯一标识。
session.getHost()
获取当前会话的主机地址。
session.getTimeout() & session.setTimeout(毫秒)
设置/获取当前Session的过期时间。
session.getStartTimestamp() & session.getLastAccessTime()
获取会话的启动时间及最后访问时间;如果是J2SE环境需要自己定期调用session.touch()去更新最后访问时间;如果是Web环境,每次进入ShiroFilter都会自动调用session.touch()来更新最后访问时间。
session.touch() & session.stop()
更新会话最后访问时间以及销毁会话;Subject.logout()会自动调用session.stop()。在Web应用中,调用HttpSession.invalidate()也会自动调用session.stop()来销毁shiro的会话。
session.setAttribute(key,val) & session.getAttribute(key) & session.removeAttribute(key)
设置/获取/删除 会话属性。

8.2.会话监听器(SessionListener接口)

①onStart(Session)
监听会话创建事件
②onStop(Session)
监听会话销毁事件
③onExpiration(Session)
监听会话过期事件

8.3.SessionDao(会话持久化、提供CRUD操作)

在这里插入图片描述

AbstractSessionDAO 提供了 SessionDAO 的基础实现,如生成会话ID等。
CachingSessionDAO 提供了对开发者透明的会话缓存的功能,需要设置相应的 CacheManager。
MemorySessionDAO 直接在内存中进行会话维护。
EnterpriseCacheSessionDAO 提供了缓存功能的会话维护,默认情况下使用 MapCache 实现,内部使用 ConcurrentHashMap 保存缓存的会话。
在这里插入图片描述

tips:在实际开发中,如果要用到SessionDAO组件,可以自定义类实现自EnterpriseCacheSessionDAO类,为其注入sessionIdGenerator属性,如果用到缓存的话还可以注入一个缓存的名字。最后将这个SesionDAO组件注入给SessionManager(会话管理器),最后将SessionManager配置给SecurityManager。下图是一个完整的配置细节。

8.4.会话验证

①Shiro提供了会话验证调度器,用于定期的验证会话是否已过期,如果过期将停止会话。
②出于性能考虑,一般情况下都是获取会话的同时来验证会话是否过期并停止会话的;但是如果在Web环境中,如果用户不主动退出是不知道会话是否过期的,因此需要定义的检测会话是否过期,Shiro提供了会话验证调度器来定期检查会话是否过期,SessionValidationScheduler
③Shrio也提供了使用Quartz会话验证调度器 QuartzSessionValidationScheduler

九、缓存

方法一:

在securityManager配置中添加cacheManager配置项,会注入到realm中。
在这里插入图片描述

方法二:在realm中配置。

realm本身实现了CacheManagerAware接口

public interface CacheManagerAware {

    /**
     * Sets the available CacheManager instance on this component.
     *
     * @param cacheManager the CacheManager instance to set on this component.
     */
    void setCacheManager(CacheManager cacheManager);
}

securityManager不只会帮realm注入cacheManager,还会帮sessionManager注入cacheManager

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="cacheManager" ref="redisCacheManager" />
        <property name="realm" ref="myRealm" />
        <property name="sessionManager" ref="sessionManager"/>
    </bean>

这样的话,realm和sessionManager就不用再配缓存

9.1. 集中式缓存

基于Redis的集中式缓存方案:https://github.com/alexxiyang/shiro-redis

基于Memcached的集中式缓存方案:https://github.com/mythfish/shiro-memcached

基于Ehcache集群模式的存放方案:添加链接描述

9.2. 本地缓存

本地缓存的实现有几种方式:

(1)直接存放到JVM堆内存
(2)使用NIO存放在堆外内存,自定义实现或者借助于第三方缓存组件。

不论是采用集中式缓存还是使用本地缓存,shiro的权限数据本身都是直接存放在本地的,不同的是缓存标志的存放位置。采用本地缓存方案是,我们将缓存标志也存放在本地,这样就避免了查询缓存标志的网络请求,能更进一步提升缓存效率。

十、shiro修仙进阶

本篇对于shiro的学习到这就结束了 如果有不全或者不足的地方

说明网址
shiro视频教程shiro视频教程
shiro系列博客shiro系列博客
shiro具体操作shiro具体操作
shiro官方文档shiro官方文档

点击全文阅读


本文链接:http://zhangshiyu.com/post/27651.html

会话  权限  用户  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1