关于java:Shiro入门学习

学习一门新技术的思路

是什么?

是用来干嘛的,利用在哪些地方?

为什么要学?

与它相干的有哪些,须要另外理解的常识?

怎么去学?

文章次要内容:

  • 一、什么是Shiro——是什么?
  • 二、Shiro的性能及具体个性(劣势)——是用来干嘛的利用在哪些地方?为什么要学?
  • 三、【拓展】RBAC(以角色为根底的访问控制)——须要另外理解的实践根底
  • 四、与其相干的SpringSecurity框架——与它相干的有哪些?
  • 五、第一个简略纯正的Shiro程序——怎么学?先会用跑起来再逐渐了解。干!
  • 六、Shiro的架构(三大外围组件)
  • 七、Shiro集成SpringBoot和数据库应用
  • 八、应用Shiro的坑
  • 九、总结

一、什么是Shiro

官网文档:

http://shiro.apache.org/index.html

官网介绍:

Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.

  • Apache Shiro 是一个简略易用的Java 平安(权限)框架
  • Shiro 能够非常容易的开发出足够好的利用,其不仅能够用在JavaSE环境,也能够用在JavaEE环 境。(即能够不须要Web和EJB等容器反对)
  • 能够提供认证、受权、加密、会话治理,Web集成,缓存等。

二、Shiro的性能个性(劣势)

Primary Concerns:

  • Authentication(认证):用户身份辨认,即一个用户是怎么的角色,是管理员或者普通用户?通常被称为用户“登录”
  • Authorization(受权):访问控制。比方某个用户是否具备某个删除操作的应用权限。
  • Session Management(会话治理):特定于用户的会话治理,即便在非web 或 EJB 应用程序,可任意应用Session API。能够响应认证、访问控制,或者Session生命周期中产生的事件。
  • Cryptography(加密):在对数据源应用加密算法加密的同时,保障易于应用。

Supporting Features:

  • Web Support(Web反对):Shiro的Web反对API有助于爱护Web应用程序。能够非常容易的集成到Web环境。
  • Caching(缓存):缓存是Apache Shiro API中的第一级,以确保安全操作放弃疾速和高效。比方用户登录后,其用户信息,领有的角色、权限不用每次去查,这样能够提高效率
  • Concurrency(并发性):Apache Shiro反对多线程利用的并发验证。即,如在一个线程中开启另一个线程,能把权限主动的流传过来。
  • Testing(测试):存在测试反对,可帮忙编写单元测试和集成测试,并确保代码按预期失去保障。
  • Run As(运行形式):容许用户承当(伪装)另一个用户的身份(如果容许)的性能,有时在治理计划中很有用。
  • Remember Me:反对提供“Remember Me”服务,获取用户关联信息而无需登录
  • 可将一个或以上用户平安数据源数据组合成一个复合的用户 “view”(视图)
  • 反对单点登录(SSO)性能

……

但,Shiro不会去保护用户、保护权限,这些须要咱们本人去设计/提供,而后通过相应的接口注入给Shiro

其中Authentication(认证), Authorization(受权), Session Management(会话治理), Cryptography(加密)被 Shiro 框架的开发团队称之为利用平安的四大基石

长处(为什么要学?):

易于应用、全面、灵便、Web反对、低耦合、被广泛支持宽泛应用(是Apache软件基金会的一部分)

公司要用、面试要问。。。

三、【拓展】RBAC(以角色为根底的访问控制)

1、什么是RBAC

维基百科:以角色为根底的访问控制Role-based access controlRBAC),是资讯平安畛域中,一种较新且广为应用的访问控制机制,不间接赋予使用者权限,而是将权限赋予角色。

RBAC通过角色关联用户,角色关联权限的形式间接赋予用户权限。如下图

有人会问为什么不间接给用户调配权限,还多此一举的减少角色这一环节呢?

其实是能够间接给用户调配权限,只是间接给用户调配权限,少了一层关系,扩展性弱了许多,适宜那些用户数量、角色类型少的平台。

对于通常的零碎,比方:存在多个用户领有雷同的权限,在调配的时候就要别离为这几个用户指定雷同的权限,批改时也要为这几个用户的权限进行一一批改。有了角色后,咱们只须要为该角色制订好权限后,将雷同权限的用户都指定为同一个角色即可,便于权限治理。

对于批量的用户权限调整,只需调整用户关联的角色权限,无需对每一个用户都进行权限调整,既大幅晋升权限调整的效率,又升高了漏调权限的概率。

小结:

RBAC 的长处次要在于易用和高效。给用户受权时只须要对角色受权,而后将相应的角色调配给用户即可;从技术角度讲,思路清晰且易于实现,且前期保护时只须要保护关系模型,显得简略而高效。

RBAC 的毛病次要有两个:一个是在进行较为简单的权限校验时须要一直地遍历和递归,会造成肯定的性能影响。另一个是短少数据权限模型,基于 RBAC 来实现数据权限校验比较复杂和低效。

当初支流的权限管理系统设计大多还是基于RBAC模型的,只是依据不同的业务和设计方案,出现不同的显示成果。

2、RBAC模型的分类

RBAC模型能够分为:RBAC0、RBAC1、RBAC2、RBAC3 四种。其中RBAC0是根底,也是最简略的,相当于底层逻辑,RBAC1、RBAC2、RBAC3都是以RBAC0为根底的降级。

个别状况下,应用RBAC0模型就能够满足惯例的权限管理系统设计了。

RBAC0模型:

最简略的用户、角色、权限模型。是根底,定义了能形成 RBAC 权限控制系统的最小的汇合。

RBAC0 由四局部形成:

  • 用户(User) 权限的应用主体
  • 角色(Role) 蕴含许可的汇合
  • 会话(Session)绑定用户和角色关系映射的两头通道。而且用户必须通过会话能力给用户设置角色。
  • 许可(Pemission) 对特定资源的特定的拜访许可。

RBAC0对应的表构造:

RBAC0 外面又蕴含了2种(用户和角色的表关系):

  1. 用户和角色是多对一关系,即:一个用户只充当一种角色,一种角色能够有多个用户担当。
  2. 用户和角色是多对多关系,即:一个用户可同时充当多种角色,一种角色能够有多个用户担当。

那么,什么时候该应用多对一的权限体系,什么时候又该应用多对多的权限体系呢?

如果零碎性能比拟繁多,应用人员较少,岗位权限绝对清晰且确保不会呈现兼岗的状况,此时能够思考用多对一的权限体系。其余状况尽量应用多对多的权限体系,保证系统的可扩展性。如:张三既是行政,也负责财务工作,那张三就同时领有行政和财务两个角色的权限。

角色与权限是多对多关系,用户与权限之间也是多对多关系,通过角色间接建设。

3张根底表:用户、角色、权限

2张两头表:建设用户与角色的多对多关系,角色与权限的多对多关系。

DROP DATABASE IF EXISTS shiro;
CREATE DATABASE shiro DEFAULT CHARACTER SET utf8;
USE shiro;
 
DROP TABLE IF EXISTS `user`;
DROP TABLE IF EXISTS role;
DROP TABLE IF EXISTS permission;
DROP TABLE IF EXISTS user_role;
DROP TABLE IF EXISTS role_permission;

/*用户表*/
CREATE TABLE `user` (
  id BIGINT AUTO_INCREMENT,
  NAME VARCHAR(100),
  PASSWORD VARCHAR(100),
  CONSTRAINT pk_users PRIMARY KEY(id)
) CHARSET=utf8 ENGINE=INNODB;

/*角色表*/
CREATE TABLE role (
  id BIGINT AUTO_INCREMENT,
  NAME VARCHAR(100),
  CONSTRAINT pk_roles PRIMARY KEY(id)
) CHARSET=utf8 ENGINE=INNODB;

/*权限表*/
CREATE TABLE permission (
  id BIGINT AUTO_INCREMENT,
  NAME VARCHAR(100),
  CONSTRAINT pk_permissions PRIMARY KEY(id)
) CHARSET=utf8 ENGINE=INNODB;

/*用户角色(关系)表*/
CREATE TABLE user_role (
  uid BIGINT,
  rid BIGINT,
  CONSTRAINT pk_users_roles PRIMARY KEY(uid, rid)
) CHARSET=utf8 ENGINE=INNODB;

/*角色权限(关系)表*/
CREATE TABLE role_permission (
  rid BIGINT,
  pid BIGINT,
  CONSTRAINT pk_roles_permissions PRIMARY KEY(rid, pid)
) CHARSET=utf8 ENGINE=INNODB;

其余模型这里不做过多深究介绍,其余模型的了解参考此篇文章:

http://www.woshipm.com/pd/1150093.html

3、权限(许可)

权限是资源的汇合。

这里的资源指的是软件中所有的内容,包含模块、菜单、页面、字段、操作性能(增删改查)等等。

具体的权限配置上,能够将权限分为:页面权限、操作权限和数据权限

页面权限:所有零碎都是由一个个的页面组成,页面再组成模块,用户是否能看到这个页面的菜单、是否能进入这个页面就称为页面权限。

操作权限:用户但凡在操作系统中的任何动作、交互都是操作权限,如增删改查等。

数据权限:个别业务管理系统,都有数据私密性的要求:哪些人能够看到哪些数据,不能够看到哪些数据。

四、与其相干的SpringSecurity框架

与Shiro相干的,可能就是SpringSecurity了

官网:https://spring.io/projects/spring-security


Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.

Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements

是一个认证和访问控制(受权)框架,来爱护基于Spring的应用程序,专一于为java利用提认证和受权。

SpringSecurity属于Spring全家桶的一部分,对于Spring我的项目来说,其实应用它是讨巧的。

对于Shiro和SpringSecurity的比照,笔者在网上查问了下材料,并没发现有讲得很好的。

大多说的是因需应用,看应用场景,抉择应用。但在简略性上,还是优先选择Shiro。本人对这两大框架的利用也并没有太多,因而也无奈讲清。在此只是提一嘴SpringSecurity,感兴趣的可自行比照钻研。

五、第一个简略纯正的Shiro程序

依据官网:

1、新建一个一般的Maven我的项目

2、导入对应的Pom依赖

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.4.1</version>
</dependency>
<!-- Shiro uses SLF4J for logging.  We'll use the 'simple' binding
             in this example app.  See http://www.slf4j.org for more info. -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.21</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>1.7.21</version>
</dependency>
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

3、编写Shiro配置

https://github.com/apache/shiro/blob/master/samples/quickstart/src/main/resources/log4j.properties

log4j.properties:

log4j.rootLogger=INFO, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n

# General Apache libraries
log4j.logger.org.apache=WARN

# Spring
log4j.logger.org.springframework=WARN

# Default Shiro logging
log4j.logger.org.apache.shiro=INFO

# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN

shiro.ini:

[users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz

# -----------------------------------------------------------------------------
# Roles with assigned permissions
# 
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
# -----------------------------------------------------------------------------
[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5

4、编写Quickstart.java

官网上有一个10分钟教程,它让咱们先看Quickstart.java学习

https://github.com/apache/shiro/blob/master/samples/quickstart/src/main/java/Quickstart.java

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;

/**
 * Simple Quickstart application showing how to use Shiro's API.
 * 简略的疾速启动应用程序,演示如何应用Shiro的API。
 * @since 0.9 RC2
 */
public class Quickstart {

    //日志门面,默认是commons-logging
    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);


    public static void main(String[] args) {

        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityUtils.setSecurityManager(securityManager);
        
        // Now that a simple Shiro environment is set up, let's see what you can do:
        // get the currently executing user:
        //取得以后执行用户(重要!!)
        Subject currentUser = SecurityUtils.getSubject();
        // Do some stuff with a Session (no need for a web or EJB container!!!)
        //不同于HttpSession,不须要Web或EJB的容器反对
        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 + "]");
        }

        // let's login the current user so we can check against roles and permissions:
        //以后用户身份验证
        if (!currentUser.isAuthenticated()) {
            //创立标记,其中用户名和明码是读取shiro.ini配置文件中的
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            token.setRememberMe(true);
            try {
                //执行登录操作!!!源码中看不到,但就是这个操作!
                currentUser.login(token);
            } catch (UnknownAccountException uae) {//未知用户异样(用户不存在)
                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) {//大异样,相似java中的Exception
                //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);
    }
}

5、main办法启动测试

运行后果:

打印了一堆默认的日志音讯

2020-09-19 13:09:07,652 INFO [org.apache.shiro.session.mgt.AbstractValidatingSessionManager] - Enabling session validation scheduler... 
2020-09-19 13:09:07,733 INFO [Quickstart] - Retrieved the correct value! [aValue] 
2020-09-19 13:09:07,734 INFO [Quickstart] - User [lonestarr] logged in successfully. 
2020-09-19 13:09:07,734 INFO [Quickstart] - May the Schwartz be with you! 
2020-09-19 13:09:07,734 INFO [Quickstart] - You may use a lightsaber ring.  Use it wisely. 
2020-09-19 13:09:07,735 INFO [Quickstart] - You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  Here are the keys - have fun! 
  • 若启动报错,Pom中尝试导入后
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
  <groupId>commons-logging</groupId>
  <artifactId>commons-logging</artifactId>
  <version>1.2</version>
</dependency>
  • 若启动后,什么都没打印,尝试Pom依赖中删掉所有的<scope>runtime</scope>作用域

6、加深了解

①类的形容

/**
* Simple Quickstart application showing how to use Shiro's API. 
* 简略的疾速启动应用程序,演示如何应用Shiro的API。
*/

②通过工厂模式创立SecurityManager的实例对象

// 读取类门路下的shiro.ini文件
// Use the shiro.ini file at the root of the classpath
// (file: and url: prefixes load from files and urls respectively):
Factory<SecurityManager> factory = new
IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
// 曾经建设了一个简略的Shiro环境

③获取以后的Subject

// get the currently executing user: 获取以后正在执行的用户
Subject currentUser = SecurityUtils.getSubject();

④session的操作

// 用会话做一些事件(不须要web或EJB容器!!!)
// Do some stuff with a Session (no need for a web or EJB container!!!)
//存值取值
Session session = currentUser.getSession(); //取得session
session.setAttribute("someKey", "aValue"); //设置Session的值!
String value = (String) session.getAttribute("someKey"); //从session中获取
值
if (value.equals("aValue")) { //判断session中是否存在这个值!
  log.info("==Retrieved the correct value! [" + value + "]");
}

⑤用户认证

// 测试以后的用户是否曾经被认证,即是否曾经登录!
// let's login the current user so we can check against roles and
permissions:
if (!currentUser.isAuthenticated()) { // isAuthenticated();是否认证
    //将用户名和明码封装为 UsernamePasswordToken ;
    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 toyour application?
    catch (AuthenticationException ae) { //认证异样,下面的异样都是它的子类
    //unexpected condition? error?
    }
}
   //             
   log.info("User [" + currentUser.getPrincipal() + "] logged insuccessfully.");

⑥角色查看

//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);

六、Shiro的架构(三大外围组件)

Shiro 架构蕴含三个次要的理念:Subject、SecurityManagerRealm

  • Subject:代表以后用户,Subject 能够是一个人,但也能够是第三方服务、守护过程帐户、时钟守护工作或者其它以后和软件交互的任何事件(网络爬虫、机器人等)。Subject与利用代码间接交互,也就是说Shiro的对外API外围就是Subject。SecurityManageer才是理论的执行者。
  • SecurityManager:平安管理器,治理所有Subject,SecurityManager 是 Shiro 架构的外围,负责与Shiro的其余组件进行交互,它相当于SpringMVC的DispatcherServlet的角色。
  • Realms:域,但这不容易了解。它用于进行权限信息的验证,须要咱们本人实现(能够用JDBC实现,也能够是内存实现)。SecurityManager 要验证用户身份,那么它须要从Realm 获取相应的用户进行比拟,来确定用户的身份是否非法;也须要从Realm失去用户相应的角色、权限,进行验证用户的操作是否可能进行。所以Realm 实质上是一个特定的平安 DAO:它封装与数据源连贯的细节,失去Shiro 所需的相干的数据。在配置 Shiro 的时候,你必须指定至多一个Realm 来实现认证(authentication)和/或受权(authorization)。

咱们须要实现Realms的Authentication 和 Authorization。其中 Authentication 是用来验证用户身份,Authorization 是受权访问控制,用于对用户进行的操作受权,证实该用户是否容许进行以后操作,如拜访某个链接,某个资源文件等。

外部架构

  • Authenticator:负责Subject认证,是一个扩大点,能够自定义实现;能够应用认证策略
    (Authentication Strategy),即什么状况下算用户认证通过了
  • Authorizer:受权器,即拜访控制器,用来决定主体是否有权限进行相应的操作;即管制着用户能
    拜访利用中的那些性能
  • SessionManager:治理Session生命周期的组件,而Shiro并不仅仅能够用在Web环境,也能够用
    在一般的JavaSE环境中
  • CacheManager:缓存控制器,来治理如用户,角色,权限等缓存的;因为这些数据基本上很少改
    变,放到缓存中后能够进步拜访的性能
  • Cryptography:明码模块,Shiro 进步了一些常见的加密组件用于明码加密,解密等。

Shiro中其余的一些概念:

  • Principals:个别指用户名等,惟一表明Subject身份也就是以后用户身份的货色;
  • Credentials:凭证,个别指明码,对以后登陆用户进行验证;

七、Shiro集成SpringBoot和数据库应用

场景工作:

  • 用户拜访注册页面,输出用户名明码,点击提交注册后,用shiro进行盐值MD5加密后保留到数据库——注册
  • 用户拜访登录页面,进行登录时通过shiro进行用户认证——登录认证
  • 登录胜利则显示以后登录的用户名(session治理)——登录认证
  • 进入首页,拜访其余页面须要认证登录——页面拦挡登录

第一局部:简略环境搭建

1、建库建表sql

DROP DATABASE IF EXISTS shiro;
CREATE DATABASE shiro DEFAULT CHARACTER SET utf8;
USE shiro;
/*用户表*/
/*salt 是盐,用来和 shiro 联合的时候,加密用的*/
CREATE TABLE `user`(
  id INT(11) NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(255)DEFAULT NULL,
  `password` VARCHAR(255)DEFAULT NULL,
  salt VARCHAR(255)DEFAULT NULL,
  PRIMARY KEY(id)
)ENGINE=INNODB DEFAULT CHARSET=utf8;

2、新建一个SpringBoot我的项目

勾选lombok、SpringWeb、thymeleaf、JDBC API以及MySQL Driver

3、导入Pom依赖,shiro两种形式

第一种:

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-web</artifactId>
    <version>1.4.0</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.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.6.0</version>
</dependency>

其中第二种会报错,在开端第九局部会具体阐明呈现了什么谬误以及怎么解决

mybatis依赖:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.1</version>
</dependency>

4、配置application.yml文件

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/shiro?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    username: root
    password: admin
    
  thymeleaf:
    cache: false
    encoding: utf-8
    mode: HTML5
    servlet:
      content-type: text/html

mybatis:
  mapper-locations: classpath:/mapper/*.xml
  type-aliases-package: com.cqy.shiro.pojo
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

server:
  servlet:
    context-path: /shiro

5、编写一个简略的register.html注册页面(应用Vue)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>注册</title>
    <script src="js/vue/2.5.16/vue.min.js"></script>
    <script src="js/axios/0.17.1/axios.min.js"></script>
<!--    <script src="js/jquery/2.0.0/jquery.min.js"></script>-->
    <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
</head>
<body>
<script>
    $(function () {
        var data4 = {
            uri: 'submitregister',
            user: {
                name: '',
                password:''
            }
        };
        var vue = new Vue({
            el: '#work',
            data: data4,
            mounted: function(){

            },
            methods: {
                submit: function () {
                    var url = this.uri;
                    axios.post(url,this.user).then(function (response) {
                       //临时不写,前面写了controller后再编写
                    });
                }
            }
        });
    });
</script>

<div id="work">
    <h3>注册</h3>
    用户名:<input type="text" v-model="user.name"><br>
    明码:<input type="password" v-model="user.password"><br>
    <button type="button" @click="submit">提交</button>
</div>
</body>
</html>

6、PageController用于页面跳转

register.html放在templates外面,间接拜访不了,须要外部跳转拜访

@Controller
public class PageController {

    @GetMapping("/register")
    public String register(){
        return "register";
    }
}

7、创立pojo,User类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;
    private String password;
    private String salt;
}

8、创立UserMapper和UserMapper.xml

这里为了便捷,间接应用Class不再进行dao、service层的编写

提供两个办法

@Repository
public interface UserMapper {
    //依据name查问用户
    public User queryUserByName(String name);
    //增加用户
    public int addUser(User user);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cqy.shiro.mapper.UserMapper">

    <select id="queryUserByName" resultType="user">
        select * from `user` where `name`=#{name}
    </select>

    <insert id="addUser" parameterType="user">
        insert into `user` values (#{id},#{name},#{password},#{salt})
    </insert>

</mapper>

9、创立一个login.html登录页面用于登录

<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>登录</title>
    <script src="js/vue/2.5.16/vue.min.js"></script>
    <script src="js/axios/0.17.1/axios.min.js"></script>
    <!--    <script src="js/jquery/2.0.0/jquery.min.js"></script>-->
    <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
</head>
<body>
<script>
    $(function () {
        var data4 = {
            uri: 'submitlogin',
            user: {
                name: '',
                password:''
            }
        };
        var vue = new Vue({
            el: '#work',
            data: data4,
            methods: {
                submit: function () {
                    var url = this.uri;
                    axios.post(url,this.user).then(function (response) {
                        //临时不写,前面写了controller后再编写
                    });
                }
            }
        });
    });
</script>
<div id="work">
    <h3>登录</h3>
    用户名:<input type="text" v-model="user.name"><br>
    明码:<input type="password" v-model="user.password"><br>
    <button type="button" @click="submit">登录</button>
</div>
</body>
</html>

第二局部:进行Shiro相干的配置

1、导入Shiro的Pom依赖(文上)

2、自定义一个 Realm 的类

用来编写一些认证与受权的逻辑

//自定义Realm
public class UserRealm extends AuthorizingRealm {
    
    //执行受权逻辑
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //执行机会:在用户进行登录,认证身份时执行
        System.out.println("====>>>>执行了受权逻辑PrincipalCollection");
        return null;
    }

    //执行认证逻辑
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //执行机会:在用户登录后,每拜访一次那些须要权限能力拜访的页面时执行
        System.out.println("====>>>>执行了认证逻辑AuthenticationToken");
        //临时为空,前面依据业务逻辑编写认证代码
        return null;
    }
}

3、编写Shiro配置类,config包下


@Configuration
public class ShiroConfig {

    //创立ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置平安管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
        return shiroFilterFactoryBean;
    }

    //创立DefaultWebSecurityManager
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("getUserRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联Realm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

     //创立Hash凭证匹配器
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        //md5默认就是加密1次
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        hashedCredentialsMatcher.setHashIterations(1);
        return hashedCredentialsMatcher;
    }
    
    //创立Realm对象,须要自定义
    @Bean
    public UserRealm getUserRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher hashedCredentialsMatcher){
        UserRealm userRealm = new UserRealm();
        //关联凭证匹配器!!!
        userRealm.setCredentialsMatcher(hashedCredentialsMatcher);
        return userRealm;
    }
}

第三局部:编写业务逻辑

注册:

1、编写注册的Controller

@RestController
public class ShiroController {

    @Autowired
    UserMapper userMapper;

    @PostMapping("/submitregister")
    public Object submitRegister(@RequestBody User user){
        String name = user.getName();
        String password = user.getPassword();
        //依据name从数据库中查问对应的user
        if (null!=userMapper.queryUserByName(name)){
            return "用户名曾经被应用,请从新输出";
        }
        //通过随机形式创立盐,并且加密算法采纳md5
        String salt = new SecureRandomNumberGenerator().nextBytes().toString();
        //加密次数,默认加密1次
        int times = 1;
        String algorithmName = "md5";
        String encodePassword = new SimpleHash(algorithmName,password,salt,times).toString();
        //盐如果失落了,无奈验证明码是否正确,因而会增加到数据库保留
        user.setSalt(salt);
        user.setPassword(encodePassword);
        //增加到数据库
        userMapper.addUser(user);
        return 0;
    }
}

2、register.html批改

<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>注册</title>
    <script src="js/vue/2.5.16/vue.min.js"></script>
    <script src="js/axios/0.17.1/axios.min.js"></script>
<!--    <script src="js/jquery/2.0.0/jquery.min.js"></script>-->
    <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
</head>
<body>
<script>
    $(function () {
        var data4 = {
            uri: 'register',
            user: {
                name: '',
                password:''
            }
        };
        var vue = new Vue({
            el: '#work',
            data: data4,
            mounted: function(){

            },
            methods: {
                submit: function () {
                    var url = this.uri;
                    axios.post(url,this.user).then(function (response) {
                        var result = response.data;
                        //如果注册胜利,跳转到注册胜利页面
                        if (0===result){
                            location.href="registerSuccess";
                        }
                    });
                }
            }
        });
    });
</script>

<div id="work">
    <h3>注册</h3>
    用户名:<input type="text" v-model="user.name"><br>
    明码:<input type="password" v-model="user.password"><br>
    <button type="button" @click="submit">提交</button>
</div>
</body>
</html>

3、创立registerSuccess.html注册胜利页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册胜利页面</title>
</head>
<body>
<h1>祝贺您,注册胜利!</h1>
</body>
</html>

4、PageController中增加跳转

@GetMapping("/registerSuccess")
public String registerSuccess(){
    return "registerSuccess";
}

5、启动SpringBoot我的项目

拜访http://localhost:8080/shiro/register

输出用户名和明码,点击提交进行注册


发现页面跳转到注册胜利页面,查看数据库user表

加密注册胜利!

登录认证:

1、编写Realm里的认证逻辑

@Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //将AuthenticationToken转成UsernamePasswordToken
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        //获取账号名
        String username = token.getUsername();
        //依据name获取用户对象从而拿到明码和盐值
        User user = userMapper.queryUserByName(username);
        //获取用户明码(数据库中加密后的),须要通过user对象来拿
        String passwordInDB = user.getPassword();
        //拿到盐
        String salt = user.getSalt();
        //认证信息里寄存账号密码,getName()是以后Realm的继承办法,通常返回以后类名:UserRealm
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, passwordInDB, ByteSource.Util.bytes(salt), getName());
        return simpleAuthenticationInfo;
    }

2、编写登录的Controller,在ShiroController中

@PostMapping("/submitlogin")
    public Object submitLogin(@RequestBody User userParam){
        String name = userParam.getName();
        String password = userParam.getPassword();
        //应用Shiro形式获取以后用户
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(name, password);
        //执行登录,与Realm中查问的数据库中用户的信息进行比拟
        try {
            subject.login(token);
            //依据用户名
            User user = userMapper.queryUserByName(name);
            subject.getSession().setAttribute("user",user);
            return 0;
        } catch (AuthenticationException e) {
            return "账号或明码谬误";
        }
    }

3、login.html批改

<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>登录</title>
    <script src="js/vue/2.5.16/vue.min.js"></script>
    <script src="js/axios/0.17.1/axios.min.js"></script>
    <!--    <script src="js/jquery/2.0.0/jquery.min.js"></script>-->
    <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
</head>
<body>
<script>
    $(function () {
        var data4 = {
            uri: 'submitlogin',
            user: {
                name: '',
                password:''
            }
        };
        var vue = new Vue({
            el: '#work',
            data: data4,
            methods: {
                submit: function () {
                    var url = this.uri;
                    axios.post(url,this.user).then(function (response) {
                        var result = response.data;
                        //如果返回0,阐明认证胜利,即跳转到登陆胜利页面
                        if (0===result){
                            location.href="loginSuccess";
                        }
                    });
                }
            }
        });
    });
</script>
<div id="work">
    <h3>登录</h3>
    用户名:<input type="text" v-model="user.name"><br>
    明码:<input type="password" v-model="user.password"><br>
    <button type="button" @click="submit">登录</button>
</div>
</body>
</html>

4、创立loginSuccess.html登录胜利页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登录胜利页面</title>
</head>
<body>
<h1>祝贺您登录胜利!</h1>
<span>以后用户是:
<a th:text="${session.user.name}"></a>
</span>
</body>
</html>

5、PageController中增加跳转

@GetMapping("/loginSuccess")
public String loginSuccess(){
    return "loginSuccess";
}

6、启动SpringBoot我的项目

拜访http://localhost:8080/shiro/login

输出用户名明码,点击登录

发现页面跳转到登录胜利页面,并显示出了以后登录用户:纸飞机

页面登录拦挡:

1、在templates目录下新建一个user目录,编写两个页面add.html、update.html

<body>
<h1>add</h1>
</body>
<body>
<h1>update</h1>
</body>

2、编写对应跳转到两个页面的Controller

@GetMapping("/user/add")
public String toAdd(){
    return "user/add";
}

@GetMapping("/user/update")
public String toUpdate(){
    return "user/update";
}

3、在templates目录下创立一个首页index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
    <a href="login">点击进入登录页面</a>
    <br>
    <br>
    <a th:href="@{/user/add}">add</a> | 
    <a th:href="@{/user/update}">update</a>
</body>
</html>

4、跳转到首页index.html的Controller

@GetMapping({"/","/index"})
public String toIndex(){
    return "index";
}

5、运行测试页面跳转是否OK(能够间接进入add或update页面)

6、筹备编写Shiro的内置过滤器(设置过滤规定)

    //创立ShiroFilterFactoryBean
    @Bean(name ="shiroFilterFactoryBean")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置平安管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
        //批改到要跳转的login页面;
        shiroFilterFactoryBean.setLoginUrl("/login");
        /*
         * 罕用的有如下过滤器:
         * anon: 无需认证就能够拜访
         * authc: 必须认证才能够拜访
         * user: 如果应用了记住我性能就能够间接拜访
         * perms: 领有某个资源权限才能够拜访
         * role: 领有某个角色权限才能够拜访
         */
        //留神此处应用的是LinkedHashMap,是有程序的,shiro会按从上到下的程序匹配验证,匹配了就不再持续验证
        Map<String, String> filterMap = new LinkedHashMap<>();
        filterMap.put("/user/add","authc");
        filterMap.put("/user/update","authc");
        //能够间接使通配符
        //filterMap.put("/user/*","authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        return shiroFilterFactoryBean;
    }

7、启动测试拜访,发现点击add|update链接无奈进入,曾经被拦挡,会主动给咱们跳转到login.jsp页面。咱们须要自定义跳转页面。

8、在ShiroFilterFactoryBean办法中增加shiroFilterFactoryBean.setLoginUrl

//批改到要跳转的login页面;
//后面咱们曾经创立好了login.html页面和跳转的Controller
shiroFilterFactoryBean.setLoginUrl("/login");

9、再次启动测试,发现点击add|update链接会间接跳转到login.html登录页面让你进行登录

退出:

1、在登录胜利页面loginSuccess增加退出的链接

<a href="logout">退出</a>

2、编写退出的Controller

//退出登录,跳转到首页
@GetMapping("/logout")
public String loginOut() {
    Subject subject = SecurityUtils.getSubject();
    if(subject.isAuthenticated())
        subject.logout();//会主动让session生效
    return "redirect:index";
}

受权:

简略Shiro的过滤器拦挡申请

1、在登录胜利页面loginSuccess增加跳转到首页index.html的链接

<a href="index">点击跳转到首页</a>

2、在ShiroFilterFactoryBean中增加

//受权拦挡,须要放在认证的下面。某个用户有add的权限才能够拜访。
filterMap.put("/user/add","perms[user:add]");

3、启动测试,登录拜访add,发现呈现401谬误,Unauthorized未受权

当咱们实现权限拦挡后,shiro会主动跳转到未受权的页面,但咱们没有这个页面,所以401

留神:ShiroFilterFactoryBean中实现受权配置后,此处拜访add必须要先登录,未登录的状况下,受权的perms参数也会间接跳转到login,要求你先登录。

4、配置一个未受权的提醒的页面,减少一个controller提醒。而后在ShiroFilterFactoryBean中增加配置

@RequestMapping("/noauth")
@ResponseBody
public String noAuth(){
    return "未经受权不能拜访此页面";
}
shiroFilterFactoryBean.setUnauthorizedUrl("/noauth"); 

5、再次启动拜访测试。拦挡胜利。

6、ShiroFilterFactoryBean中残缺代码

    //创立ShiroFilterFactoryBean
    @Bean(name ="shiroFilterFactoryBean")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置平安管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);

        /*
         * 罕用的有如下过滤器:
         * anon: 无需认证就能够拜访
         * authc: 必须认证才能够拜访
         * user: 如果应用了记住我性能就能够间接拜访
         * perms: 领有某个资源权限才能够拜访
         * role: 领有某个角色权限才能够拜访
         */
        //留神此处应用的是LinkedHashMap,是有程序的,shiro会按从上到下的程序匹配验证,匹配了就不再持续验证
        //所以下面的url要刻薄,宽松的url要放在上面,尤其是"/**"要放到最上面,如果放后面的话其后的验证规定就没作用了。
        Map<String, String> filterMap = new LinkedHashMap<>();
        //受权拦挡,须要放在认证的下面。某个用户有add的权限才能够拜访。
        //受权,失常状况下,没有受权会跳转到未受权页面
        filterMap.put("/user/update","perms[user:update]");
        filterMap.put("/user/add","perms[user:add]");

        //认证
//        filterMap.put("/user/add","authc");
//        filterMap.put("/user/update","authc");

        //批改到要跳转的login页面;
        shiroFilterFactoryBean.setLoginUrl("/login");
        //跳转到未受权的申请页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        return shiroFilterFactoryBean;
    }

而如何给某个用户具体受权,进行权限的操作等。本文不做粗疏的解说,因为这须要联合我的项目中具体的业务逻辑来进行编写,并且须要进行数据库权限表等一系列的设计操作等,相较简单。

八、应用Shiro的坑

在文章集成SpringBoot局部,说了Shiro的导入Pom依赖的两种形式。其中第二种可能会报错。

报错内容一:

可能会呈现启动时无奈找到shiroFilterFactoryBean的bean

***************************
APPLICATION FAILED TO START
***************************

Description:

Method filterShiroFilterRegistrationBean in org.apache.shiro.spring.config.web.autoconfigure.ShiroWebFilterConfiguration required a bean named 'shiroFilterFactoryBean' that could not be found.


Action:

Consider defining a bean named 'shiroFilterFactoryBean' in your configuration.

解决:如果在Shiro配置类中的ShiroFilterFactoryBean的办法名没有命名为shiroFilterFactoryBean,须要手动在@Bean上增加上名字,否则无奈辨认注入。如下

@Bean(name = "shiroFilterFactoryBean")

报错内容二:

我的项目无奈启动,报没有authorizer的bean的谬误: No bean named 'authorizer' available

解决:本人在配置类中配置

@Configuration
public class ShiroConfig {

@Bean
public Authorizer authorizer(){
    return new ModularRealmAuthorizer();
    }
}

具体参考这篇文章:https://www.cnblogs.com/insan…

九、总结

本文只是Shiro的入门学习,适宜于老手初探Shiro,有个大抵的理解与利用。

次要讲了一些Shiro的基础知识,以及集成SpringBoot简略利用的流程和配置,而对于Shiro的一些源码等内容并没有进行一个剖析和解说,可能会在之后的进阶文章中补充。

学习倡议:

1、Shiro的一些配置类和简略业务逻辑代码是死的,大可不必花很多精力去死记那些单词又长的代码,在初学之后应总结成本人的环境模板,用时即取即可,多将精力放在Shiro的整个认证受权的运行过程上,理解是怎么进行的。

2、Shiro可定制化水平很高,在很多波及到权限的我的项目中都有使用,可自行去码云或GitHub上寻找优质的(star数较高)的开源我的项目(如权限管理系统)来进行学习,看理论的我的项目中Shiro的利用。

3、本文文中波及的代码。需要的话自行下载:链接:https://pan.baidu.com/s/1xicF…
提取码:quo0


参考:

1、http://shiro.apache.org/index… Shiro官网

2、https://www.bilibili.com/vide… 遇见狂神说,【狂神说Java】SpringBoot整合Shiro框架

3、https://zhuanlan.zhihu.com/p/… 我没有三颗心脏,Shiro平安框架【疾速入门】就这一篇!公众号wmyskxz

4、https://juejin.im/post/684490… 码农小胖哥,Spring Security 实战干货: RBAC权限管制概念的了解

5、http://www.woshipm.com/pd/115… 珣玗琪,RBAC模型:基于用户-角色-权限管制的一些思考

6、https://how2j.cn/k/shiro/shir… How2j Shiro系列教材


谢谢您看完这篇技术文章

如果能对您有所帮忙

那将是一件很美妙的事件

放弃好奇心的一生学习也是极棒的事

愿世界简略又多彩

转载请注明出处

​ ——纸飞机

本文由乐趣区整理发布,转载请注明出处,谢谢。

You may also like...

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据