shiro分为认证和授权,这里我们先学习shiro的认证。
在eclipse中显示的工程结构
最核心的jar包就是:shiro-core.jar(下边其他的是需要依赖的jar包)
在工程中创建一个lib文件夹(Folder),把jar包拷贝进去,然后build path。
在eclipse中显示的工程结构
这里我也贴一下,用maven时候,pom文件中的坐标
org.apache.shiro
shiro-core
1.2.3
org.apache.shiro
shiro-web
1.2.3
org.apache.shiro
shiro-spring
1.2.3
org.apache.shiro
shiro-ehcache
1.2.3
org.apache.shiro
shiro-quartz
1.2.3
也可以通过引入shiro-all包括shiro所有的包:
org.apache.shiro
shiro-all
1.2.3
创建一个config文件夹(Source Folder),把log4j的日志文件贴进去。
这里可以简短的贴一下log4j日志文件最基本的内容
log4j.rootLogger=debug,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
到时候创建一个log4j.properties文件,把上边的贴入进去就可以了。
在eclipse中显示的工程结构
这样我们这个最入门的工程就搭建好了。
首先我们创建一个包com.shiro.authentication,创建一个测试类AnthenticationTest.java。
我们在测试这个环境中,并没有连接数据库,而是创建一个ini文件,在测试运行的时候加载ini文件里边配置的内容,就相当于从数据库中比对了。(ini文件相对于properties文件,就是可以分组,而properties文件只是简单的key,value形式)
ini文件配置的内容
(ini配置文件放在config文件夹中)
接下来是测试类的内容
package com.shiro.authentication;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Test;
/**
*
* Title: AnthenticationTest
* Description: 认证测试
* Company: www.zonsim.com
* @author Liuyl
* @date 2016年10月5日上午12:14:55
* @version 1.0
*/
public class AnthenticationTest {
// 用户登陆和退出
@Test
public void testLoginAndLogout(){
/**
* (1)构建securityManager环境
*/
// 创建securityManager工厂,通过ini配置文件创建securityManager工厂(ini是window中的一种配置文件)
Factory factory = new IniSecurityManagerFactory(
"classpath:shiro-first.ini");
//在ini里进行的配置,SecurityManager创建的时候就把ini里边的信息加载进来了,就相当于查询数据库了
//在说明一些SecurityManager类要导入org.apache.shiro.mgt.SecurityManager;包
// 创建SecurityManager
SecurityManager securityManager = factory.getInstance();
//通过创建SecurityManager工厂和创建SecurityManager对象,完成了第一步构建securityManager环境
/**
* 这个算是中间衔接的过程
*/
// 将securityManager设置当前的运行环境中(这里是单例里的,但是不用我们管理,SecurityUtils完成这件事)
SecurityUtils.setSecurityManager(securityManager);
/**
* 现在是一个测试环境,需要模拟一个subject,这里需要我们构造出来。
* 正常情况下,这个subject不需要我们来管,到时候就是我的一个http请求就是subject
*/
// 从SecurityUtils里边创建一个subject
Subject subject = SecurityUtils.getSubject();
/**
* (2)执行认证提交
*/
// 在认证提交前准备token(令牌)
// 这里的账号和密码 将来是由用户输入进去
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan",
"111111");
//我们在认证的时候可以抛出一个异常
try {
//认证提交
subject.login(token);
} catch (AuthenticationException e) {//AuthenticationException:shiro自己的认证异常
// TODO Auto-generated catch block
e.printStackTrace();
}
/**
* (3)得到一个验证的结果
*/
// 是否认证通过
boolean isAuthenticated = subject.isAuthenticated();
System.out.println("是否认证通过:" + isAuthenticated);
// 退出操作
subject.logout();//一退出,身份信息就没有了
// 是否认证通过
isAuthenticated = subject.isAuthenticated();
System.out.println("是否认证通过:" + isAuthenticated);//退出了就因该是false了
/**
* 整体的流程就是token中的用户名密码要和ini配置文件里边的用户名密码进行比对,
* 成功了就是true,没有就是false
*/
}
}
(1)上边测试的就过是true,false。
在执行程序的时候,会拿着UsernamePasswordToken产生的token,去对面ini配置文件里边定义的帐号密码。有就返回true。
当退出之后,身份信息就没有了,在进行身份认证,就是返回false了。
(2)如果用户名不对
会报帐号不存在异常:org.apache.shiro.authc.UnknownAccountException
返回结果则是false,false
(3)如果密码不正确
会报凭证错误异常:org.apache.shiro.authc.IncorrectCredentialsException
返回结果则是false,false
@1.通过ini配置文件创建securityManager。
@2.调用subject.login方法主体提交认证,提交的token。
@3.securityManager进行认证,securityManager通过Authenticator接口,最终由(调用)ModularRealmAuthenticator进行认证。
@4.ModularRealmAuthenticator调用IniRealm(给realm传入的token)去ini配置文件中查询用户信息。
@5.IniRealm根据输入的token(UsernamePasswordToken)从shiro-first.ini(自己定义的ini文件)查询用户信息,根据账号查询用户信息(账号和密码)
如果查询到用户信息,就给ModularRealmAuthenticator返回用户信息(账号和密码),
如果查询不到,就给ModularRealmAuthenticator返回null(说明账号不存在)。
@6.ModularRealmAuthenticator接收IniRealm返回Authentication认证信息,
如果返回的认证信息是null,ModularRealmAuthenticator抛出异常(org.apache.shiro.authc.UnknownAccountException)。
如果返回的认证信息不是null(说明inirealm找到了用户),ModularRealmAuthenticator对IniRealm返回用户密码(在ini文件中存在)和token中的密码进行对比,如果不一致抛出异常(org.apache.shiro.authc.IncorrectCredentialsException)
将来实际开发需要realm从数据库中查询用户信息。
这个自定义realm类一般继承AuthorizingRealm这个类。
下面就是自定义realm类
package com.shiro.realm;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
/**
*
* Title: CustomRealm
* Description: 自定义realm
* Company: www.zonsim.com
* @author Liuyl
* @date 2016年10月5日下午3:47:58
* @version 1.0
*/
public class CustomRealm extends AuthorizingRealm{
// 设置realm的名称(这里边就读取ini配置文件中的数据)
@Override
public void setName(String name) {
super.setName("customRealm");
}
/**
* 需要实现AuthorizingRealm这个接口的两个方法
*/
//用户认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//因为我们现在还不连接数据库,我们在这里模拟一下
//第一步从token中取出身份信息( token是用户输入的)(这里边userCode取到的值是前边测试类中UsernamePasswordToken设置的值)
String userCode = (String) token.getPrincipal();
// 第二步:根据用户输入的userCode从数据库查询
// ....
//下边这步是模拟没有找到用户时候的情况
// 如果查询不到返回null
//数据库中用户账号是zhangsansan
if(!userCode.equals("zhangsansan")){//
return null;
}
// 模拟从数据库查询到密码
String password = "111111";
// 如果查询到返回认证信息AuthenticationInfo
//参数1.身份信息 2.凭证3.realm的名称
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
userCode, password, this.getName());
return simpleAuthenticationInfo;
}
//用户授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
// TODO Auto-generated method stub
return null;
}
}
自定义reaml定义好之后,要对realm进行配置,如果不配置realm,securityManager就不知道调用哪个realm。
所以需要在shiro-realm.ini(也可以配置到shiro-first.ini)配置realm注入到securityManager中。
ini配置文件的内容为:
就是上边测试类,改变一下加载的ini配置文件名称,不加载shiro-first.ini,改为加载shiro-realm.ini
这样完成了自定义realm的认证方式。
以后的开发中都会用自定义realm的方式来写。
最后测试的结果,同上边的入门工程测试结果一样。
通常需要对密码进行散列,常用的有md5、sha,(md5散列后就不能逆向了)。
对md5密码,如果知道散列后的值可以通过穷举算法,得到md5密码对应的明文。
建议对md5进行散列时加salt(盐),进行加密相当于对原始密码+盐进行散列。
正常使用时散列方法:
在程序中对原始密码+盐进行散列,将散列值存储到数据库中,并且还要将盐也要存储在数据库中。
如果进行密码对比时,使用相同方法,将原始密码+盐进行散列,进行比对。
package com.shiro.authentication;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.junit.Test;
/**
*
* Title: MD5Test
* Description:
* Company: www.zonsim.com
* @author Liuyl
* @date 2016年10月5日下午4:36:30
* @version 1.0
*/
public class MD5Test {
@Test
public void MD5_Test(){
//原始 密码
String source = "111111";
//盐
String salt = "qwerty";
//散列次数
int hashIterations = 2;
//上边散列1次:f3694f162729b7d0254c6e40260bf15c
//上边散列2次:36f2dfa24d0a9fa97276abbe13e596fc
//构造方法中:
//第一个参数:明文,原始密码
//第二个参数:盐,通过使用随机数
//第三个参数:散列的次数,比如散列两次,相当 于md5(md5(''))
Md5Hash md5Hash = new Md5Hash(source, salt, hashIterations);
//得到md5散列后的密码
String password_md5 = md5Hash.toString();
System.out.println(password_md5);
//第一个参数:散列算法 (这个和上边的作用是一样的)
SimpleHash simpleHash = new SimpleHash("md5", source, salt, hashIterations);
System.out.println(simpleHash.toString());
}
}
package com.shiro.realm;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
/**
*
* Title: CustomRealm
* Description: 自定义realm
* Company: www.zonsim.com
* @author Liuyl
* @date 2016年10月5日下午3:47:58
* @version 1.0
*/
public class CustomRealmMD5 extends AuthorizingRealm{
// 设置realm的名称
@Override
public void setName(String name) {
super.setName("customRealmMD5");
}
/**
* 需要实现AuthorizingRealm这个接口的两个方法
*/
//用户认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//因为我们现在还不连接数据库,我们在这里模拟一下
//第一步从token中取出身份信息( token是用户输入的)(这里边userCode取到的值是前边测试类中UsernamePasswordToken设置的值)
String userCode = (String) token.getPrincipal();
// 第二步:根据用户输入的userCode从数据库查询
// ....
/*//下边这步是模拟没有找到用户时候的情况
// 如果查询不到返回null
//数据库中用户账号是zhangsansan
if(!userCode.equals("zhangsansan")){//
return null;
}*/
// 模拟从数据库查询到密码,进行散列后的值(原来的明文密码是111111)
String password = "f3694f162729b7d0254c6e40260bf15c";
// 从数据库获取salt
String salt = "qwerty";
// 如果查询到返回认证信息AuthenticationInfo
//参数1.身份信息 2.凭证3.盐4.realm的名称
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
userCode, password, ByteSource.Util.bytes(salt), this.getName());
return simpleAuthenticationInfo;
}
//用户授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
// TODO Auto-generated method stub
return null;
}
}
这里需要把凭证匹配器和自定义的realm都定义到realm中。
ini配置文件的内容为:
就是上边测试类,改变一下加载的ini配置文件名称,不加载shiro-realm.ini,改为加载shiro-realm-md5.ini
这种方式基本上都用这种。
最后测试的结果,同上边的入门工程测试结果一样。
这样关于shiro认证方面的使用方法和知识点都介绍完成了。