Apache Shiro是什么?
Apache Shiro一个功能强大,使用简单的Java安全框架,它为开发人员提供一个直观而全面的认证,授权,加密及会话管理的解决方案。
实际上,Shiro的主要功能是管理应用程序中与安全相关的全部,同时尽可能支持多种实现方法。Shiro是建立在完善的接口驱动设计和面向对象原则之上的,支持各种自定义行为。Shiro提供的默认实现,使其能完成与其他安全框架同样的功能,这不也是我们一直努力想要得到的吗!
1.认证的概念
身份认证,即在应用中谁能证明他就是他本人。一般提供如他们的身份ID一些标识信息来表明他就是他本人,如提供身份证,用户名/密码来证明。
在shiro中,用户需要提供principals (身份)和credentials(证明)给shiro,从而应用能验证用户身份:
principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号。
credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。
最常见的principals和credentials组合就是用户名/密码了。接下来先通过一个入门程序进行一个基本的身份认证。
另外两个相关的概念是之前提到的Subject及Realm,分别是主体及验证主体的数据源。
2.Shiro认证流程
Shiro认证流程图如下:
3.Shiro认证入门程序
本入门程序是通过模仿用户的登录与退出来完成认证。我们将用户登录时输入的信息与数据库中的信息进行比较,只有二者信息符合时就说明用户认证通过了。
3.1
首先我们创建一个Maven WebApp项目
Pom.xml导入以下Jar包
在src包下创建名为shiro-first.ini文件,程序中我们没有连接到数据库,所以采用后缀为ini的文件来模拟数据库中的数据。
#对用户信息进行配置
[users]
#用户账号与密码
zhansan=111111
lisi=1212
Resources文件夹下创建日志配置文件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=TRACE
# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
3.2编写入门程序
public class AuthenticationTest
{
//用户的登录和退出
@Test
public void testLoginAndLogout()
{
//创建securityManager工厂
Factory factory=new IniSecurityManagerFactory(
"classpath:shiro-first.ini");
//创建SecurityManager
SecurityManager securityManager = factory.getInstance();
//将securityManager设置到当前的运行环境中
SecurityUtils.setSecurityManager(securityManager);
//丛SecurityUtils里边的到一个subject
Subject subject = SecurityUtils.getSubject();
// 在认证提交前准备token(令牌)
// 模拟用户输入的账号和密码,将来是由用户输入进去从页面传送过来
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan",
"111111");
try {
// 执行认证提交
subject.login(token);
} catch (AuthenticationException e) {
// 认证失败
e.printStackTrace();
}
// 是否认证通过
boolean isAuthenticated = subject.isAuthenticated();
System.out.println("是否认证通过:" + isAuthenticated);
// 退出操作
subject.logout();
}
}
运行程序,输出是否认证通过:true。因为该用户输入的信息和数据库中的信息匹配,所以认证成功。代码讲解:
首先通过new IniSecurityManagerFactory并指定一个ini配置文件来创建一个SecurityManager工厂。
接着获取SecurityManager并绑定到SecurityUtils,这是一个全局设置,设置一次即可。
通过SecurityUtils得到Subject,其会自动绑定到当前线程;如果在web环境在请求结束时需要解除绑定;然后获取身份验证的Token(即用户输入的信息),如用户名/密码。
调用subject.login方法进行登录,其会自动委托给SecurityManager.login方法进行登录。
如果身份验证失败请捕获AuthenticationException或其子类异常,常见的如 :DisabledAccountException(禁用的帐号)、LockedAccountException(锁定的帐号)、UnknownAccountException(错误的帐号)、ExcessiveAttemptsException(登录失败次数过多)、IncorrectCredentialsException (错误的凭证)、ExpiredCredentialsException(过期的凭证)等,具体请查看其继承关系;对于页面的错误消息展示,最好使用如“用户名/密码错误”而不是“用户名错误”/“密码错误”,防止一些恶意用户非法扫描帐号库。
最后可以调用subject.logout退出,其会自动委托给SecurityManager.logout方法退出。
从上面的测试类中我们发现的几个问题:1、用户名/密码硬编码在ini配置文件,以后需要改成如数据库存储,且密码需要加密存储。2、用户身份Token可能不仅仅是用户名/密码,也可能还有其他的,如登录时允许用户名/邮箱/手机号同时登录。
上面我们是直接访问数据库(.ini文件)的,而我们真正用到Shiro时是通过Realm来访问数据库的,在这里也应该是通过Realm来访问.ini配置文件。所以下面我们来讲讲通过上面提到的Realm完成认证功能。
4.自定义Realm
实际开发中我们通过realm从数据库中查询用户信息,所以realm的作用可想而知:根据token中的身份信息去查询数据库(入门程序我们使用ini配置文件模拟数据库),如果查到用户则返回认证信息,如果查询不到就返回null。
在Shiro架构中,realm接口中的java代码如下:
String getName(); //返回一个唯一的Realm名字
boolean supports(AuthenticationToken token); //判断此Realm是否支持此Token
AuthenticationInfo getAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException; //根据Token获取认证信息
而往往我们都是自定义Realm,所以我们需要自定义一个CustomRealm.java文件并让它继承AuthorizingRealm抽象类,在src包下创建一个realm包,在realm包中创建我们的自定义Realm,java代码如下:
public class CustomRealm extends AuthorizingRealm
{
//设置realm的名称
@Override
public void setName(String name) {
super.setName("customRealm");
}
//用于认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//token是用户输入的
//第一步:丛token中取出身份信息
String userCode= (String) token.getPrincipal();
//第二步:根据用户输入的userCode丛数据库查询
//模拟从数据库查询到的密码
String password="1";
//如果查不到返回null,
//如果查询到,返回认证信息AuthenticationInfo
SimpleAuthenticationInfo simpleAuthenticationInfo=new
SimpleAuthenticationInfo(userCode,password,this.getName());
return simpleAuthenticationInfo;
}
//用于授权,该功能在下篇文章中进行讲解
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
}
4.1配置自定义Realm
自定义Realm的代码实现后,我们就需要将这个自定义的Realm进行配置,在src包下创建一个shiro-reaml.ini文件(你也可以在先前我们创建的shiro-first.ini文件下进行修改),而该配置文件跟我们入门程序中的配置文件是不一样的,入门程序中的配置文件是模拟的数据库,而这里的配置文件是将Realm进行配置,数据库中的信息我们在自定义Reaml的代码中已进行模拟,内容如下:
内容中的解释也在注释中给出,内容中的属性值customRealm任意取,不一定要跟Reaml实现java代码中方法setName()的值一样,当然若是你自定义了多个Realm,那么配置文件中的内容如下:
#声明多个realm
customRealm1=realm.CustomRealme2
customRealme=realm.CustomRealme2
#指定securityManager的realms实现
securityManager.realms=$myRealm1,$myRealm2
完成自定义Realm的代码实现并配置后,我们便可进行该Reaml的测试了,测试代码如下,仍然模拟的用户登录认证:
@Test
public void testCustomRealm()
{
//创建securityManager工厂
Factory factory=new IniSecurityManagerFactory(
"classpath:shiro-realm.ini");
//创建SecurityManager
SecurityManager securityManager = factory.getInstance();
//将securityManager设置到当前的运行环境汇中
SecurityUtils.setSecurityManager(securityManager);
//丛SecurityUtils里边创建一个
Subject subject = SecurityUtils.getSubject();
// 在认证提交前准备token(令牌)
// 这里的账号和密码 将来是由用户输入进去
UsernamePasswordToken token = new UsernamePasswordToken("zhansan",
"111111");
try {
// 执行认证提交
subject.login(token);
} catch (AuthenticationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 是否认证通过
boolean isAuthenticated = subject.isAuthenticated();
System.out.println("是否认证通过:" + isAuthenticated);
}
扩展:看到这里相信你就会使用Shiro完成认证功能了,但是这里我们是通过用户输入的密码直接去查询数据库中的明文密码,而往往为了保护用户信息所以数据库中的密码都是经过了MD5的算法进行加密后才放入数据库的,所以实际开发中我们需要通过Shiro获取数据库中经过md5加密后的密码来和用户输入的密码进行比对。所以接下来我要讲Shiro中是如何将用户输入的信息与数据库中的加密信息进行对比从而实现的认证。