二、自定义Realm
通过上面我们发现仅仅将数据信息定义在ini文件中我们实际开发环境有很大不兼容,所以我们希望能够自己定义Realm。
1.自定义Realm的实现
(1)、创建自定义Realmjava类
创建一个java文件继承AuthorizingRealm类,重写两个抽象方法:
package com.sxt.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.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
/**
* @author Administrator
* 定义的Realm
*/
public class SecurityRealm extends AuthorizingRealm{
/**
* 认证的方法
* 就是我们在测试代码中 定义的UserPassWoldToken对象
* 有我们保存的需要验证的账号密码信息
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken t=(UsernamePasswordToken) token;
//获取登录的账号
String username=t.getUsername();
System.out.println("登录的账号:"+username);
//通过jdbc去数据库中查询该账号对应的记录
if(!"root".equals(username)){
//账号不存在
return null;
}
//数据库中查询的密码是123456
String password="123456";
//身份信息(可以是账号也可以是对象) 密码 realmName(自定义)
return new SimpleAuthenticationInfo(username,password,"tang");
}
/**
* 授权方法
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
// TODO Auto-generated method stub
return null;
}
}
方法名 | 说明 |
---|---|
doGetAuthentictionInfo | 完成账号认证的方法 |
doGetAuthorizationInfo | 完成用户授权的方法 |
(2)、配置ini.xml文件:
[main]
#自定义 realm
customRealm=com.sxt.realm.SecurityRealm
#将realm设置到securityManager
securityManager.realms=$customRealm
(3)、测试(代码跟上面的一样)
package com.sxt.test;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
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;
/**
* @author Administrator
*shiro的第一个入门案例
*/
public class HelloTest {
public static void main(String[] args) {
//1.加载配置文件获取Factory对象
Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:shiro.ini");
//2.获取SecurityManager对象
SecurityManager securityManager =factory.getInstance();
//3.将SecurityManager添加到系统
SecurityUtils.setSecurityManager(securityManager);
//4.通过SecurityManager获取Subject对象
Subject subject=SecurityUtils.getSubject();
//账号密码是客户端提交的数据
AuthenticationToken token=new UsernamePasswordToken("root","123456");
//5.实现认证操作
try{
subject.login(token);
System.out.println("认证成功");
}catch(UnknownAccountException e){
System.out.println("账号输入错误。,,,");
}catch (IncorrectCredentialsException e) {
System.out.println("密码输入错误。。。");
}
}
}
2.原理分析
为什么要继承AuthorizingRealm?
通过分析认证的流程,我们发现在认证的过程中核心代码是: 核心方法是==doGetAuthenticationInfo(token)== 在Realm的结构中 AuthorizingRealm和AuthenticatingRealm都提供的有doGetAuthenticationInfo(token)的抽象方法。但是AuthenticatingRealm中要重写的抽象方法太多而AuthorizingRealm只需要重写两个方法,且这两个方法都是我们需要使用的。故选择继承AuthorizingRealm
自定义的Realm什么时候被调用的?
密码验证什么时候执行的?
==注意==:自定义Realm中只完成了账号的认证。密码认证还是在==AuthenticatingRealm==中完成的,只是我们在自定义Realm中完成了密码的设置。
三、shiro-加密
加密,是以某种特殊的算法改变原有的信息数据,使得未授权的用户即使获得了已加密的信息,但因不知解密的方法,任然无法了解信息的内容
1、概念:
数据加密的基本过程就是对原来为明文的文件或数据按照某种算法进行处理,使其成为不可读的一段代码,通常称为“密文”,使其只能在输入相应的密匙之后才能显示出本来内容,通过这样的途径来达到保护数据不被非法人窃取、阅读的目的。该过程的逆过程为==解密==,即将该编码信息转换为其原来数据的过程。
2、加密分类:
(1)、对称加密
双方使用的同一个密匙,既可以加密又可以解密,这种加密方法称为对称加密,也称单密匙加密。
(2)、非对称加密
一对密匙由公钥和私钥组成(可以使用很多对密匙)。私钥解密公钥加密数据,公钥解密私钥加密数据(私钥公钥可以互相加密解密)。
3、加密算法分类
(1)、单项加密
单项加密是不可逆的,也就是只能加密,不能解密。通常用来传输类型用户名和密码,直接将加密后的数据提交到后台,因为后台不需要知道用户名和密码,可以直接将接收到的加密后的数据存储到数据库
(2)、双向加密
通常分为对称性加密算法和非对称性加密算法,对于对称性加密算法,信息接收双方都需事先知道密匙和加解密算法且其密匙是相同的,之后便是对数据进行 加解密了。非对称算法与之不同,发送双方A,B事先均生成一堆密匙,然后A将自己的公有密匙发送给B,B将自己的公有密匙发送给A,如果A要给B发送消 息,则先需要用B的公有密匙进行消息加密,然后发送给B端,此时B端再用自己的私有密匙进行消息解密,B向A发送消息时为同样的道理。
4、常见的算法
MD5的使用:
package com.sxt.test;
import org.apache.shiro.crypto.hash.Md5Hash;
public class Md5HashTest {
public static void main(String[] args) {
// 对单个信息加密
Md5Hash md5 = new Md5Hash("123456");
System.out.println(md5.toString());
// 加密添加盐值 增大解密难度
md5 = new Md5Hash("123456","aaa");
System.out.println(md5.toString());
// 加密添加盐值 增大解密难度 迭代1024次
md5 = new Md5Hash("123456","aaa",1024);
System.out.println(md5);
}
}
输出的结果:
盐值的作用:
使用MD5存在一个问题,相同的password生成的hash值是相同的,如果两个用户设置了相同的密码,那么数据库中会存储两个相同的值,这是极不安全的,加Salt可以在一定程度上解决这一问题,所谓的加Salt方法,就是加点‘佐料’。其基本想法是这样的,当用户首次提供密码时(通常是注册时)由系统自动往这个密码里撒一些‘佐料’,然后在散列,而当用户登录时,系统为用户提供的代码上撒上相同的‘佐料’,然后散列,再比较散列值,来确定密码是否正确。 加盐的原理: 给原文加入随机数生成新的MD5的值
shiro中使用MD5加密
1.认证方法中修改
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 获取账号信息
String principal = (String) token.getPrincipal();
// 正常逻辑此处应该根据账号去数据库中查询,此处我们默认账号为 root 密码123456
// 验证账号
if(!"root".equals(principal)){
// 账号错误
return null;
}
//String pwd = "123456";
// 12345 根据 盐值 aaa 加密获取的密文
//88316675d7882e3fdbe066000273842c 1次迭代的密文
//a7cf41c6537065fe724cc9980f8b5635 2次迭代的密文
String pwd = "88316675d7882e3fdbe066000273842c";
// 验证密码
AuthenticationInfo info = new SimpleAuthenticationInfo(
principal, pwd,new SimpleByteSource("aaa"),"myrealm");
return info;
}
2.ini.xml文件的修改:
[main]
#定义凭证匹配器
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
#散列算法
credentialsMatcher.hashAlgorithmName=md5
#散列次数
credentialsMatcher.hashIterations=1
#将凭证匹配器设置到realm
customRealm=com.dpb.realm.MyRealm
customRealm.credentialsMatcher=$credentialsMatcher
securityManager.realms=$customRealm
3.测试:
@Test
public void test() {
// 1.获取SecurityManager工厂对象
Factory<SecurityManager> factory =
new IniSecurityManagerFactory("classpath:shiro.ini");
// 2.通过Factory对象获取SecurityManager对象
SecurityManager securityManager = factory.getInstance();
// 3.将SecurityManager对象添加到当前运行环境中
SecurityUtils.setSecurityManager(securityManager);
// 4.获取Subject对象
Subject subject = SecurityUtils.getSubject();
AuthenticationToken token = new UsernamePasswordToken("root", "123456");
// 登录操作
try {
subject.login(token);
} catch (UnknownAccountException e) {
System.out.println("账号出错...");
} catch(IncorrectCredentialsException e){
System.out.println("密码出错...");
}
// 获取登录的状态
System.out.println(subject.isAuthenticated());
}