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. 【spring】-- springboot配置全局异常处理器

    一.为什么要使用全局异常处理器? 什么是全局异常处理器? 就是把错误异常统一处理的方法. 应用场景: 1.当你使用jsr303参数校验器,如果参数校验不通过会抛异常,而且无法使用try-catch语句 ...

  2. 操作系统PV编程题目总结一

    1.今有一个文件F供进程共享,现把这些进程分为A.B两组,规定同组的进程可以同时读文件F:但当有A组(或B组)的进程在读文件F时就不允许B组(或A组)的进程读文件F.试用P.V操作(记录型信号量)来进 ...

  3. CentOS7 VMware-Tools安装与共享文件夹设置

    一. VMware-Tools安装 1.加载VMware Tools的光驱:点击"虚拟机"->"安装VMware Tools".这里,由于我已经安装了,所 ...

  4. 4.20 Linux01

    2019-4-20 21:04:14 day102linux 开始认真学习Linux ,因为服务器部署还是得会Linux 开始整理一下笔记 等把Linux全部学完后 然后写个文章整理一下! Linux ...

  5. 818C.soft thief

    Yet another round on DecoForces is coming! Grandpa Maks wanted to participate in it but someone has ...

  6. JS 多选文件或者选择文件夹

    <%--文件多选--%> <input type="file" name="file" id="file" multipl ...

  7. SpringMVC的配置和使用

    SpringMVC的配置和使用 什么是SpringMVC? SpringMVC是Spring家族的一员,Spring是将现在开发中流行的组件进行组合而成的一个框架!它用在基于MVC的表现层开发,类似于 ...

  8. 支付宝2018年最新SDK对接验签的问题

    下单加签 AopUtils.SignAopRequest(sortedTxtParams,应用私钥, "UTF-8", false, "RSA2"); 异步回调 ...

  9. ubuntu Nvidia driver install

    在图形界面中,有软件和更新,可以使用附加驱动来更新 最上面的驱动是最新版本,英伟达目前Linux最新的版本是375.39 后面的括号,专有意思是代表英伟达自家的驱动,不开源 选择好之后点击应用更改 关 ...

  10. Java作业 十一(2017-11-13)

    /*关键字*/ package com.baidu.www; abstract class A { private String name; public A(String name) { this. ...