目录
一、权限的基本概念
1.什么是权限管理
权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则控制用户可以且仅能访问自己被授权的资源。
权限管理包括身份认证和授权两部分,简称认证授权。
2.什么是身份认证
判断一个用户是否为合法用户的处理过程。
最常见的方式是用户名和口令,还可以是指纹,刷脸等方式。
3.什么是授权
授权,即访问控制,控制谁能访问哪些资源。
二、Shiro的基本了解
1.什么是Shiro
Shiro是Apache旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证、权限授权、加密、会话管理等功能,组成一个通用的安全认证框架。
2.shiro的核心架构
三、Shrio第一个程序用户认证
1.基本概念
(1)Subject:主体
访问系统的用户,进行认证的都是主体
(2)Principal:身份信息
是主体进行身份认证的标识,具有唯一性。一个主体可以有多个身份,但是必须有一个主身份
(3)credential:凭证信息
只有主体自己知道的安全信息
2.认证流程
主体通过身份信息,凭证信息生成另外,然后校验令牌是否合法。
3.实例编写
(1)创建项目
创建好后自动引入maven,然后新增两个resources文件夹:
(2)引入shiro依赖
(3)引入shiro配置文件
注意,shiro是以ini为结尾的配置文件,而不是常规的properties或者yml。
配置文件只需要放在resources下面的任意一个目录中即可。
注意:真正项目中,我们是用不到ini这个文件的,这个文件用于我们学习中书写系统相关权限。省去了编辑数据库相关的操作,简化学习过程
上面的[users]是固定写死的:
(4)编写控制类
package com.xupeng;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.subject.Subject;
public class TestAuthenticator {
public static void main(String[] args) {
//1.创建安全管理器对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//2.给安全管理器设置realm(realm提供数据相关)
securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
//3.SecurityUtils全局安全工具类
SecurityUtils.setSecurityManager(securityManager);
//4.关键对象:subject对象
Subject subject = SecurityUtils.getSubject();
//5.创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("xupeng","456");
try{
System.out.println("认证状态:"+subject.isAuthenticated());
subject.login(token);//用户登录验证
System.out.println("认证状态:"+subject.isAuthenticated());
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("认证失败,认证用户不存在");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("认证失败,密码错误");
}
}
}
(5)测试
我们会发现状态变了,证明登录成功
4.认证源码的解读
(1)最终用户名的认证是在SimpleAccountRealm中:
(2)密码的校验是在AuthenticatingRealm类中:
一旦我们密码不正确,他会报IncorrectCredentialsException异常,源码看这里也就清晰了
5.实例优化:自定义Realm
上述我们例子中,用户名密码是写在ini文件中,但是在实际使用中肯定是放在数据库,这边我们来模拟数据库先做一个简单的例子
CustomerRealm:
package com.xupeng.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;
/**
* 自定义Realm实现,将认证/授权数据的来源转为数据库的实现
*/
public class CustomerRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//在token中获取用户名
String principal = (String) authenticationToken.getPrincipal();
System.out.println(principal);
//根据身份信息使用jdbc mybatis查询相关数据库
if("xupeng".equals(principal)){
//参数1:数据库中正确用户名
//参数2:数据库中正确密码
//参数3:提供当前Realm名字
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("xupeng","456",this.getName());
return simpleAuthenticationInfo;
}
return null;
}
}
TestCustomerRealmAuthenticator:
package com.xupeng;
import com.xupeng.realm.CustomerRealm;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;
/**
* 使用自定义的Realm
*/
public class TestCustomerRealmAuthenticator {
public static void main(String[] args) {
//创建SecurityManager
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//设置自定义realm
defaultSecurityManager.setRealm(new CustomerRealm());
//设置安全工具类
SecurityUtils.setSecurityManager(defaultSecurityManager);
//通过安全工具类来获取subject
Subject subject = SecurityUtils.getSubject();
//创建token
UsernamePasswordToken token = new UsernamePasswordToken("xupeng","456");
try {
subject.login(token);
System.out.println(subject.isAuthenticated());
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("认证失败,认证用户不存在");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("认证失败,密码错误");
}
}
}
测试:
四、DM5和Salt
数据库不能明文存储密码,我们需要MD5加密,并且加Salt(盐)。
1.MD5算法
(1)特点
- MD5算法不可逆。
- 如果内容相同,无论执行多少次md5生成的结果都是一样的
(2)作用
一般用于加密或者签名。
签名这里我们来解释一下,我们下载软件的时候经常官网会看到一个md5后缀的文件,这个文件用于校验你下载的软件是否存在缺失。我们将下载的文件整体经过md5加密后,应该跟官网提供的md5后缀的那个文件相同才对。
(3)生成结果
md5生成的结果始终是一个16进制32位长度的字符串
2.Salt(盐)
(1)MD5算法的问题
现在网上有穷举网站,如果我们直接密码存md5,拿着MD5去穷举网站比对,还是比较容易比对出的。所以我们给密码再加一点自己的东西,这个东西就叫盐。增加比对的复杂性,保证密码的相对安全。
3.md5+salt+hash散列实例
五、Shiro中认证:MD5+Salt+Hash的实现
1.查询密码对应的密文
TestShiroMD5
package com.xupeng;
import org.apache.shiro.crypto.hash.Md5Hash;
public class TestShiroMD5 {
public static void main(String[] args) {
//直接使用md5算法
Md5Hash md5Hash = new Md5Hash("456");
System.out.println(md5Hash.toHex());
//使用md5+salt
Md5Hash md5Hash1 = new Md5Hash("456","xp");
System.out.println(md5Hash1.toHex());
//使用md5+salt+hash散列
//其中,1024表示对加盐结果进行1024次散列
Md5Hash md5Hash2 = new Md5Hash("456","xp",1024);
System.out.println(md5Hash2.toHex());
}
}
2.Shiro中MD5+Salt+Hash的实现
CustomerMd5Realm:
package com.xupeng.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;
/**
* 自定义Realm实现,加入md5+salt+hash散列
*/
public class CustomerMd5Realm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//在token中获取用户名
String principal = (String) authenticationToken.getPrincipal();
System.out.println(principal);
//根据身份信息使用jdbc mybatis查询相关数据库
if("xupeng".equals(principal)){
//参数1:数据库中正确用户名
//参数2:数据库中正确MD5密码
//参数3:提供当前Realm名字
// SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
// "xupeng",
// "250cf8b51c773f3f8dc8b4be867a9a02",
// this.getName());
//参数1:数据库中正确用户名
//参数2:数据库中正确MD5密码+salt后的密码。如果加了散列次数,就设置散列后的密码
//参数3:注册时的随机盐
//参数4:提供当前Realm名字
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
"xupeng",
"4ca532fb479910d125d72992a3f57b33",
ByteSource.Util.bytes("xp"),
this.getName());
return simpleAuthenticationInfo;
}
return null;
}
}
TestCustomerMd5RealmAuthenticator:
package com.xupeng;
import com.xupeng.realm.CustomerMd5Realm;
import com.xupeng.realm.CustomerRealm;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;
/**
* 使用自定义的Realm,加入md5+salt+hash散列
*/
public class TestCustomerMd5RealmAuthenticator {
public static void main(String[] args) {
//创建SecurityManager
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//注入Realm
CustomerMd5Realm customerMd5Realm = new CustomerMd5Realm();
//设置realm使用hash凭证匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("md5");
credentialsMatcher.setHashIterations(1024);
customerMd5Realm.setCredentialsMatcher(credentialsMatcher);
defaultSecurityManager.setRealm(customerMd5Realm);
//设置安全工具类
SecurityUtils.setSecurityManager(defaultSecurityManager);
//通过安全工具类来获取subject
Subject subject = SecurityUtils.getSubject();
//创建token
UsernamePasswordToken token = new UsernamePasswordToken("xupeng","456");
try {
subject.login(token);
System.out.println(subject.isAuthenticated());
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("认证失败,认证用户不存在");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("认证失败,密码错误");
}
}
}
六、Shiro中的授权
授权,即访问控制,控制谁能访问哪些资源。
1.基本概念
(1)主体:Subject
(2)资源:Resource
(3)权限/许可:Permission
2.授权方式
- 基于角色的访问控制
- 基于资源的访问控制
3.权限字符串
对于基于资源的访问控制,权限字符串的规则是:
资源标识符:操作:资源实例标识符
意思是对那个资源的哪个实例有什么操作
实例:
用户创建权限:
user:create, 或者user:create:*
用户修改实例001的权限:
user:update:001
用户实例001的所有权限:
user:*:001
4.授权的编程实现
(1)编程式
(2)注解式
(3)标签式
5.授权的实例代码
授权的前提是认证通过,我们拿上面MD5+盐+hash的例子来操作:
CustomerMd5Realm:
doGetAuthorizationInfo方法就是用来授权的
package com.xupeng.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.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
/**
* 自定义Realm实现,加入md5+salt+hash散列
*/
public class CustomerMd5Realm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("------------------------");
String principal = (String) principalCollection.getPrimaryPrincipal();
System.out.println("身份信息:"+principal);
//根据身份信息 用户名获取当前用户的角色信息,以及权限信息
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addRole("admin");
simpleAuthorizationInfo.addRole("user");
//将数据库中查询权限信息复制给权限对象
simpleAuthorizationInfo.addStringPermission("user:*:01");
simpleAuthorizationInfo.addStringPermission("product:create");
return simpleAuthorizationInfo;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//在token中获取用户名
String principal = (String) authenticationToken.getPrincipal();
System.out.println(principal);
//根据身份信息使用jdbc mybatis查询相关数据库
if("xupeng".equals(principal)){
//参数1:数据库中正确用户名
//参数2:数据库中正确MD5密码
//参数3:提供当前Realm名字
// SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
// "xupeng",
// "250cf8b51c773f3f8dc8b4be867a9a02",
// this.getName());
//参数1:数据库中正确用户名
//参数2:数据库中正确MD5密码+salt后的密码。如果加了散列次数,就设置散列后的密码
//参数3:注册时的随机盐
//参数4:提供当前Realm名字
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
"xupeng",
"4ca532fb479910d125d72992a3f57b33",
ByteSource.Util.bytes("xp"),
this.getName());
return simpleAuthenticationInfo;
}
return null;
}
}
TestCustomerMd5RealmAuthenticator
package com.xupeng;
import com.xupeng.realm.CustomerMd5Realm;
import com.xupeng.realm.CustomerRealm;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;
import java.util.Arrays;
/**
* 使用自定义的Realm,加入md5+salt+hash散列
*/
public class TestCustomerMd5RealmAuthenticator {
public static void main(String[] args) {
//创建SecurityManager
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//注入Realm
CustomerMd5Realm customerMd5Realm = new CustomerMd5Realm();
//设置realm使用hash凭证匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("md5");
credentialsMatcher.setHashIterations(1024);
customerMd5Realm.setCredentialsMatcher(credentialsMatcher);
defaultSecurityManager.setRealm(customerMd5Realm);
//设置安全工具类
SecurityUtils.setSecurityManager(defaultSecurityManager);
//通过安全工具类来获取subject
Subject subject = SecurityUtils.getSubject();
//创建token
UsernamePasswordToken token = new UsernamePasswordToken("xupeng","456");
try {
subject.login(token);
System.out.println(subject.isAuthenticated());
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("认证失败,认证用户不存在");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("认证失败,密码错误");
}
//认证用户进行授权
if(subject.isAuthenticated()){
//1.基于角色权限控制
System.out.println(subject.hasRole("admin"));
//2.基于多角色的权限控制
System.out.println(subject.hasRoles(Arrays.asList("admin","user")));
//3.是否具有其中一个角色
boolean[] booleans = subject.hasRoles(Arrays.asList("admin","super","user"));
for(boolean b:booleans){
System.out.println(b);
}
System.out.println("================================");
//基于权限字符串的访问控制 资源标识符:操作:资源实例标识符
System.out.println("资源:"+subject.isPermitted("user:update:01"));
System.out.println("资源:"+subject.isPermitted("product:create:02"));
//分别具有哪些权限
boolean[] permitted = subject.isPermitted("user:*:01","order:*:10");
for(boolean b:permitted){
System.out.println(b);
}
//同时具有哪些权限
boolean permitAll = subject.isPermittedAll("user:*:01","product:create");
System.out.println(permitAll);
}
}
}
七、打赏请求
如果本篇博客对您有所帮助,打赏一点呗,谢谢了呢~