Shiro是Apache基金会下的一个开源安全框架,提供了身份验证、授权、密码学和会话管理等功能,Shiro框架不仅直观易用,而且也能提供健壮的安全性,另外一点值得说的是Shiro的前身是一个始于2004的开源项目JSecurity,该项目于2008年加入Apache,并于2010年成为Apache的顶级项目。
OK,以上是关于Shiro的一点简单介绍,实际上,我在之前有一篇关于权限控制的博客在Spring Boot中使用Spring Security实现权限控制,Shiro的功能没有Spring Security强大,但是对于一般的项目而言也是够用了,而且用起来要更简单一些。OK,关于这两个孰优孰劣,我们不去争论,我们这里主要来看Shiro的使用。

三个核心

Subject

在Shiro中,任意一个和程序交互的主体都是一个Subject,Subject包括但是不限于用户,所有的Subject都绑定到SecurityManager上 ,所有交由Subject处理的操作最终都会委托给SecurityManager。

SecurityManager

SecurityManager单从字面上我们可以理解为一个安全管理器,所有的Subject都由SecurityManager来管理,它是Shiro的核心,SecurityManager有点类似于Spring框架中的DispatcherServlet。

Realm

Shiro在运行的过程中,从Realm中获取安全数据,比如用户的权限、角色等,每当SecurityManager要验证用户身份的时候,那么他就从Realm中获取相应的数据进行比对,这个有点类似于DAO,由它提供数据源。

通过上面的简单介绍,我们可以大致梳理一下一个Shiro应用的流程:
代码通过Subject来进行认证和授权等操作,而Subject又将这个操作委托给SecurityManager,我们将要验证的数据源注入到Realm中,SecurityManager在Realm中查询数据进行验证。

登录操作

OK,通过以上的介绍,相信小伙伴对Shiro已经有了一个基本的认识,接下来我们来看看几个基本的操作。

创建工程并添加依赖

我这里使用IntelliJ IDEA作为开发工具,我们先来创建一个基本的Maven工程,然后添加如下依赖:

<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>

单元测试框架、日志框架和一个shiro-core三个东西就足够了。

通过ini问价创建数据源

我们这里先通过ini文件创建一个简单的数据源,注意文件的位置:

OK,shiro.ini文件的内容如下:

[users]
zuolengchan=456

注意上面是users,有s,users表示对用户、密码、角色等的配置。我们这里提供了一个用户名为zuolengchan,密码为456的用户。

测试

OK,接下来,我们通过一个简单的单元测试来看看怎么登录,如下:

@Test
public void test1() {
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zuolengchan", "456");
try {
subject.login(token);
System.out.println("登录成功");
} catch (AuthenticationException e) {
//登录失败
System.out.println("登录失败");
e.printStackTrace();
}
subject.logout();
}

关于以上测试文件,我说以下几点:

1.首先我们需要获取SecurityManager工厂,获取的时候通过ini配置文件来初始化。
2.通过SecurityManager工厂获取到SecurityManager的实例,并将该实例绑定给SecurityUtils,设置给SecurityUtils是一个全局设置,设置一次即可。
3.从SecurityUtils中获取到一个Subject实例
4.通过UsernamePasswordToken对象创建用户名密码身份验证Token
5.调用Subject中的login方法执行登录操作,Subject会将这个登录操作自动委托给SecurityManager去执行。
6.在登录过程中,如果没有抛异常,说明登录成功,如果抛异常,说明登录失败
7.调用logout方法我们可以退出登录,退出登录的操作也是委托给SecurityManager去执行。

OK,我们在登录的过程中,输入正确的用户名和密码就能成功登录。

自定义Realm

OK,上个案例中,我们在ini文件中预设了数据源,当然我们也可以自定义Realm,前面我们也说过Realm相当于是我们的数据源,我们可以在Realm中来进行数据匹配,自定义Realm需要我们实现Realm接口,该接口中有三个方法需要我们实现:

1.getName()方法用来返回该Realm的唯一名字。
2.supports(AuthenticationToken token)方法用来验证传入的Token是否被支持,因为我们验证方式不仅仅Username和Password这种方式,还有很多其他方式。
3.getAuthenticationInfo(AuthenticationToken token)这个方法用来执行验证操作,并且将验证结果返回。

OK,有了上面的介绍,接下来我们来实现一个Realm。

定义Realm

public class MyRealm1 implements Realm {

    public String getName() {
return "myrealm1";
} public boolean supports(AuthenticationToken authenticationToken) {
return authenticationToken instanceof UsernamePasswordToken;
} public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
String password = new String(((char[]) token.getCredentials()));
if (!"wang".equals(username)) {
//用户名错误
throw new UnknownAccountException();
}
if (!"123".equals(password)) {
//密码错误
throw new IncorrectCredentialsException();
}
return new SimpleAuthenticationInfo(username, password, getName());
}
}

getName()方法返回了当前Realm的名字,supports方法判断是否是Username+Password的验证,getAuthenticationInfo方法中进行验证,首先获取到传入的用户名和密码,然后进行比对,如果不存在用户名就抛出UnknownAccountException异常,如果密码错误则抛出IncorrectCredentialsException异常,最后将结果返回。

定义ini文件

这里的ini文件就比较简单了,如下:

myRealm1=org.sang.MyRealm1
securityManager.realms=$myRealm1

首先定义myRealm1,值为我们自定义Realm的全路径,然后设置securityManager的realms属性即可,OK,做完这些我们就可以测试啦。

测试

@Test
public void test2() {
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhang", "456");
try {
subject.login(token);
System.out.println("登录成功");
} catch (AuthenticationException e) {
//登录失败
System.out.println("登录失败");
e.printStackTrace();
}
subject.logout();
}

如果我们在登录的时候输入正确的用户名和密码就能够成功登录,如果有任意一个输错,则会抛出相应的异常。

定义多个Realm

OK,以上是我们自定义一个Realm,事实上我们可以自定义多个,如下:

public class MyRealm2 implements Realm {

    public String getName() {
return "myrealm2";
} public boolean supports(AuthenticationToken authenticationToken) {
return authenticationToken instanceof UsernamePasswordToken;
} public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
String password = new String(((char[]) token.getCredentials()));
if (!"zhang".equals(username)) {
//用户名错误
throw new UnknownAccountException();
}
if (!"456".equals(password)) {
//密码错误
throw new IncorrectCredentialsException();
}
return new SimpleAuthenticationInfo(username, password, getName());
}
}

自定义多个Realm之后需要我们在ini文件中配置多个,如下:

myRealm1=org.sang.MyRealm1
myRealm2=org.sang.MyRealm2
securityManager.realms=$myRealm1,$myRealm2

这里如果我们不写最后 一行也是可以的,Shiro会自动将前面定义的myRealm1,myRealm2设置给SecurityManager的realms属性。
OK,这里的测试方式和上文一致,不再赘述。

JdbcRealm

定义数据库

上面的数据源都是我们提前定义好,写死的,当然我们也可以将数据定义在数据库中,比如,我创建如下三张表,并预设一条数据:

drop database if exists shiro1;
create database shiro1 default character set=utf8 collate utf8_bin;
use shiro1;
create table users(
id bigint auto_increment,
username varchar(100),
password varchar(100),
password_salt varchar(100),
constraint pk_users primary key(id)
)charset=utf8;
create unique index idx_users_username on users(username);
create table user_roles(
id bigint auto_increment,
username varchar(100),
role_name varchar(100),
constraint pk_user_roles primary key(id)
)charset=utf8;
create unique index idx_user_roles on user_roles(username,role_name);
create table roles_permissions(
id bigint auto_increment,
role_name varchar(100),
permission varchar(100),
constraint pk_roles_permissions primary key(id)
)charset=utf8;
create unique index idx_roles_permissions on roles_permissions(role_name,permission);
insert into users(username,password) values('wang','111');

添加相关的依赖

然后在Maven中添加数据库驱动和连接池:

    <dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.29</version>
</dependency>

添加ini文件

在ini文件中配置JdbcRealm,如下:

jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
dataSource=com.alibaba.druid.pool.DruidDataSource
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://localhost:3306/shiro1?useUnicode=true&characterEncoding=UTF-8
dataSource.username=root
dataSource.password=123
jdbcRealm.dataSource=$dataSource
securityManager.realm=$jdbcRealm

jdbcRealm定义了我们要用的JdbcRealm,在最后将之设置给SecurityManager的realm属性,jdbcRealm中还有dataSource,就是我们自定义的数据源,其他的都是数据库连接的东西,我就不再赘述。

测试

@Test
public void test3() {
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-jdbc-realm.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("wang", "111");
try {
subject.login(token);
System.out.println("登录成功");
} catch (AuthenticationException e) {
//登录失败
System.out.println("登录失败");
e.printStackTrace();
}
subject.logout();
}

测试方式还是和前文一致。不再啰嗦。

自定义验证策略

Shiro中有三种不同的验证策略,如下:

1.FirstSuccessfulStrategy:表示只要有一个Realm验证成功即可,然后返回第一个Realm身份验证成功的认证信息,其他的忽略。
2.AtLeastOneSuccessfulStrategy:表示只要有一个Realm验证成功即可,但是它会将所有的成功验证成功的信息返回。
3.AllSuccessfulStrategy:表示所有的Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息。

Shiro默认使用了第二种策略。

OK,假设我现在有三个Realm,分别如下:

public class MyRealm3 implements Realm {

    public String getName() {
return "myrealm3";
} public boolean supports(AuthenticationToken authenticationToken) {
return authenticationToken instanceof UsernamePasswordToken;
} public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
String password = new String(((char[]) token.getCredentials()));
if (!"zhang".equals(username)) {
//用户名错误
throw new UnknownAccountException();
}
if (!"123".equals(password)) {
//密码错误
throw new IncorrectCredentialsException();
}
return new SimpleAuthenticationInfo(username, password, getName());
} }
public class MyRealm4 implements Realm { public String getName() {
return "myrealm4";
} public boolean supports(AuthenticationToken authenticationToken) {
return authenticationToken instanceof UsernamePasswordToken;
} public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
String password = new String(((char[]) token.getCredentials()));
if (!"wang".equals(username)) {
//用户名错误
throw new UnknownAccountException();
}
if (!"123".equals(password)) {
//密码错误
throw new IncorrectCredentialsException();
}
return new SimpleAuthenticationInfo(username, password, getName());
} }
public class MyRealm5 implements Realm { public String getName() {
return "myrealm5";
} public boolean supports(AuthenticationToken authenticationToken) {
return authenticationToken instanceof UsernamePasswordToken;
} public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
String password = new String(((char[]) token.getCredentials()));
if (!"zhang".equals(username)) {
//用户名错误
throw new UnknownAccountException();
}
if (!"123".equals(password)) {
//密码错误
throw new IncorrectCredentialsException();
}
return new SimpleAuthenticationInfo("zhang@163.com", password, getName());
} }

然后在ini文件中配置验证策略:

#指定securityManager的authenticator实现
authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator
securityManager.authenticator=$authenticator
#验证策略
allSuccessfulStrategy= org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy
securityManager.authenticator.authenticationStrategy=$allSuccessfulStrategy myRealm3=org.sang.MyRealm3
myRealm4=org.sang.MyRealm4
myRealm5=org.sang.MyRealm5
securityManager.realms=$myRealm3,$myRealm5,$myRealm4

这个表示我一会验证的时候,验证的条件只要有一个满足即可验证成功,并且在验证成功后系统会返回所有的验证信息给我。
当然,我也可以给allSuccessfulStrategy设置另外两种值,如下:

allSuccessfulStrategy= org.apache.shiro.authc.pam.AllSuccessfulStrategy
allSuccessfulStrategy= org.apache.shiro.authc.pam.FirstSuccessfulStrategy

OK,我们来看看测试代码:

@Test
public void test4() {
login("classpath:shiro-authenticator-all-success.ini");
Subject subject = SecurityUtils.getSubject();
PrincipalCollection principals = subject.getPrincipals();
Iterator iterator = principals.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
} private void login(String configFile) {
Factory<SecurityManager> factory = new IniSecurityManagerFactory(configFile);
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("wang", "123");
try {
subject.login(token);
System.out.println("登录成功");
} catch (AuthenticationException e) {
System.out.println("登录失败");
e.printStackTrace();
}
}

登录成功之后将所有的认证信息打印出来。

OK,以上就是Shiro的一个简单应用。

本文案例地址:
https://github.com/lenve/Shiro

更多JavaEE资料请移步这里:
https://github.com/lenve/JavaEETest

参考资料:
张开涛大神的《跟我学Shiro》,原文连接http://jinnianshilongnian.iteye.com/blog/2018398

关注公众号【江南一点雨】,专注于 Spring Boot+微服务以及前后端分离等全栈技术,定期视频教程分享,关注后回复 Java ,领取松哥为你精心准备的 Java 干货!

初识Shiro的更多相关文章

  1. 第一章 初识shiro

    shiro学习教程来自开涛大神的博客:http://jinnianshilongnian.iteye.com/blog/2018936 第一章 初识shiro 简单了解shiro主要记住三张图即可. ...

  2. 【shiro】shiro学习笔记1 - 初识shiro

    [TOC] 认证流程 st=>start: Start e=>end: End op1=>operation: 构造SecurityManager环境 op2=>operati ...

  3. shiro学习总结(一)----初识shiro

    本系列内容大多总结自官网和张开涛的<跟我学Shiro> 一.shiro简介 1.1.shiro有什么用? shiro是一个功能强大使用简单的java安全框架,主要提供了五大功能: 1.认证 ...

  4. Shiro:初识Shiro及简单尝试

    Shiro 一.什么是Shiro Apache Shiro是Java的一个安全(权限)框架 作用:认证.授权.加密.会话管理.与web集成.缓存等 下载地址:http://shiro.apache.o ...

  5. Shiro第四篇【Shiro与Spring整合、快速入门、Shiro过滤器、登陆认证】

    Spring与Shiro整合 导入jar包 shiro-web的jar. shiro-spring的jar shiro-code的jar 快速入门 shiro也通过filter进行拦截.filter拦 ...

  6. Shiro【授权、整合Spirng、Shiro过滤器】

    前言 本文主要讲解的知识点有以下: Shiro授权的方式简单介绍 与Spring整合 初始Shiro过滤器 一.Shiro授权 上一篇我们已经讲解了Shiro的认证相关的知识了,现在我们来弄Shiro ...

  7. Shiro中的授权问题(二)

    上篇博客(Shiro中的授权问题 )我们介绍了Shiro中最最基本的授权问题,以及常见的权限字符的匹配问题.但是这里边还有许多细节需要我们继续介绍,本节我们就来看看Shiro中授权的一些细节问题. 验 ...

  8. Shiro中的授权问题

    在初识Shiro一文中,我们对Shiro的基本使用已经做了简单的介绍,不懂的小伙伴们可以先阅读上文,今天我们就来看看Shiro中的授权问题. Shiro中的授权,大体上可以分为两大类,一类是隐式角色, ...

  9. (八) SpringBoot起飞之路-整合Shiro详细教程(MyBatis、Thymeleaf)

    兴趣的朋友可以去了解一下前几篇,你的赞就是对我最大的支持,感谢大家! (一) SpringBoot起飞之路-HelloWorld (二) SpringBoot起飞之路-入门原理分析 (三) Sprin ...

随机推荐

  1. java编写之jpg图片与base64编码之间的转换

    /** * @author zyq * 将网络图片进行Base64位编码 * @param imgUrl * */ public static String encodeWebImageToBase6 ...

  2. 理解WindowManagerService

    --摘自<Android进阶解密> *WMS的职责* 1)窗口管理 WMS负责窗口的启动.添加和删除,另外窗口的大小和层级也是由WMS进行管理的 2)窗口动画 WMS的动画子系统Windo ...

  3. Python ftplib模块

    Python ftplib模块 官方文档:https://docs.python.org/3/library/ftplib.html?highlight=ftplib#module-ftplib 实例 ...

  4. excel导出使用get请求参数过长问题

    遇到的问题: excel导出功能时,使用的是window.location.href=url也就是get请求.当传入参数过长的时候就报了414,地址过长的错误. 解决思路: 将get请求换为post请 ...

  5. js中常用的正则表达式总结

    去除所有空格: str = str.replace(/\s+/g,""); 去除两头空格: str = str.replace(/^\s+|\s+$/g,"") ...

  6. thrift小试--C++

    [转自]http://blog.csdn.net/poechant/article/details/6618284# Thrift可以实现C++.Java.Python等多种语言的自动生成,此处以C+ ...

  7. 传入两坐标点,利用div+css画线

    上样式生成函数代码 lineStyle (x1, y1, x2, y2, lineWidth = 4, color = 'black') { let rectX = x1 < x2 ? x1 : ...

  8. pip离线安装依赖包

    pip安装离线本地包 导出本地已有的依赖包 pip freeze > requirements.txt 将依赖包下载到本地 # 下载到当前目录,指定pip源 pip download -r re ...

  9. wait event & wake up

    在linux驱动中一个常用的场景, 驱动需要等待中断的响应, 才得以执行后续的代码,达到一个原子操作的目的 /* 静态申请队列 */ static DECLARE_WAIT_QUEUE_HEAD(s_ ...

  10. h5唤起APP并检查是否成功

    // 检查app是否打开 function checkOpen(cb) { const clickTime = +(new Date()); function check(elsTime) { if ...