初识Shiro
来源:互联网 发布:图文广告软件下载 编辑:程序博客网 时间:2024/06/06 00:52
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.MyRealm1securityManager.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.MyRealm1myRealm2=org.sang.MyRealm2securityManager.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.JdbcRealmdataSource=com.alibaba.druid.pool.DruidDataSourcedataSource.driverClassName=com.mysql.jdbc.DriverdataSource.url=jdbc:mysql://localhost:3306/shiro1?useUnicode=true&characterEncoding=UTF-8dataSource.username=rootdataSource.password=123jdbcRealm.dataSource=$dataSourcesecurityManager.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.ModularRealmAuthenticatorsecurityManager.authenticator=$authenticator#验证策略allSuccessfulStrategy= org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategysecurityManager.authenticator.authenticationStrategy=$allSuccessfulStrategymyRealm3=org.sang.MyRealm3myRealm4=org.sang.MyRealm4myRealm5=org.sang.MyRealm5securityManager.realms=$myRealm3,$myRealm5,$myRealm4
这个表示我一会验证的时候,验证的条件只要有一个满足即可验证成功,并且在验证成功后系统会返回所有的验证信息给我。
当然,我也可以给allSuccessfulStrategy设置另外两种值,如下:
allSuccessfulStrategy= org.apache.shiro.authc.pam.AllSuccessfulStrategyallSuccessfulStrategy= 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
- shiro初识
- 初识Shiro
- 初识Shiro
- 初识Shiro
- Shiro--初识Shiro
- 【shiro】shiro初识
- 【shiro】shiro学习笔记1-shiro初识
- 01-Shiro初识
- Shiro框架学习-初识
- Shiro初识HelloWorld
- 初识shiro(一)
- 【shiro】shiro 学习笔记4-初识shiro授权
- Apache Shiro安全框架初识
- shiro学习之路(1)---初识shiro(Hello Word)
- shiro
- shiro
- Shiro
- shiro
- 编程练习(第四周)
- OpenCV 应用边缘检测与霍夫线变换实现旋转角度检测
- JS小笔记
- webots自学笔记(一)软件界面和简单模型仿真
- JAVA中常见的三种打印异常的方式
- 初识Shiro
- 第四周:[LeetCode]332. Reconstruct Itinerary
- Python中文乱码问题
- C++/C++11中std::set用法汇总
- C语言enum枚举体的用法
- Sequence
- unity5.5版本制作UFO2D项目
- 大型软件系统中表结构设计的一点体会和经验
- 部分JavaScript ECMAscript 2016 新特性