什么Shiro?
- Apache的强大灵活的开源安全框架
- 提供, 认证, 授权, 企业会话管理, 安全加密, 缓存管理
一般我们使用Shiro能够快捷方便的完成项目里的权限管理模块开发
Shiro与Spring Security
Apache Shiro | Spring Security |
---|---|
简单,灵活,轻量级 | 复杂,笨重,重量级 |
可脱离Spring | 不可脱离Spring |
粒度较粗 | 粒度更细 |
我个人更喜欢用Shiro, 主要因为它比较简单, 灵活, 有些没有的功能我们可以自己去拓展. 权限粒度较粗这一块, 因为我们基本上是基于资源去做权限控制. 如果我们要做数据权限的话, 一定会和我们业务代码耦合, 所以Spring Security的控制粒度更细也没有体现出来, 或者说没有体现的很明显. 而且Spring的官网它也是用的Shiro做安全管理.
Shiro整体架构
- 首页最上面浅黄的部分可以理解为当前的----操作用户
Security Manager部分是我们Shiro的核心. 我们来看一下它的组件
- Authenticator(认证器)管理我们的登陆, 登出
- Authorizer(授权器) 赋予我们主体的权限
- SessionManager(会话管理器)可以不在不借助任何web容器下使用Session
- SessionDAO(会话操作)提供了Session的操作, 主要是有增删改查
- CacheManager(缓存管理器)利用缓存管理器可以缓存我们的角色数据和权限数据
- PluggableRealms(插接式连接器)Shiro获取认证信息, 权限数据, 角色数据, 连接数据库
- Cryptography(加密器)可以使用它, 非常快捷便利的行进加密
用户提交请求到SecurityManager, SecurityManager调用认证器去做一个认证, 而认证器是通过插接式连接器, 从数据库中查询获取到认证信息. 授权器同样如此.
Shiro认证
- 创建SecurityManager对象, 构建SecurityManager的环境
- 主体提交认证(也就是上图中的最上面部分,操作用户)到SecurityManager进行认证
- SecurityManager调用Authenticator(认证器)进行认证, 进行认证的时候需要使用PluggableRealms(插接式连接器)获取认证数据, 然后再进行认证.
认证小demo(可以改一下不正确的角色认证试试看会有什么效果)
SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();
@Before
public void addUser(){
//添加一个用户
simpleAccountRealm.addAccount("ljj","123");
}
@Test
public void testAuthentication(){
//1. 构建SecurityManager环境
DefaultSecurityManager manager = new DefaultSecurityManager();
manager.setRealm(simpleAccountRealm);//设置Realm环境
//2. 主体提交认证请求
SecurityUtils.setSecurityManager(manager);//设置SecurityManager环境
Subject subject = SecurityUtils.getSubject();//获得主体
UsernamePasswordToken token = new UsernamePasswordToken("ljj","123");//存放验证数据
subject.login(token);//进行验证
//是否认证成功
System.out.println(subject.isAuthenticated());
//登出
subject.logout();
System.out.println(subject.isAuthenticated());
}
Shiro授权
shiro授权和基本一致, 只不过是将Authenticator(认证器)更换成了Authorizer(授权器)
授权小demo (可以改一下不正确的角色权限试试看会有什么效果)
@Before
public void addUser(){
//添加一个用户, 并赋予角色权限,可以有多个角色
simpleAccountRealm.addAccount("ljj","123","admin","user");
}
@Test
public void testAuthentication(){
//1. 构建SecurityManager环境
DefaultSecurityManager manager = new DefaultSecurityManager();
manager.setRealm(simpleAccountRealm);//设置Realm环境
//2. 主体提交认证请求
SecurityUtils.setSecurityManager(manager);//设置SecurityManager环境
Subject subject = SecurityUtils.getSubject();//获得主体
UsernamePasswordToken token = new UsernamePasswordToken("ljj","123");//存放验证数据
subject.login(token);//进行验证
//是否认证成功
System.out.println(subject.isAuthenticated());
//权限认证, 单个是checkRole, 多个才要加 s
subject.checkRoles("admin","user");
}
Shiro—IniRealm
首页我们得在resources文件夹下创建一个*.ini文件
内容如下
[users] //认证信息, 角色权限
ljj=123,admin
[roles] //角色权限限制
admin=user:delete,user:update
java代码
@Test
public void testAuthentication(){
//创建IniRealm对象
IniRealm iniRealm = new IniRealm("classpath:user.ini");
//1. 构建SecurityManager环境
DefaultSecurityManager manager = new DefaultSecurityManager();
manager.setRealm(iniRealm);//设置Realm环境
//2. 主体提交认证请求
SecurityUtils.setSecurityManager(manager);//设置SecurityManager环境
Subject subject = SecurityUtils.getSubject();//获得主体
UsernamePasswordToken token = new UsernamePasswordToken("ljj","123");//存放验证数据
subject.login(token);//进行验证
//是否认证成功
System.out.println(subject.isAuthenticated());
subject.checkRole("admin");
//判断是否具有对应操作的权限
subject.checkPermission("user:update");
}
大家可以尝试认证错误信息以及错误权限看看会发生什么情况
Shiro----jdbcRealm
感谢大家的关注, 关于连接数据库进行认证与授权的教程我将在Shiro实战教程中去编写, 点个赞支持一下作者.
Shiro—自定义
自定义Realm需要继承于AuthorizingRealm, 并且实现它的两个方法, 如下
/**
* 授权
* @param principalCollection 授权信息
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/**
* 认证
* @param authenticationToken 认证信息
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
return null;
}
如注释所描述一样, 在其方法中便可进行认证与授权了.
认证
public class CustomRealm extends AuthorizingRealm {
Map<String, String> userMap = new HashMap<>();
{
userMap.put("ljj","123");
//realmName可以随意取名
super.setName("customRealm");
}
/**
* 认证
* @param authenticationToken 认证信息
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1.从主体传过来的认证信息中,获取用户名
String userName = (String) authenticationToken.getPrincipal();
//2.通过用户名到数据库中获取凭证
String password = getPasswordByUserName(userName);
//如果不存在
if (password == null) {
return null;
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
"ljj",password,"customRealm");
return authenticationInfo;
}
/**
* 模拟数据库查询凭证
* 一般我们需要在这里连接数据进行验证, 由于我这只是一个小demo就直接写了一个map
* 大家可以去连接数据库进行验证
* @param userName
* @return
*/
private String getPasswordByUserName(String userName){
return userMap.get(userName);
}
测试类
@Test
public void testAuthentication(){
CustomRealm customRealm = new CustomRealm();
//1. 构建SecurityManager环境
DefaultSecurityManager manager = new DefaultSecurityManager();
manager.setRealm(customRealm);//配置环境
//2. 主体提交认证请求
SecurityUtils.setSecurityManager(manager);//设置SecurityManager环境
Subject subject = SecurityUtils.getSubject();//获得主体
UsernamePasswordToken token = new UsernamePasswordToken("ljj","123");//存放验证数据
subject.login(token);//进行验证
//是否认证成功
System.out.println(subject.isAuthenticated());
授权:
/**
* 授权
*
* @param principalCollection 授权信息
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String userName = (String) principalCollection.getPrimaryPrincipal();
//从数据库或者缓存中获取角色数据
Set<String> roles = getRolesByUserName(userName);
//从数据库或者缓存中获取权限数据
Set<String> permissions = getPermissionsByUserName(userName);
//创建SimpleAuthorizationInfo对象, 返回得到的数据
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//设置权限
simpleAuthorizationInfo.setStringPermissions(permissions);
//设置角色
simpleAuthorizationInfo.setRoles(roles);
return simpleAuthorizationInfo;
}
/**
* 模拟数据库查询权限
* @param userName
* @return
*/
private Set<String> getPermissionsByUserName(String userName) {
Set<String> sets = new HashSet<>();
sets.add("user:delete");
sets.add("user:add");
return sets;
}
/**
* 模拟数据库查询授权
* 这里和getPasswordByUserName一样
*
* @param userName
* @return
*/
private Set<String> getRolesByUserName(String userName) {
Set<String> sets = new HashSet<>();
sets.add("admin");
sets.add("user");
return sets;
}
测试类
在认证测试类中加上这两句, 同样,大家可以尝试一下错误的认证与授权, 看看会发生什么情况
//判断是否具有此角色信息
subject.checkRole("admin");
//判断是否具有对应操作的权限
subject.checkPermissions("user:add","user:delete");
Shiro加密
Shiro散列配置
- HashedCredentialsMatcher
- 自定义Realm中使用散列
- 盐的使用
首先我们先要创建一个HashedCredentialsMatcher工具类
@Test
public void testAuthentication(){
CustomRealm customRealm = new CustomRealm();
//1. 构建SecurityManager环境
DefaultSecurityManager manager = new DefaultSecurityManager();
manager.setRealm(customRealm);//配置环境
//创建工具类
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//设置加密的名称,加密方式
matcher.setHashAlgorithmName("md5");
//设置加密的次数
matcher.setHashIterations(1);
//在自定义的Realm中配置HashedCredentialsMatcher对象
customRealm.setCredentialsMatcher(matcher);
//2. 主体提交认证请求
SecurityUtils.setSecurityManager(manager);//设置SecurityManager环境
Subject subject = SecurityUtils.getSubject();//获得主体
UsernamePasswordToken token = new UsernamePasswordToken("ljj","123");//存放验证数据
subject.login(token);//进行验证
//是否认证成功
System.out.println(subject.isAuthenticated());
// subject.checkRole("admin");
//
// //判断是否具有对应操作的权限
// subject.checkPermissions("user:add","user:delete");
}
在自定义类中创建main方法, 使用md5获得加密后的密码
public static void main(String[] args) {
//得到加密数据
Md5Hash md5Hash = new Md5Hash("123");
System.out.println(md5Hash.toString());
}
更改map里的密码为加密后的数据(数据库里的密码都是经过加密的只不过我这里的map是用来模拟)
但是由于密码只进行了一次加密, 安全级别还是达不到我们所需要的级别, 所以我们需要进行加盐.
获得加盐数据
public static void main(String[] args) {
//得到加盐数据
Md5Hash md5Hash = new Md5Hash("123","ljj");
System.out.println(md5Hash.toString());
}
更改map里面的密码
我们还需要在认证方法中添加这样一句代码
authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(“ljj”)); 需要把所加的盐设置进SimpleAuthenticationInfo
/**
* 认证
*
* @param authenticationToken 认证信息
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1.从主体传过来的认证信息中,获取用户名
String userName = (String) authenticationToken.getPrincipal();
//2.通过用户名到数据库中获取凭证
String password = getPasswordByUserName(userName);
//如果不存在
if (password == null) {
return null;
}
//创建SimpleAuthenticationInfo,返回数据
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
"ljj", password, "customRealm");
//需要把所加的盐设置进来 直接将字符串转换成ByteSource对象
authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes("ljj"));
return authenticationInfo;
}
在下一篇博客中, 我将带着大家从数据库和缓存中获取数据, 跟真实的项目完全一致, 谢谢大家的关注
springBoot整合Shiro实战教程
https://blog.csdn.net/I_No_dream/article/details/93227335