shiro入门与认证原理
一、shiro介绍
1、什么是shiro
shiro是apache的一个开源框架,是一个权限管理的框架,实现 用户认证、用户授权。
2、shiro的优点
(1)shiro将安全认证相关的功能抽取出来组成一个框架,使用shiro就可以非常快速的完成认证、授权等功能的开发,降低系统成本。
(2)shiro使用广泛,shiro可以运行在web应用,非web应用,集群分布式应用中越来越多的用户开始使用shiro。
(3)java领域中spring security(原名Acegi)也是一个开源的权限管理框架,但是spring security依赖spring运行,而shiro就相对独立,最主要是因为shiro使用简单、灵活,所以现在越来越多的用户选择shiro。
3、shiro的架构
(1)Subject
Subject即主体,外部应用与subject进行交互,subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。 Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权
(2)SecurityManager
SecurityManager即安全管理器,对全部的subject进行安全管理,它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等,实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。
SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口。
(3)Authenticator
Authenticator即认证器,对用户身份进行认证,Authenticator是一个接口,shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器。
(4)Authorizer
Authorizer即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。
(5)realm
Realm即领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。
注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码。
(6)sessionManager
sessionManager即会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。
(7)SessionDAO
SessionDAO即会话dao,是对session会话操作的一套接口,比如要将session存储到数据库,可以通过jdbc将会话存储到数据库。
(8)CacheManager
CacheManager即缓存管理,将用户权限数据存储在缓存,这样可以提高性能。
(9)Cryptography
Cryptography即密码管理,shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。
4.依赖jar包
(1)maven方式
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-quartz</artifactId>
<version>1.2.3</version>
</dependency>
//也可以通过引入shiro-all包括shiro所有的包:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.2.3</version>
</dependency>
(2)导入方式的jar包
二、shiro认证
1、认证流程
2、入门程序(用户登陆和退出)
创建一个Java工程,其目录结构如下
(1)创建shiro_first.ini文件,通过此配置文件创建securityManager工厂。
#对用户信息进行配置
[users]
#用户名和密码
zhangsan=111111
lisi=111111
(2)测试代码
public void test1(){
//创建安全管理器 SecurityManager工厂
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:config/shiro-first.ini");
//创建安全管理器SecurityManager
SecurityManager securityManager = factory.getInstance();
//将SecurityManager设置到当前运行环境中
SecurityUtils.setSecurityManager(securityManager);
//从SecurityUtils里面创建一个主体
Subject subject = SecurityUtils.getSubject();
//创建认证使用的token
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan","1111111");
//subject提交认证
try{
subject.login(token);
}catch (Exception e){
e.printStackTrace();
}
//是否认证通过
boolean isAuthenticate = subject.isAuthenticated();
System.out.println("通过认证:"+isAuthenticate);
System.out.println("是否认证通过:" + isAuthenticated);
//退出操作
subject.logout();
//是否认证通过
isAuthenticated = subject.isAuthenticated();
System.out.println("是否认证通过:" + isAuthenticated);
}
(3)执行流程分析
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)
(4)总结
ModularRealmAuthenticator作用进行认证,需要调用realm查询用户信息(在数据库中存在用户信息)
ModularRealmAuthenticator进行密码对比(认证过程)。
realm:需要根据token中的身份信息去查询数据库(入门程序使用ini配置文件),如果查到用户返回认证信息,如果查询不到返回null
3、自定义realm
(1)定义realm要实现AuthorizingRealm接口
public class CustomerRealm extends AuthorizingRealm {
@Override
public void setName(String name) {
super.setName(name);
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//从token中取出身份信息
String userCode = (String) authenticationToken.getPrincipal();
//从数据库中去查询该用户是否存在
//如果查询不到就返回 null
//如果查询到了,就获取该用户的密码
String password = "111111";
//返回认证信息
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userCode,password,this.getName());
return simpleAuthenticationInfo;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}
(2)需要在shiro-realm.ini配置realm注入到securityManager中。
[main]
#自定义realm
customRealm=com.jack.realm.CustomerRealm
#将realm设置到securityManager
securityManager.realms=$customRealm
(3)测试代码
public void test2(){
//创建安全管理器 SecurityManager工厂
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:config/shiro-realm.ini");
//创建安全管理器SecurityManager
SecurityManager securityManager = factory.getInstance();
//将SecurityManager设置到当前运行环境中
SecurityUtils.setSecurityManager(securityManager);
//从SecurityUtils里面创建一个主体
Subject subject = SecurityUtils.getSubject();
//创建认证使用的token
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan","111111");
//subject提交认证
try{
subject.login(token);
}catch (Exception e){
e.printStackTrace();
}
//是否认证通过
boolean isAuthenticate = subject.isAuthenticated();
System.out.println("通过认证:"+isAuthenticate);
}
4、散列算法
(1)介绍
通常需要对密码 进行散列,常用的有md5、sha,
对md5密码,如果知道散列后的值可以通过穷举算法,得到md5密码对应的明文。
建议对md5进行散列时加salt(盐),进行加密相当 于对原始密码+盐进行散列。
正常使用时散列方法:
在程序中对原始密码+盐进行散列,将散列值存储到数据库中,并且还要将盐也要存储在数据库中。
如果进行密码对比时,使用相同 方法,将原始密码+盐进行散列,进行比对。
(2)md5散列测试程序
//md5加密,不加盐
String password_md5 = new Md5Hash("111111").toString();
System.out.println("md5加密,不加盐="+password_md5);
//md5加密,加盐,一次散列
String password_md5_sale_1 = new Md5Hash("111111", "eteokues", 1).toString();
System.out.println("password_md5_sale_1="+password_md5_sale_1);
String password_md5_sale_2 = new Md5Hash("111111", "uiwueylm", 1).toString();
System.out.println("password_md5_sale_2="+password_md5_sale_2);
//两次散列相当于md5(md5())
//使用SimpleHash
String simpleHash = new SimpleHash("MD5", "111111", "eteokues",1).toString();
System.out.println(simpleHash);
(3)自定义realm支持散列算法
//继承AuthorizingRealm类,实现自定义realm
public class CustomerRealmMd5 extends AuthorizingRealm {
@Override
public void setName(String name) {
super.setName(name);
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//从token中取出身份信息
String userCode = (String) authenticationToken.getPrincipal();
//从数据库中去查询该用户是否存在
//如果查询不到就返回 null
//如果查询到了,就获取该用户的密码
String password = "745c144d7721368a3257dcd0f728e4f1";
//从数据库获取salt信息
String salt = "adfgx";
//返回认证信息
SimpleAuthenticationInfo simpleAuthenticationInfo = new
SimpleAuthenticationInfo(userCode,password, ByteSource.Util.bytes(salt),this.getName());
return simpleAuthenticationInfo;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}
(4)定义散列的凭证匹配器
[main]
#定义凭证匹配器
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
#散列算法
credentialsMatcher.hashAlgorithmName=md5
#散列次数
credentialsMatcher.hashIterations=1
#将凭证匹配器设置到realm
customRealm=com.jack.realm.CustomerRealmMd5
customRealm.credentialsMatcher=$credentialsMatcher
securityManager.realms=$customRealm