Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
Web Support:Web支持,可以非常容易的集成到Web环境;
Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
Testing:提供测试支持;
Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
三个核心组件:Subject, SecurityManager 和 Realms.
下面进行传统的权限认证方式和RBAC认证方式的比较
特点:为每个人单独的分配权限模块,能够实现权限控制,但是当公司人员庞大之后,非常难管理。
上述权限控制如何设计表?
关系:员工和菜单权限的关系:多对多
员工id | 菜单名称 |
---|---|
1 | 取派管理 |
2 | 快递员管理 |
2 | 运单管理 |
好处:可以方便的 实现权限控制
缺陷:比如当修改权限的时候,公司统一的给组长级别的人 加一个“计算工资”权限,这时候,得修改权限表中所有组长的权限,每个组长在数据库中都得增加一条“计算工资”记录的权限
后来,这个“计算工资”的功能,在给组长之后,发现,这个权限不合适,得收回这个权限,这个时候,需要删除多条记录
Role Based Access Controller :基于角色的访问控制
前无古人后无来者
其实到这,RBAC认证方式的优点已经显而易见了。
好了,到这你已经大概了解Shiro是干嘛用的了。
下面是SpringBoot整合Shiro的简单使用
前提:能独力创建SpringBoot项目(SpringBoot快速入门)
步骤1:SpringBoot项目已经创建好了,导入maven坐标
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.czxygroupId>
<artifactId>shiro-demoartifactId>
<version>0.0.1-SNAPSHOTversion>
<packaging>jarpackaging>
<name>shiro-demoname>
<description>Demo project for Spring Bootdescription>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.1.RELEASEversion>
<relativePath/>
parent>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>1.3.2version>
dependency>
<dependency>
<groupId>tk.mybatisgroupId>
<artifactId>mapper-spring-boot-starterartifactId>
<version>2.0.2version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.46version>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>1.3.2version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
步骤2:导入sql语句
/* Navicat Premium Data Transfer Source Server : mysql Source Server Type : MySQL Source Server Version : 50559 Source Host : localhost:3306 Source Schema : shiro_demo Target Server Type : MySQL Target Server Version : 50559 File Encoding : 65001 Date: 09/12/2018 21:11:53 author https://blog.csdn.net/chen_2890 */
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for permission
-- ----------------------------
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission` (
`pid` int(11) NOT NULL AUTO_INCREMENT,
`description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`keyword` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`pid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1005 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of permission
-- ----------------------------
INSERT INTO `permission` VALUES (1001, '添加功能', 'add', '添加');
INSERT INTO `permission` VALUES (1002, '查询功能', 'select', '查询');
INSERT INTO `permission` VALUES (1003, '更新功能', 'update', '更新');
INSERT INTO `permission` VALUES (1004, '删除功能', 'delete', '删除');
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`rid` int(11) NOT NULL AUTO_INCREMENT,
`description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`keyword` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`rid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1004 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1001, '普通用户', 'Commom', '普通用户');
INSERT INTO `role` VALUES (1002, '一般会员', 'Member', '会员用户');
INSERT INTO `role` VALUES (1003, '超级会员', 'Vip', 'VIP用户');
-- ----------------------------
-- Table structure for role_permission
-- ----------------------------
DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission` (
`role_id` int(11) NOT NULL,
`permission_id` int(11) NOT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of role_permission
-- ----------------------------
INSERT INTO `role_permission` VALUES (1001, 1001);
INSERT INTO `role_permission` VALUES (1001, 1002);
INSERT INTO `role_permission` VALUES (1002, 1001);
INSERT INTO `role_permission` VALUES (1002, 1002);
INSERT INTO `role_permission` VALUES (1002, 1003);
INSERT INTO `role_permission` VALUES (1003, 1002);
INSERT INTO `role_permission` VALUES (1003, 1001);
INSERT INTO `role_permission` VALUES (1003, 1003);
INSERT INTO `role_permission` VALUES (1003, 1004);
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`password` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`age` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`uid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'jack', 'ae7f487d56152e165afdfd87c2b819a5', 18);
INSERT INTO `user` VALUES (2, 'tom', 'cc804223edc8063d7b3d9dc94b81fba3', 19);
INSERT INTO `user` VALUES (3, 'rose', 'c89f94fdfb8ae723413296a03c0f8d3b', 20);
-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`role_id` int(11) NOT NULL,
`uid` int(11) NOT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES (1001, 1);
INSERT INTO `user_role` VALUES (1001, 2);
INSERT INTO `user_role` VALUES (1002, 2);
INSERT INTO `user_role` VALUES (1001, 3);
INSERT INTO `user_role` VALUES (1002, 3);
INSERT INTO `user_role` VALUES (1003, 3);
SET FOREIGN_KEY_CHECKS = 1;
步骤3:导入Shiro配置类
package com.czxy.config;
import com.czxy.shiro.ShiroCredentialsMatcher;
import com.czxy.shiro.ShiroRealm;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
/** * 功能描述: 在ShiroConfig中做什么事情呢? * 1 配置shiro安全管理器,向安全管理器中注入Realm域 * 2 配置Realm域:注入密码比较器 * 3 配置密码比较器 * 4 配置拦截路径和放行路径 * @author https://blog.csdn.net/chen_2890 * @date 2018/12/9 20:32 */
@Configuration
public class ShiroConfig {
/** * 配置安全管理器,并且注入Realm域 * @param realm */
@Bean
public SecurityManager securityManager(Realm realm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
return securityManager;
}
/** * Credentials:凭证/证书 --- * * 配置Realm域,注入密码比较器 * @param credentialsMatcher */
@Bean
public ShiroRealm realm(CredentialsMatcher credentialsMatcher){
ShiroRealm shiroRealm = new ShiroRealm();
shiroRealm.setCredentialsMatcher(credentialsMatcher);
return shiroRealm;
}
/** * 密码比较器 */
@Bean
public CredentialsMatcher credentialsMatcher(){
return new ShiroCredentialsMatcher();
}
/** * 配置拦截路径和放行路径 * @param securityManager */
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){
// shiro过滤器工厂类
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
//拦截器----Map集合
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/login*", "anon");
filterChainDefinitionMap.put("/index.html*", "anon");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/images/**", "anon");
//根据用户角色赋予相应的权限
filterChainDefinitionMap.put("/add-success.html", "roles[Commom]");
filterChainDefinitionMap.put("/selete-success.html", "roles[Commom]");
filterChainDefinitionMap.put("/update-success.html", "roles[Member]");
filterChainDefinitionMap.put("/delete-success.html", "roles[Vip]");
//根据用户拥有的具体权限赋予相应的权限
filterChainDefinitionMap.put("/add-success.html", "perms[add]");
filterChainDefinitionMap.put("/selete-success.html", "perms[select]");
filterChainDefinitionMap.put("/update-success.html", "perms[update]");
filterChainDefinitionMap.put("/delete-success.html", "perms[delete]");
// /** 匹配所有的路径 // 通过Map集合组成了一个拦截器链 ,自顶向下过滤,一旦匹配,则不再执行下面的过滤 // 如果下面的定义与上面冲突,那按照了谁先定义谁说了算 // /** 一定要配置在最后 filterChainDefinitionMap.put("/**", "authc"); // 将拦截器链设置到shiro中 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 shiroFilterFactoryBean.setLoginUrl("/index.html"); // 登录成功后要跳转的链接 shiroFilterFactoryBean.setSuccessUrl("/button.html"); //未授权界面; shiroFilterFactoryBean.setUnauthorizedUrl("/power.html"); return shiroFilterFactoryBean; } /** * 开启shiro aop注解支持 * 使用代理方式;所以需要开启代码支持 * @param securityManager */
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/** * 开启cglib代理 */
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
}
步骤4:创建Realm域
package com.czxy.shiro;
import com.czxy.domain.Permission;
import com.czxy.domain.Role;
import com.czxy.domain.User;
import com.czxy.service.PermissionService;
import com.czxy.service.RoleService;
import com.czxy.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
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.springframework.beans.factory.annotation.Autowired;
import java.util.List;
/** * 功能描述:ShiroRealm域 * @author https://blog.csdn.net/chen_2890 * @date 2018/12/5 19:42 */
public class ShiroRealm extends AuthorizingRealm {
/** * 描述:userService对象 * @date 2018/12/9 20:54 */
@Autowired
private UserService userService;
/** * 描述:roleService对象 * @date 2018/12/9 20:54 */
@Autowired
private RoleService roleService;
/** * 描述:permissionService对象 * @date 2018/12/9 20:54 */
@Autowired
private PermissionService permissionService;
/** * 功能描述:shiro认证 * @param token * @return org.apache.shiro.authc.AuthenticationInfo * @author https://blog.csdn.net/chen_2890 * @date 2018/12/5 19:42 **/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取用户输入的用户名和密码
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
//获取用户名
String username = upToken.getUsername();
//根据用户名去数据库查询
User user = userService.findUserByUsername(username);
//用户名不存在
if(user == null){
return null;
}
//用户名存在,进去密码比较器比较密码
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), this.getName());
return authenticationInfo;
}
/** * 功能描述:shiro授权 * @param principal * @return org.apache.shiro.authz.AuthorizationInfo * @author https://blog.csdn.net/chen_2890 * @date 2018/12/9 20:55 **/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
//创建授权对象
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//获取已经认证通过的用户信息
User user = (User) SecurityUtils.getSubject().getPrincipal();
//根据用户信息查找对应的角色
List<Role> roleList = roleService.findRoleByUser(user);
for (Role role : roleList) {
authorizationInfo.addRole(role.getKeyword());
}
//根据用户信息查找对应的权限
List<Permission> permissionList = permissionService.findPermissionByUser(user);
for (Permission permission : permissionList) {
authorizationInfo.addStringPermission(permission.getKeyword());
}
return authorizationInfo;
}
}
步骤5:创建shiro认证器
package com.czxy.shiro;
import com.czxy.util.Encrypt;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
/** * 功能描述:shiro认证器:ShiroCredentialsMatcher * @author https://blog.csdn.net/chen_2890 * @date 2018/12/9 20:47 */
public class ShiroCredentialsMatcher extends SimpleCredentialsMatcher {
/** * 功能描述:shiro的密码比较器 * @param token : 用户页面输入的信息 * @param info : 数据库中的信息 * @return boolean * @author https://blog.csdn.net/chen_2890 * @date 2018/12/9 20:49 **/
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
//获取用户输入的用户名和密码
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
//得到密码凭证
char[] pwd = upToken.getPassword();
//转换为string类型
String myPwd = new String(pwd);
//将用户输入的密码转为密文
String newPwd = Encrypt.md5(myPwd, upToken.getUsername());
//获取数据库中的密文密码
Object dbPwd = info.getCredentials();
//密码验证
return equals(newPwd, dbPwd);
}
}
步骤6:导入加密工具类
package com.czxy.util;
import org.apache.shiro.crypto.hash.*;
/** * 功能描述:加密工具 * @author https://blog.csdn.net/chen_2890 * @date 2018/12/5 23:29 */
public class Encrypt {
/** * 功能描述:高强度加密算法,不可逆 * @param password:密码 * @param salt:盐 * @return java.lang.String * @author https://blog.csdn.net/chen_2890 * @date 2018/12/5 23:29 **/
public static String md5(String password, String salt){
return new Md5Hash(password,salt,2).toString();
}
/** * 功能描述:用于测试的main方法 * @param args * @author https://blog.csdn.net/chen_2890 * @date 2018/12/5 23:31 **/
public static void main(String[] args) {
//ae7f487d56152e165afdfd87c2b819a5
System.out.println(new Md5Hash("123456","jack",2).toString());
//cc804223edc8063d7b3d9dc94b81fba3
System.out.println(new Md5Hash("123456","tom",2).toString());
//c89f94fdfb8ae723413296a03c0f8d3b
System.out.println(new Md5Hash("123456","rose",2).toString());
System.out.println();
System.out.println(md5("123456","jack"));
System.out.println(md5("123456","tom"));
System.out.println(md5("123456","rose"));
}
}
步骤7:编写LoginController控制器
package com.czxy.controller;
import com.czxy.domain.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
/** * 功能描述:LoginController * @author https://blog.csdn.net/chen_2890 * @date 2018/12/5 19:41:01 */
@RestController
public class LoginController {
/** * 描述:session对象 * @date 2018/12/9 20:35 */
@Autowired
private HttpSession session;
/** * 功能描述:login,即shiro的认证 * @param user * @return org.springframework.http.ResponseEntity * @author https://blog.csdn.net/chen_2890 * @date 2018/12/9 20:35 **/
@GetMapping("/login")
public ResponseEntity<Void> login(User user){
//1 接收页面参数,转成对象----系统自动完成了
//2 获取Subject对象
Subject subject = SecurityUtils.getSubject();
//3 Subject启动Shiro
// 准备数据
UsernamePasswordToken upToken = new UsernamePasswordToken(user.getUsername(), user.getPassword());
try{
subject.login(upToken);
// 能够执行到这步,肯定登录已经成功
// 获取用户信息,保存到session中
User loginUser = (User) subject.getPrincipal();
// 放入session中
session.setAttribute("loginUser",loginUser);
//返回状态
return new ResponseEntity<>(HttpStatus.OK);
} catch ( UnknownAccountException uae ) {
//用户名未知...
System.out.println("用户不存在");
} catch ( IncorrectCredentialsException ice ) {
//凭据不正确,例如密码不正确 ...
System.out.println("密码不正确");
} catch ( LockedAccountException lae ) {
//用户被锁定,例如管理员把某个用户禁用...
System.out.println("用户被禁用");
} catch ( ExcessiveAttemptsException eae ) {
//尝试认证次数多余系统指定次数 ...
System.out.println("请求次数过多,用户被锁定");
} catch ( AuthenticationException ae ) {
//其他未指定异常
System.out.println("未知错误,无法完成登录");
}
//返回状态
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
/** * 功能描述: shiro的logout退出,只是将放置PrincipalCollection这个集合置空, * 删除了session,但是没有清空缓存,需要手动清除缓存 * @return org.springframework.http.ResponseEntity * @author https://blog.csdn.net/chen_2890 * @date 2018/12/9 20:35 **/
@PutMapping("/logout")
public ResponseEntity<Void> logout(){
//清空session
session.removeAttribute("loginUser");
//退出shiro
SecurityUtils.getSubject().logout();
//返回状态
return new ResponseEntity<>(HttpStatus.OK);
}
}
最后一步:核心代码已全部给出,最后一步就是创建相应的类实现方法了。
考虑到有人实在是懒,下面已经备好了一份demo,仅供参考。
shiro-demo.rar
提取码:seq6
好了,到这里SpringBoot整合Shiro完毕了
要是还有不太明白的地方请留言,评论必回
要是对我的文章感兴趣的话,关注一下吧,谢谢!