# ShiroDemo
**Repository Path**: big_peng/ShiroDemo
## Basic Information
- **Project Name**: ShiroDemo
- **Description**: No description available
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 0
- **Created**: 2019-06-27
- **Last Updated**: 2020-12-19
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# Shiro权限管理框架
## Shiro简介
- Shiro是一个非常强大的Java安全框架,其主要功能有身份验证、权限控制等。该框架主要构成如下
- 
- 该框架在Spring的Java项目中主要实现的是用户登录拦截、权限识别拦截、密码匹配等功能。
- Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你可以把它认为是Shiro的“用户”概念。
- Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
- Subject中封装了用户的登录、退出方法,以及手动进行权限、角色的验证
- 当用户登录之后,Subject就记录了当前的登录用户的信息,调用logout方法之后删除
-
- SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
- Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。
- 从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
- Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。
- Subject: 即"用户",外部应用都是和Subject进行交互的,subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。 Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权(Subject相当于SecurityManager的门面)。
- SecurityManager: 即安全管理器,它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等,实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。此外SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口
- Authenticator:是一个执行对用户的身份验证(登录)的组件。通过与一个或多个Realm 协调来存储相关的用户/帐户信息。从Realm中找到对应的数据,明确是哪一个登陆人。如果存在多个realm,则接口AuthenticationStrategy(策略)会确定什么样算是登录成功(例如,如果一个Realm成功,而其他的均失败,是否登录成功?)。它是一个接口,shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器。
- Authorizer:即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。就是用来判断是否有权限,授权,本质就是访问控制,控制哪些URL可以访问.
- Realm:即领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,通常一个数据源配置一个realm.s比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。
## 测试项目
- [https://gitee.com/big_peng/ShiroDemo/tree/master/src/main/java/com/example/shiro_demo1/](https://gitee.com/big_peng/ShiroDemo/tree/master/src/main/java/com/example/shiro_demo1/)
## 数据库设计
```sql
/*
Navicat MySQL Data Transfer
Source Server : hahha
Source Server Version : 50721
Source Host : localhost:3306
Source Database : shiro_test
Target Server Type : MYSQL
Target Server Version : 50721
File Encoding : 65001
Date: 2019-06-27 18:21:49
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for permission
-- ----------------------------
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission` (
`pid` int(11) NOT NULL,
`name` varchar(20) DEFAULT NULL,
`url` varchar(20) DEFAULT NULL,
PRIMARY KEY (`pid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of permission
-- ----------------------------
INSERT INTO `permission` VALUES ('1', 'add', '/add');
INSERT INTO `permission` VALUES ('2', 'delete', '/delete');
INSERT INTO `permission` VALUES ('3', 'user', '/edit');
INSERT INTO `permission` VALUES ('4', 'query', '/query');
-- ----------------------------
-- Table structure for permission_role
-- ----------------------------
DROP TABLE IF EXISTS `permission_role`;
CREATE TABLE `permission_role` (
`rid` int(11) NOT NULL,
`pid` int(11) NOT NULL,
PRIMARY KEY (`pid`,`rid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of permission_role
-- ----------------------------
INSERT INTO `permission_role` VALUES ('1', '1');
INSERT INTO `permission_role` VALUES ('2', '1');
INSERT INTO `permission_role` VALUES ('1', '2');
INSERT INTO `permission_role` VALUES ('1', '3');
INSERT INTO `permission_role` VALUES ('1', '4');
INSERT INTO `permission_role` VALUES ('2', '4');
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`rid` int(11) NOT NULL,
`rname` varchar(255) DEFAULT NULL,
PRIMARY KEY (`rid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES ('1', 'admin');
INSERT INTO `role` VALUES ('2', 'customer');
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`uid` int(11) NOT NULL,
`username` varchar(20) DEFAULT NULL,
`password` varchar(20) DEFAULT NULL,
PRIMARY KEY (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'admin', '123456');
INSERT INTO `user` VALUES ('2', 'demo', '123456');
-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`rid` int(11) NOT NULL,
`uid` int(11) NOT NULL,
PRIMARY KEY (`uid`,`rid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES ('1', '1');
INSERT INTO `user_role` VALUES ('2', '2');
```
## 测试运行
- Shiro大致的运行过程:浏览器发送Request请求,该请求首先会进入Shiro配置类中的shiroFilterFactoryBean方法中配置好的请求过滤器中,对该请求url进行判断,如果为跳过认证的,则可以进入Controller执行。
- 如果是由于需要进行登录或者权限角色认证,则进行登录和权限角色认证操作
- 登录认证操作为验证当前Subject有没有用户进行过登录操作,如果有,则通过,反之不通过转向指定的请求。
- 登录认证操作放行之后进行权限角色认证,验证当前用户是否拥有该权限。如下图,则表示/user/**的请求需要当前登录的用户拥有user权限才可以放行。
- 
-
# Swagger接口测试插件
## Shiro中的盐加密
- 盐加密就是在原有的MD5加密的基础之上添加了一个盐值,也就是说需要有两个值来同时决定数据库中的加密之后的密码值
- 所以一般情况下盐加密过后的密码是没有办法通过反推来破解出原密码的
- 盐加密的Java代码
```java
public static void main(String[] args) {
//盐:为了即使相同的密码不同的盐加密后的结果也不同
ByteSource salt1 = ByteSource.Util.bytes("admin");
//加密方式
String hashAlgorithmName = "MD5";
//密码
String source = "123456";
//加密次数
int hashIterations = 1024;
SimpleHash result = new SimpleHash(hashAlgorithmName, source, salt1, hashIterations);
System.out.println(result.toHex());
}
```
- 在Shiro中添加盐加密认证,首先需要在ShiroConfiguration类中添加Realm验证身份的方法
```java
/**
* Realm在验证用户身份的时候,要进行密码匹配
* 最简单的情况就是明文直接匹配,然后就是加密匹配,这里的匹配工作则就是交给CredentialsMatcher来完成
* 支持任意数量的方案,包括纯文本比较、散列比较和其他方法。除非该方法重写,否则默认值为
* @return
*/
@Bean("hashedCredentialsMatcher")
public RetryLimitHashedCredentialsMatcher hashedCredentialsMatcher() {
RetryLimitHashedCredentialsMatcher credentialMatcher = new RetryLimitHashedCredentialsMatcher();
// 采用MD5方式加密
credentialMatcher.setHashAlgorithmName("MD5");
// 设置加密次数
credentialMatcher.setHashIterations(1024);
return credentialMatcher;
}
```
- 另外还需要将该类中的定义Realm的方法传入的参数修改为上述的bean对象
- 接下来需要将自定义的Realm类中的登录认证的方法进行重写
```java
/**
* 用户的登录认证的方法
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("将用户,密码填充完UsernamePasswordToken之后,进行subject.login(token)之后");
//这边是界面的登陆数据,将数据封装成token
UsernamePasswordToken userpasswordToken = (UsernamePasswordToken) authenticationToken;
String username = userpasswordToken.getUsername();
User user = userService.findByUsername(username);
SimpleAuthenticationInfo info = null;
if (null!=user){
info = new SimpleAuthenticationInfo(user, user.getPassword(), this.getClass().getName());
info.setCredentialsSalt(ByteSource.Util.bytes(user.getSalt()));
}else{
throw new UnknownAccountException("用户不存在");
}
return info;
}
```
- 此外还可以将Shiro配置类中的获取密码验证的方法返回值修改为我们自定义的密码验证类,只需要继承原有的HashedCredentialsMatcher类即可
```java
package com.example.shiro_demo1.shiro;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
/**
* 自定义密码验证类
* @author cxp
* @version 1.0
* @date 2019/7/1 14:29
*/
public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher {
/**
* 自定义密码匹配验证方法
* 只是在原有的密码验证基础之上添加了密码错误的异常
* @param token
* @param info
* @return
*/
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
//调用父类的密码匹配验证方法
boolean matches = super.doCredentialsMatch(token, info);
System.out.println(matches);
if(!matches){
throw new IncorrectCredentialsException("密码错误");
}
return true;
}
}
```
- 比较完整的Springboot+shiro盐加密https://blog.csdn.net/qq_21046665/article/details/79782865
- https://blog.csdn.net/Colton_Null/article/details/78992836