1. 什么是Shiro
Shiro是一个基于Java的安全框架,它提供了身份验证
、授权
、加密和会话管理等
安全功能,可以帮助Java应用程序实现安全性。
2. 根据Shiro的基本使用了解其基本原理
1. 添加依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.9.0</version>
</dependency>
2. 创建ini文件,实现登录功能
在resources文件目录下创建 后缀为 .ini 的文件。内容如下:
[users]
zhangsan=123
lisi=123
解释:表示创建了一个2个用户 分别是shangsan,lisi。他们的密码都是123。
//////////////////////////
subject.login 认证的原理如下:
1. 首先调用 Subject.login(token) 进行登录,其会自动委托给 SecurityManager
2. SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份验证
3. Authenticator 才是真正的身份验证者,Shiro API 中核心的身份 认证入口点,此处可以自定义插入自己的实现
4. Authenticator 可能会委托给相应的 AuthenticationStrategy 进 行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm身份验证
5. Authenticator 会把相应的 token 传入 Realm,从 Realm 获取 身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处 可以配置多个Realm,将按照相应的顺序及策略进行访问
自己总结:就是说 subject.login 最后其实调用的是 AuthenticatingRealm 类下的 doGetAuthenticationInfo(token) 方法
//////////////////////////
public class ShiroRun {
public static void main(String[] args) {
//1 初始化获取 SecurityManager
IniSecurityManagerFactory factory = new
IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
//2 获取 Subject 对象
Subject subject = SecurityUtils.getSubject();
//3 创建 token 对象, web 应用用户名密码从页面传递
AuthenticationToken token = new UsernamePasswordToken("zhangsan","z3");
//4 完成登录
try {
subject.login(token);
System.out.println("登录成功");
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户不存在");
} catch (IncorrectCredentialsException e) {
e.printStackTrace();
System.out.println("密码错误");
} catch (AuthenticationException ae) {
//unexpected condition? error?
}
}
}
3. 利用shiro提供加密
Shiro 内嵌了很多常用的加密算法,比如MD5加密。
常见的加密算法:
对称加密:加密,解密使用同一个秘钥进行,常见的有 DES、3DES、AES 等。
非对称加密:使用一对密钥(公钥和私钥是一对)进行加密和解密,常见的有 RSA、DSA、ECC 等。
消息摘要算法:把任意长度的输入揉和而产生长度固定的伪随机结果的算法。常见的有 MD5、SHA-1、SHA-256、SHA-512 等。
数字签名算法:数字签名算法主要是为了解决消息的真实性和完整性问题,常见的有 RSA 签名、DSA 签名、ECDSA 签名等。
MD5:
被称为信息摘要算法,所谓的信息摘要就是把明文内容按一定规则生成一段哈希(hash)值,即得到这段明文内容的信息摘要。
md5是不可逆的,也就是没有对应的算法,从生产的md5值逆向得到原始数据。但是可以通过暴力解析得到原始数据。一般不会直接使用,都会对密码进行加盐处理。
SHA-256:
也被称为信息摘要算法,对于任意长度的消息,SHA256都会产生一个256位的哈希值。[总之,是目前很安全的加密算法,推荐]
// 使用Shiro提供的MD5进行加密:
public class ShiroMD5 {
public static void main(String[] args) {
// 密码明文
String password = "z3";
// 使用 md5 加密
Md5Hash md5Hash = new Md5Hash(password);
System.out.println("md5 加密:"+md5Hash.toHex());
// 带盐的 md5 加密,盐就是在密码明文后拼接新字符串,然后再进行加密
Md5Hash md5Hash2 = new Md5Hash(password,"salt");
System.out.println("md5 带盐加密:"+md5Hash2.toHex());
// 为了保证安全,避免被破解还可以多次迭代加密,保证数据安全
Md5Hash md5Hash3 = new Md5Hash(password,"salt",3);
System.out.println("md5 带盐三次加密:"+md5Hash3.toHex());
}
}
4. 授权(授予角色,授予权限)
授权方式可以分为:
编程式:
if(subject.hasRole("root")){
// 有权限
} else{
// 没权限
}
注解式:
@RequiresRoles("admin")
public MsgResult listUser(){
}
/////////////////////////
更改.ini文件:
[users]
zhangsan=shangsan,role1,role2
lisi=lisi
[roles]
role1=user:list,user:insert
解释:
表示zhangsan用户拥有role1与role2角色
role1角色拥有 user:list 的权限, role2角色拥有 user:insert 权限
【这里的 user:list 只是一种标识,可以任意定义,表示role1具有某种权限字符串,拥有该权限字符串,就能让它有什么权限】
/////////////////////////
public class ShiroRun {
public static void main(String[] args) {
//1 初始化获取 SecurityManager
IniSecurityManagerFactory factory = new
IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
//2 获取 Subject 对象
Subject subject = SecurityUtils.getSubject();
//3 创建 token 对象, web 应用用户名密码从页面传递
AuthenticationToken token = new UsernamePasswordToken("zhangsan","z3");
//4 完成登录
try {
subject.login(token);
System.out.println("登录成功");
// 判断角色
if(subject.hasRole("role1")){
System.out.println("该用户拥有角色:role1")
}
// 判断权限
if(subject.isPermitted("user:insert")){
System.out.println("该用户拥有权限:user:insert")
}
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户不存在");
} catch (IncorrectCredentialsException e) {
e.printStackTrace();
System.out.println("密码错误");
} catch (AuthenticationException ae) {
//unexpected condition? error?
}
}
}
3. 使用自定义Realm更改shiro的默认登录操作
更改.ini文件:
[main]
md5CredentialsMatcher=org.apache.shiro.authc.credential.Md5CredentialsMatcher
md5CredentialsMatcher.hashIterations=3
myrealm=com.lihao.config.realm.MyRealm
myrealm.credentialsMatcher=$md5CredentialsMatcher
securityManager.realms=$myrealm
[users]
zhangsan=7174f64b13022acd3c56e2781e098a5f,role1,role2
lisi=root
[roles]
role1=user:insert,user:select
/////////////////////////////
// subject.login(token) 实现登录,它的本质调用的是 AuthenticatingRealm 类下的 doGetAuthenticationInfo(token) 方法。
我们想改造shiro的登录,就可以继承AuthenticatingRealm类,重写doGetAuthenticationInfo(token) 方法以达到我们想在登录功能里面进行的额外操作。
public class MyRealm extends AuthenticatingRealm {
// 这个类是我们自己写的,需要配置自定义的 realm 生效,在 ini 文件中配置,或 Springboot 中配置
// 该方法终究能操作的只是部分登录认证(对比密码),真正认证逻辑还是按照 Shiro 的底层认证逻辑完成认证。
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 方法参数:AuthenticationToken 是个接口。
有2个属性:principal(username),credentials(password)
有2个子接口:RememberMeAuthenticationToken(有1个属性:isRememberMe),HostAuthenticationToken(有1个属性:getHost)
这2个子接口,都有一个共同的实现类:public class UsernamePasswordToken implements HostAuthenticationToken, RememberMeAuthenticationToken
//1 获取身份信息
String principal = authenticationToken.getPrincipal().toString();
//2 获取凭证信息
String password = new String((char[])
authenticationToken.getCredentials());
System.out.println("认证用户信息:"+principal+"---"+password);
//3 获取数据库中存储的用户信息
if(principal.equals("zhangsan")){
//3.1 数据库存储的加盐迭代 3 次密码
String pwdInfo = "7174f64b13022acd3c56e2781e098a5f";
//3.2 创建封装了校验逻辑的对象,将要比较的数据给该对象
AuthenticationInfo info = new SimpleAuthenticationInfo(authenticationToken.getPrincipal(),
pwdInfo,ByteSource.Util.bytes("salt"), this.getName());
return info;
}
return null;
}
}