shiro入门

2021-01-23

第一章:shiro入门

权限管理

权限管理介绍

基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理能实现对用户访问系统的控制,按照安全规则或者安全策略限制用户操作,只允许用户访问被授权的资源。

权限管理包括用户身份认证和授权两部分,简称认证授权。

身份认证

身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。

img

img

上边的流程图中需 要理解以下关键对象:

Subject:主体(当前登录用户)

​ 访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体;

Principal:身份信息(username)

是主体(subject)进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)。

credential:凭证信息(password)

是只有主体自己知道的安全信息,如密码、证书等。

shiro概述

什么是shiro

Apache Shiro 是Java 的一个安全框架。Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE 环境,也可以用在JavaEE 环境。Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与Web 集成、缓存等。

为什么要学shiro

​ 既然shiro将安全认证相关的功能抽取出来组成一个框架,使用shiro就可以非常快速的完成认证、授权等功能的开发,降低系统成本。

​ shiro使用广泛,shiro可以运行在web应用,非web应用,集群分布式应用中越来越多的用户开始使用shiro。

​ java领域中spring security(原名Acegi)也是一个开源的权限管理框架,但是spring security依赖spring运行,而shiro就相对独立,最主要是因为shiro使用简单、灵活,所以现在越来越多的用户选择shiro。

获取shiro

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

帮助文档:https://shiro.apache.org/reference.html

shiro基本功能

image-20191203112031777

Authentication

[ɔːˌθentɪˈkeɪʃn]

身份认证/登录,验证用户是不是拥有相应的身份;

Authorization

授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;

Session Manager

会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
Web Support:Web 支持,可以非常容易的集成到Web 环境;
Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
Concurrency:shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
Testing:提供测试支持;
Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
Shiro 不会去维护用户、维护权限;这些需要我们自己去设计/提供;然后通过相应的接口注入给Shiro即可

shiro架构

shiro架构图

image-20191204153257793

subject

​ Subject即主体,外部应用与subject进行交互,subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。 Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权

SecurityManager

​ SecurityManager即安全管理器,对全部的subject进行安全管理,它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等,实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。

​ SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口。

Authenticator

​ Authenticator即认证器,对用户身份进行认证,Authenticator是一个接口,shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器。

Authorizer

​ Authorizer即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。

realm

​ Realm即领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。

​ 注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码

sessionManager

sessionManager即会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。

SessionDAO

SessionDAO即会话dao,是对session会话操作的一套接口,比如要将session存储到数据库,可以通过jdbc将会话存储到数据库。

CacheManager

CacheManager即缓存管理,将用户权限数据存储在缓存,这样可以提高性能。

Cryptography

Cryptography即密码管理,shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。

shiro身份验证

基本概念

身份验证

​ 即在应用中谁能证明他就是他本人。一般提供如他们的身份ID 一些标识信息来表明他就是他本人,如提供身份证,用户名/密码来证明。

​ 在 shiro 中,用户需要提供principals (身份)和credentials(证明)给shiro,从而应用能验证用户身份

principals

身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。

一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号。

credentials

证明/凭证,即只有主体知道的安全值,如密码/数字证书等。

最常见的principals和credentials组合就是用户名/密码了。

验证流程

image-20191204154631539

身份验证案例

需求

使用shiro实现用户登录及退出

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- shiro核心依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.2</version>
</dependency>
<!-- slf4j日志依赖开始 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.21</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.21</version>
<scope>compile</scope>
</dependency>
<!-- slf4j日志依赖结束 -->

eclispe中配置.ini打开方式

image-20200821103227860

shiro配置文件(shiro.ini)

在资源文件夹下创建shiro.ini配置文件(配置文件名称不固定)

1
2
3
[users]
admin=123456
test=123

在idea开发工具中,ini配置文件默认为记事本打开,可以使用以下方式安装插件

image-20191204163721914

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.bdqn.shiro;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;

public class HelloWorld {

public static void main(String[] args) {
//1.读取配置文件,初始化工厂对象
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//2.获取SecurityManager实例
SecurityManager manager = factory.getInstance();
//3.将SecurityManager绑定到工具类(环境,在spring中交给IOC)
SecurityUtils.setSecurityManager(manager);
//4.通过SecurityUtils得到当前登录的用户
Subject currentUser = SecurityUtils.getSubject();
//5.窗口登录令牌
UsernamePasswordToken token = new UsernamePasswordToken("test","123");
try {
//6.登录并传入令牌
currentUser.login(token);
System.out.println("身份信息验证成功!");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("身份信息验证失败!");
}
//7.退出
currentUser.logout();
}
}

eclipse小课堂:ctrl+T显示这个接口实现的类有哪些

image-20200821104729328

执行流程梳理

1、通过ini配置文件创建securityManager

2、调用subject.login方法主体提交认证,提交的token 3、securityManager进行认证,securityManager最终由ModularRealmAuthenticator进行认证。

4、ModularRealmAuthenticator调用IniRealm(给realm传入token) 去ini配置文件中查询用户信息

5、IniRealm根据输入的token(UsernamePasswordToken)从 shiro-first.ini查询用户信息,根据账号查询用户信息(账号和密码) 如果查询到用户信息,就给ModularRealmAuthenticator返回用户信息(账号和密码) 如果查询不到,就给ModularRealmAuthenticator返回null

6、ModularRealmAuthenticator接收IniRealm返回Authentication认证信息 如果返回的认证信息是null,ModularRealmAuthenticator抛出异常(org.apache.shiro.authc.UnknownAccountException) 如果返回的认证信息不是null(说明inirealm找到了用户),对IniRealm返回用户密码 (在ini文件中存在)和 token中的密码 进行对比,如果不一致抛出异常(org.apache.shiro.authc.IncorrectCredentialsException)

常见错误

错误1:用户名错误

image-20191204161141188

错误2:密码错误

image-20191204161308804

ini配置文件

shiro.ini文件概述

​ .ini 文件是Initialization File的缩写,即初始化文件,是windows的系统配置文件所采用的存储格式,统管windows的各项配置,一般用户就用windows提供的各项图形化管理界面就可实现相同的配置了。但在某些情况,还是要直接编辑ini才方便,一般只有很熟悉windows才能去直接编辑。

​ shiro使用时可以连接数据库,也可以不连接数据库,不连接这在shiro.ini配置文件中配置静态数据。

​ .ini 配置文件类似于 Java 中的 properties(key=value),不过提供了将 key/value 分类的特性, key 是每个部分不重复即可,而不是整个配置文件。

shiro.ini文件组成部分

shiro.ini配置文件共有[main],[users],[roles],[urls]共4部分组成

  1. [main] 用于定义全局变量
  2. [users] 用于定义用户名及密码
  3. [roles] 用于定义角色
  4. [urls] 用于定义访问url及拦截验证方式

[main]

1
2
3
4
5
[main]
#提供了对根对象 securityManager 及其依赖的配置
securityManager=org.apache.shiro.mgt.DefaultSecurityManager
jdbcRealm = xxxxx
securityManager.realms=$jdbcRealm

注意:引用变量名语法为:$变量名

[users]

1
2
3
4
5
6
#提供了对用户/密码及其角色的配置,用户名=密码,角色 1,角色 2 username=password,role1,role2
#定义用户信息
[users]
#用户名=密码
admin=123456
test=123

[roles]

1
2
3
4
5
#提供了角色及权限之间关系的配置,角色=权限 1,权限 2 role1=permission1,permission2
[roles]
role1=user:query,user:add,user:update,user:delete,user:export
role2=user:query,user:add
role3=user:query,user:export

[urls]

1
2
3
#用于 web,提供了对 web url 拦截相关的配置,url=拦截器[参数],拦截器
/index.html = anon
/admin/** = authc, roles[admin]

默认过滤器

Filter Name Class
anon org.apache.shiro.web.filter.authc.AnonymousFilter
authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
logout org.apache.shiro.web.filter.authc.LogoutFilter
noSessionCreation org.apache.shiro.web.filter.session.NoSessionCreationFilter
perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
port org.apache.shiro.web.filter.authz.PortFilter
rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
ssl org.apache.shiro.web.filter.authz.SslFilter
user org.apache.shiro.web.filter.authc.UserFilter

Realm域

realm域概述

​ Realm:域,Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色/权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource ,即安全数据源。如我们之前的ini配置方式 将使用org.apache.shiro.realm.text.IniRealm。

​ Shiro默认使用自带的IniRealm,IniRealm从ini配置文件中读取用户的信息,大部分情况下需要从系统的数据库中读取用户信息,所以需要自定义realm。

JDBCRealm

数据库

1
2
3
4
5
6
7
8
CREATE DATABASE  db_shiro;
USE db_shiro;
CREATE TABLE users(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(100),
PASSWORD VARCHAR(100)
);
INSERT INTO users (username,PASSWORD) VALUES('admin','123456'),('test','123');

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!-- shiro核心依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.2</version>
</dependency>
<!-- slf4j日志依赖开始 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.21</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.21</version>
<scope>compile</scope>
</dependency>
<!-- slf4j日志依赖结束 -->

<!-- 阿里巴巴数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
</dependency>

shiro.ini配置文件

在resources资源目录下新建jdbc_realm.ini配置文件,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[main]
#使用jdbcRealm
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
#alibaba数据源
dataSource=com.alibaba.druid.pool.DruidDataSource
#数据库驱动
dataSource.driverClassName=com.mysql.cj.jdbc.Driver
#连接字符串
dataSource.url=jdbc:mysql://localhost:3306/db_shiro?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
#数据库用户名
dataSource.username=root
#数据库密码
dataSource.password=root
#引用、注入数据源
jdbcRealm.dataSource=$dataSource
#引用realm
securityManager.realms=$jdbcRealm

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.bdqn.shiro;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;

public class JDBCRealmTest {

public static void main(String[] args) {
//1.读取配置文件,初始化工厂对象
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:jdbc_realm.ini");
//2.获取SecurityManager实例
SecurityManager manager = factory.getInstance();
//3.将SecurityManager绑定到工具类
SecurityUtils.setSecurityManager(manager);
//4.通过SecurityUtils得到当前登录的用户
Subject currentUser = SecurityUtils.getSubject();
//5.窗口登录令牌
UsernamePasswordToken token = new UsernamePasswordToken("test","123");
try {
//6.登录并传入令牌
currentUser.login(token);
System.out.println("身份信息验证成功!");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("身份信息验证失败!");
}
//7.退出
currentUser.logout();

}
}