看完这个你还不懂shiro吗,小例子(附源码)

24 篇文章 6 订阅
5 篇文章 0 订阅

看完这个你还不懂shiro吗,小例子(附源码)

前言

今天是学习Shiro的笔记哦,所有的学习笔记都是适合初学者的,如果你学习过Shiro也当作一次复习再过一遍,我就经常再拿回来看,这样快捷方便自己回顾很多东西,那看到这里还不点个赞?毕竟好多学习加上写笔记都要好几天,所以博主更新时快时慢,希望通过这篇博客对你有帮助;

我们最后通过一个小实例来学习Shiro的一个小应用吧,毕竟只有写代码才能更好的理解,源码在最后;

介绍

官网:https://shiro.apache.org/

image-20220406075452777

来自百度百科:

image-20220406075440801

Quickstart

首先我们进入官网,然后这里有一个下载,我们点击下载;

image-20220702125940401

这里有最新版本更新到了1.9.1,点击然后就往下走了,让你去github上下载,我们用这个链接进入github

image-20220702130138233

我们点击code下载一个zip压缩文件就可以了

image-20220702130304547

打开文件里面有一个Quickstart十分钟快速上手,参考这个代码,我们看下

这个里面有很多英文注释,相信大家看到一看就懂了,而且很简单对不对,十分钟上手体验代码,这里我不是很懂留下来做个翻译;

image-20220702130644262

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

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


/**
 * Simple Quickstart application showing how to use Shiro's API.
 *
 * @since 0.9 RC2
 */
public class Quickstart {

    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);


    public static void main(String[] args) {

        // The easiest way to create a Shiro SecurityManager with configured
        // realms, users, roles and permissions is to use the simple INI config.
        // We'll do that by using a factory that can ingest a .ini file and
        // return a SecurityManager instance:

        // Use the shiro.ini file at the root of the classpath
        // (file: and url: prefixes load from files and urls respectively):
        // 首先我们可以读取ini文件,里面的内容创建一个factory实例
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        // 然后我们可以从工厂中实例一个对象叫做securityManager
        SecurityManager securityManager = factory.getInstance();

        // for this simple example quickstart, make the SecurityManager
        // accessible as a JVM singleton.  Most applications wouldn't do this
        // and instead rely on their container configuration or web.xml for
        // webapps.  That is outside the scope of this simple quickstart, so
        // we'll just do the bare minimum so you can continue to get a feel
        // for things.
        // 我们可以把这个实例对象交给SecurityUtils这个工具类,然后它帮我们做一些步骤
        SecurityUtils.setSecurityManager(securityManager);

        // Now that a simple Shiro environment is set up, let's see what you can do:
        // 其实我们发现,前面几步骤,我们写过那么多代码了是不是都是固定死的啊,好比我们用jdbc创建链接到关闭链接
        // 然后我们这个时候得到了第一个可以认为是user的对象,第一个对象来了;
        // 我们记住这个对象
        // get the currently executing user:
        Subject currentUser = SecurityUtils.getSubject();

        // 我们还可以创建一个关于这个对象的session,这里的session可不是http里面的session,但是作用可以互相理解
        // Do some stuff with a Session (no need for a web or EJB container!!!)
        Session session = currentUser.getSession();

        // 可以用来存值和取值
        session.setAttribute("someKey", "aValue");
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("Retrieved the correct value! [" + value + "]");
        }

        // Shiro安全管理嘛,看到Authenticated一定知道是授权,看看有没有权限
        // let's login the current user so we can check against roles and permissions:
        if (!currentUser.isAuthenticated()) {
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            // 看单词就知道了,记住我功能
            token.setRememberMe(true);
            try {
                // 登录,他这个登录失败靠什么呢,异常
                currentUser.login(token);
            } catch (UnknownAccountException uae) {
                // 没有这个用户抛出这个异常UnknownAccountException
                log.info("There is no user with username of " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {
                // 密码错误
                log.info("Password for account " + token.getPrincipal() + " was incorrect!");
            } catch (LockedAccountException lae) {
                // 没有权限
                log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                        "Please contact your administrator to unlock it.");
            }
            // ... catch more exceptions here (maybe custom ones specific to your application?
            catch (AuthenticationException ae) {
                // 猜猜这是什么
                //unexpected condition?  error?
            }
        }

        //say who they are:
        //print their identifying principal (in this case, a username):、
        // 登录成功之后,我们可以拿到用户信息(这里是拿到有用户名)
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

        //test a role:
        // 还可以是否有什么角色
        if (currentUser.hasRole("schwartz")) {
            log.info("May the Schwartz be with you!");
        } else {
            log.info("Hello, mere mortal.");
        }

        // 还可以看是否对资源有访问授权
        //test a typed permission (not instance-level)
        if (currentUser.isPermitted("lightsaber:wield")) {
            log.info("You may use a lightsaber ring.  Use it wisely.");
        } else {
            log.info("Sorry, lightsaber rings are for schwartz masters only.");
        }

        //a (very powerful) Instance Level permission:
        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!");
        }

        // 退出登录
        //all done - log out!
        currentUser.logout();

        // 程序结束
        System.exit(0);
    }
}

这里我们看这张图再细细理解一下,第一个subject可以理解为用户,然后通过securityManager进行管理,最后允许你访问数据;这是官方给出的小例子,快速入门,是不是就理解,如果没有理解看看咱下面的小例子;

image-20220702130921700

小例子

首先小例子的那例子还是咱们springsecurity中的小例子,这里拿过来接着用,这里是springsecurity跟这个一样的教程:

看完这一篇你还不会springsecurity吗?springsecurity小实例,简单实现网站登录获权(附源码)

同样的我们先看下效果,这里主要完成页面,页面简单方便理解,实现功能就行;

首先就是进入主页,只有主页公共信息显示;

image-20220702151519494

然后登录,我们这里有三个用户,然后实现不同用户展示不用界面,看一眼就明白哈

image-20220702151552788

比如我们登录root用户

image-20220702151634690

登录zhangsan

image-20220702151711073

lisi就不用说了,这就是简单功能实现通过Shrio,我们看下代码:

新建项目,首先就是导入依赖

<dependencies>


    <!-- https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro -->
    <dependency>
        <groupId>com.github.theborakompanioni</groupId>
        <artifactId>thymeleaf-extras-shiro</artifactId>
        <version>2.1.0</version>
    </dependency>



    <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring-boot-web-starter -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring-boot-web-starter</artifactId>
        <version>1.9.0</version>
    </dependency>

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

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

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>


    <!-- mysql-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>


    <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.0.5</version>
    </dependency>


    <!--模板引擎 依赖:mybatis-plus代码生成的时候报异常-->
    <dependency>
        <groupId>org.apache.velocity</groupId>
        <artifactId>velocity-engine-core</artifactId>
        <version>2.0</version>
    </dependency>
    <!--配置ApiModel在实体类中不生效-->
    <dependency>
        <groupId>com.spring4all</groupId>
        <artifactId>spring-boot-starter-swagger</artifactId>
        <version>1.5.1.RELEASE</version>
    </dependency>
    <!--freemarker-->
    <dependency>
        <groupId>org.freemarker</groupId>
        <artifactId>freemarker</artifactId>
        <version>2.3.30</version>
    </dependency>
    <!--beetl-->
    <dependency>
        <groupId>com.ibeetl</groupId>
        <artifactId>beetl</artifactId>
        <version>3.3.2.RELEASE</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.80</version>
    </dependency>

</dependencies>

我们先看下项目结构:

image-20220702151901691

首先我们用什么我们需要将这个注册到spring容器中,我们首先看下config配置,首先就是我们那个图里面的三个对象,然后互相创建有依赖关系,从上到下,固定的,跟我们刚才quickstart里面上面那几行代码就是那个,这里都是管理权限的;

@Configuration
public class ShiroConfig {

    //创建realm对象
    @Bean
    public UserRealm userRealm() {
        UserRealm realm = new UserRealm();
        /*        //设置加密算法为md5
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName("md5");
        //设置加密次数
        credentialsMatcher.setHashIterations(1024);
        realm.setCredentialsMatcher(credentialsMatcher);*/
        return realm;
    }

    //DefaultWebSecurityManager 管理器
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    // 设置权限过滤器
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(
        @Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);


        /*
            anon: 任何人都可以访问
            authc: 必须认证才可以访问
            user: 使用记住我功能才可以使用
            perms[]: 拥有对应某个权限,资源才可以访问
            role: 角色拥有的权限才可以访问
         */
        // 存放自定义的filter
        LinkedHashMap<String, Filter> filtersMap = new LinkedHashMap<>();
        filtersMap.put("permsOrFilter", new RoleFilter());
        shiroFilterFactoryBean.setFilters(filtersMap);

        Map<String, String> filter = new LinkedHashMap<>();
        //设置登录退出
        filter.put("/logout", "logout");

        // 然后拥有什么权限 perms这里只能能达到单一权限单一认证,
        // 并不能达到我们想要的ssvip也可以访问vip服务
        // 所以这里我们重写下过滤器,然后我们可以这样实现

        /* filter.put("/user/ssvip", "perms[ssvip]");
        //想要ssvip也可以访问vip服务
        filter.put("/user/svip", "perms[svip]");
        filter.put("/user/vip", "perms[vip]");*/

        //        自定义 or角色 认证
        filter.put("/user/ssvip", "permsOrFilter[ssvip]");
        filter.put("/user/svip", "permsOrFilter[ssvip,svip]");
        filter.put("/user/vip", "permsOrFilter[ssvip,svip,vip]");
        //必须要授权才可以
        filter.put("/user/*", "authc");



        shiroFilterFactoryBean.setFilterChainDefinitionMap(filter);
        //没有登录去登录页面
        shiroFilterFactoryBean.setLoginUrl("/tologin");
        //未授权跳转到未授权页面
        //        shiroFilterFactoryBean.setUnauthorizedUrl("");

        return shiroFilterFactoryBean;
    }

    //shiro整合thymeleaf
    @Bean
    public ShiroDialect getShiroDialect(){
        return new ShiroDialect();
    }

}

这里是我们创建的UserRealm,用户信息首先是认证,然后授权,也就我们通过shrio框架登录方法,首先进入AuthenticationInfo通过前端获取的参数,去数据库查找,认证成功可以将对象信息放入Session,这里的Session不是httpSession

然后到AuthorizingRealm获取登录的用户权限;

public class UserRealm extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //授权
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//        info.addStringPermission("ROLE_vip");
        //可以获取到下面的user对象
        Subject subject = SecurityUtils.getSubject();
        Users user = (Users) subject.getPrincipal();

        //添加当前用户的权限
        info.addStringPermission(user.getUserRole());

        return info;
    }

    @Autowired
    UsersService usersService;



    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //认证
        /*String name ="root";
        String password = "root";*/

        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
        QueryWrapper<Users> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_name",usernamePasswordToken.getUsername());
        Users user = usersService.getOne(queryWrapper);


        if (!usernamePasswordToken.getUsername().equals(user.getUserName())) {
            return null; //自动抛出用户名错误异常,密码shiro会自己做
        }

        //我们可以把登录的对象存入session,这里的session并不是httpSession
        Subject subject = SecurityUtils.getSubject();
        Session session = subject.getSession();
        session.setAttribute("user",user);

        return new SimpleAuthenticationInfo(user, user.getUserPassword(), user.getUserName());
    }
}

因为我们想完成一个多权限也就是OR的认证,默认没有这样的认证,所以我们自己写了一个过滤器,对用户角色的权限的分配按优先级也就是你ssvip可以访问svip和vip这种的,配置进去就行了,其实很简单,自己做下debug想怎么改怎么改,这里是用的Role当然还可以做Permission等等

@Component
public class  RoleFilter extends RolesAuthorizationFilter {
    @Override
    public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
            throws IOException {

        final Subject subject = getSubject(request, response);
        final String[] rolesArray = (String[]) mappedValue;

        if (rolesArray == null || rolesArray.length == 0) {
            // 无指定角色时,无需检查,允许访问
            return true;
        }

        for (String roleName : rolesArray) {
            System.out.println("==================");
            System.out.println(roleName);
            //如果我们设置的role我们可以使用这个
            System.out.println(subject.hasRole(roleName));
            System.out.println(subject.isPermitted(roleName));
            if (subject.isPermitted(roleName)) {
                return true;
            }
        }
        return false;
    }
}

我们通过前端获取用户名用户密码,然后通过异常进行错误信息的抛出提示,上面以下都是路由;

image-20220702152808903

这就是基本流程,其他的都是mybaitis框架代码,这里不用关注;

然后就是整合thymeleaf,上面有依赖记得导入依赖,然后就是config里面配置进去就可以了,注入一个bean,这些代码就很简单了,用session和一些shiro的标签:shiro:hasAnyPermissions,这些一看就知道了,没什么好说的吧;

如果说还想了解,可以搜一下更多,比如这里这些一看就知道吧:https://wenku.baidu.com/view/1b0c053dfc00bed5b9f3f90f76c66137ee064faf.html

image-20220702153307588

主要就是自己动手尝试下就可以了,尝试写贼简单,一遍就会了,下面是源码,可以比对着实现下简单的功能;

链接:https://pan.baidu.com/s/1QNq66gCJMXMXW0kwlYltEQ?pwd=q41v 
提取码:q41v

小结

最后就是通过这个小例子希望大家可以简单实用Shiro快速入门篇,一定要自己动手实现下,不然光看一遍怎么可以,跟着写一下小功能,都看到这里了还不给博主点个赞吗,留个关注也行啊,后面还有更多精彩内容,bye~

  • 24
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 42
    评论
评论 42
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学习日记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值