Springboot整合Shiro,用ehcache做缓存+mybatis-plus
该项目源于尚硅谷2022shiro,视频连接
尚硅谷2022新版Shiro安全框架实例教程(4h深入掌握shiro)
数据库代码:
/*
Navicat Premium Data Transfer
Source Server : localhost_3306
Source Server Type : MySQL
Source Server Version : 80013
Source Host : localhost:3306
Source Schema : shirodb
Target Server Type : MySQL
Target Server Version : 80013
File Encoding : 65001
Date: 11/12/2022 00:50:53
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for permissions
-- ----------------------------
DROP TABLE IF EXISTS `permissions`;
CREATE TABLE `permissions` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
`name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限名',
`info` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限信息',
`desc` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '权限表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of permissions
-- ----------------------------
INSERT INTO `permissions` VALUES (1, '删除用户', 'user:delete', '删除用户');
INSERT INTO `permissions` VALUES (2, '新增用户', 'user:add', '新增用户');
INSERT INTO `permissions` VALUES (3, '修改用户', 'user:edit', '修改用户');
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
`name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名',
`desc` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
`realname` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色显示名',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, 'admin', '所以权限', '管理员');
INSERT INTO `role` VALUES (2, 'userMag', '用户管理权限', '用户管理');
-- ----------------------------
-- Table structure for role_ps
-- ----------------------------
DROP TABLE IF EXISTS `role_ps`;
CREATE TABLE `role_ps` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
`rid` bigint(20) NULL DEFAULT NULL COMMENT '角色 id',
`pid` bigint(20) NULL DEFAULT NULL COMMENT '权限 id',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色权限映射表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of role_ps
-- ----------------------------
INSERT INTO `role_ps` VALUES (1, 1, 1);
INSERT INTO `role_ps` VALUES (2, 1, 2);
INSERT INTO `role_ps` VALUES (3, 1, 3);
-- ----------------------------
-- Table structure for role_user
-- ----------------------------
DROP TABLE IF EXISTS `role_user`;
CREATE TABLE `role_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
`uid` bigint(20) NULL DEFAULT NULL COMMENT '用户 id',
`rid` bigint(20) NULL DEFAULT NULL COMMENT '角色 id',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色用户映射表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of role_user
-- ----------------------------
INSERT INTO `role_user` VALUES (1, 1, 1);
INSERT INTO `role_user` VALUES (2, 1, 2);
INSERT INTO `role_user` VALUES (3, 2, 2);
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
`name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户名',
`pwd` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
`rid` bigint(20) NULL DEFAULT NULL COMMENT '角色编号',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, '张三', '7174f64b13022acd3c56e2781e098a5f', NULL);
INSERT INTO `user` VALUES (2, '李四', '7174f64b13022acd3c56e2781e098a5f', NULL);
SET FOREIGN_KEY_CHECKS = 1;
数据库结构:
对应的pom文件:
<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
<artifactId>shiro_springboot</artifactId>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.9.0</version>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--配置Thymeleaf与Shrio的整合依赖-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<!--Shiro整合EhCache-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.2</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>2.4</version>
<type>maven-plugin</type>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.7</version>
<type>maven-plugin</type>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12.4</version>
<type>maven-plugin</type>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>3.3</version>
<type>maven-plugin</type>
</dependency>
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
<version>1.9.22</version>
</dependency>
</dependencies>
<resource>
<directory>src/main/resources</directory>
</resource>
</project>
对应的yml配置文件
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:mapper/*.xml
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/shirodb?characterEncoding=utf-8&useSSL=false
username: root
password: 123456
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
spring.thymeleaf.content-type: text/html
spring.thymeleaf.cache: false
spring.thymeleaf.mode: LEGACYHTML5
shiro:
loginUrl: /myController/login
对应的echache-shiro.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="ehcache" updateCheck="false">
<!--磁盘的缓存位置-->
<diskStore path="java.io.tmpdir"/>
<!--默认缓存-->
<defaultCache
maxEntriesLocalHeap="1000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="3600"
overflowToDisk="false">
</defaultCache>
<!--登录认证信息缓存:缓存用户角色权限-->
<cache name="loginRolePsCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true"/>
</ehcache>
前端页面代码:
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Shiro登录认证</h1>
<br>
<form action="/myController/userLogin">
<div>用户名:<input type="text" name="name" value=""></div>
<div>密码:<input type="password" name="pwd" value=""></div>
<div>记住用户:<input type="checkbox" name="rememberMe" value="true"></div>
<div><input type="submit" value="登录"></div>
</form>
</body>
</html>
main.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Shiro登录认证后主页面</h1>
<br>
登录用户为:<span th:text="${session.user}"></span>
<br>
<a href="/logout">登出</a>
<br>
<shiro:hasRole name="admin">
<a href="/myController/userLoginRoles">测试授权-角色验证</a>
</shiro:hasRole>
<br>
<a shiro:hasPermission="user:delete" href="/myController/userPermissions">测试授权-权限验证</a>
</body>
实体类:
package com.atguigu.shiro.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private String pwd;
private Integer rid;
}
mapper层:
package com.atguigu.shiro.mapper;
import com.atguigu.shiro.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface UserMapper extends BaseMapper<User> {
@Select("SELECT NAME FROM role WHERE id IN (SELECT rid FROM role_user WHERE uid=(SELECT id FROM USER WHERE NAME=#{principal}))")
List<String> getUserRoleInfoMapper(@Param("principal")String principal );
@Select({
"<script>",
"select info FROM permissions WHERE id IN ",
"(SELECT pid FROM role_ps WHERE rid IN (",
"SELECT id FROM role WHERE NAME IN ",
"<foreach collection='roles' item='name' open='(' separator=',' close=')'>",
"#{name}",
"</foreach>",
"))",
"</script>"
})
List<String> getUserPermissionInfoMapper(@Param("roles")List<String> roles);
}
service层:
package com.atguigu.shiro.service;
import com.atguigu.shiro.entity.User;
import java.util.List;
public interface UserService {
//用户登录
User getUserInfoByName(String name);
//根据用户查询角色信息
List<String> getUserRoleInfo(String principal);
//获取用户角色权限信息
List<String> getUserPermissionInfo(List<String> roles);
}
package com.atguigu.shiro.service.impl;
import com.atguigu.shiro.entity.User;
import com.atguigu.shiro.mapper.UserMapper;
import com.atguigu.shiro.service.UserService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User getUserInfoByName(String name) {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name",name);
User user = userMapper.selectOne(wrapper);
return user;
}
//根据用户查询角色信息
@Override
public List<String> getUserRoleInfo(String principal) {
return userMapper.getUserRoleInfoMapper(principal);
}
//获取用户角色权限信息
@Override
public List<String> getUserPermissionInfo(List<String> roles) {
return userMapper.getUserPermissionInfoMapper(roles);
}
}
controller层:
package com.atguigu.shiro.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpSession;
@Controller
@RequestMapping("myController")
public class MyController {
//跳转登录页面
@GetMapping("login")
public String login(){
return "login";
}
@GetMapping("userLogin")
public String userLogin(String name, String pwd,
@RequestParam(defaultValue = "false")boolean rememberMe,
HttpSession session){
//1获取subject对象
Subject subject = SecurityUtils.getSubject();
//2封装请求数据到token
AuthenticationToken token = new UsernamePasswordToken(name,pwd,rememberMe);
//3调用login方法进行登录认证
try {
subject.login(token);
//return "登录成功";
session.setAttribute("user",token.getPrincipal().toString());
return "main";
} catch (AuthenticationException e) {
e.printStackTrace();
System.out.println("登录失败");
return "登录失败";
}
}
//登录认证验证rememberMe
@GetMapping("userLoginRm")
public String userLogin(HttpSession session) {
session.setAttribute("user","rememberMe");
return "main";
}
//登录认证验证角色
@RequiresRoles("admin")
@GetMapping("userLoginRoles")
@ResponseBody
public String userLoginRoles(){
System.out.println("登录认证验证角色");
return "验证角色成功";
}
//登录认证验证权限
@RequiresPermissions("user:delete")
@GetMapping("userPermissions")
@ResponseBody
public String userLoginPermissions(){
System.out.println("登录认证验证权限");
return "验证权限成功";
}
}
异常处理:
package com.atguigu.shiro.controller;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
public class PermissionsException {
@ResponseBody
@ExceptionHandler(UnauthorizedException.class)
public String unauthorizedException(Exception e){
return "无权限";
}
@ResponseBody
@ExceptionHandler(AuthorizationException.class)
public String authorizationException(Exception e){
return "权限认证失败";
}
}
配置类文件:
这里采用的是3次md5加密加盐值的方式对密码进行加密
package com.atguigu.shiro.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.atguigu.shiro.realm.MyRealm;
import net.sf.ehcache.CacheManager;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.io.ResourceUtils;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
import java.io.InputStream;
@Configuration
public class ShiroConfig {
@Autowired
private MyRealm myRealm;
//配置SecurityManager
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(){
//1创建defaultWebSecurityManager 对象
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//2创建加密对象,设置相关属性
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//2.1采用md5加密
matcher.setHashAlgorithmName("md5");
//2.2迭代加密次数
matcher.setHashIterations(3);
//3将加密对象存储到myRealm中
myRealm.setCredentialsMatcher(matcher);
//4将myRealm存入defaultWebSecurityManager 对象
defaultWebSecurityManager.setRealm(myRealm);
// //4.5设置rememberMe
defaultWebSecurityManager.setRememberMeManager(rememberMeManager());
// //4.6设置缓存管理器
defaultWebSecurityManager.setCacheManager(getEhCacheManager());
//5返回
return defaultWebSecurityManager;
}
//
//缓存管理器
public EhCacheManager getEhCacheManager(){
EhCacheManager ehCacheManager = new EhCacheManager();
InputStream is =null;
try {
is = ResourceUtils.getInputStreamForPath("classpath:ehcache/ehcache-shiro.xml");
} catch (IOException e) {
e.printStackTrace();
}
CacheManager cacheManager = new CacheManager(is);
ehCacheManager.setCacheManager(cacheManager);
return ehCacheManager;
}
//cookie属性设置
public SimpleCookie rememberMeCookie(){
SimpleCookie cookie = new SimpleCookie("rememberMe");
//设置跨域
//cookie.setDomain(domain);
cookie.setPath("/");
cookie.setHttpOnly(true);
cookie.setMaxAge(30*24*60*60);
return cookie;
}
//创建Shiro的cookie管理对象
public CookieRememberMeManager rememberMeManager(){
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
cookieRememberMeManager.setCipherKey("1234567890987654".getBytes());
return cookieRememberMeManager;
}
//配置Shiro内置过滤器拦截范围
@Bean
public DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){
DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
//设置不认证可以访问的资源
definition.addPathDefinition("/myController/userLogin","anon");
definition.addPathDefinition("/myController/login","anon");
//设置登出过滤器
definition.addPathDefinition("/logout","logout");
//设置需要进行登录认证的拦截范围
definition.addPathDefinition("/**","authc");
//添加存在用户的过滤器(rememberMe)
definition.addPathDefinition("/**","user");
return definition;
}
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
}
Realm:
该类实现登录和授权方法
package com.atguigu.shiro.realm;
import com.atguigu.shiro.entity.User;
import com.atguigu.shiro.service.UserService;
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;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
//自定义授权方法:获取当前登录用户的角色、权限信息,返回给shiro用来进行授权认证
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("自定义授权方法");
//1获取用户身份信息
String principal = principalCollection.getPrimaryPrincipal().toString();
//2调用业务层获取用户的角色信息(数据库)
List<String> roles = userService.getUserRoleInfo(principal);
System.out.println("当前用户角色信息 = " + roles);
//2.5调用业务层获取用户的权限信息(数据库)
List<String> permissions = userService.getUserPermissionInfo(roles);
System.out.println("当前用户权限信息 = " + permissions);
//3创建对象,封装当前登录用户的角色、权限信息
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRoles(roles);
info.addStringPermissions(permissions);
//4返回信息
return info;
// return null;
}
//自定义登录认证方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1获取用户身份信息
String name = authenticationToken.getPrincipal().toString();
//2调用业务层获取用户信息(数据库)
User user = userService.getUserInfoByName(name);
//3非空判断,将数据封装返回
if(user !=null){
AuthenticationInfo info = new SimpleAuthenticationInfo(
authenticationToken.getPrincipal(),
user.getPwd(),
ByteSource.Util.bytes("salt"),
authenticationToken.getPrincipal().toString()
);
return info;
}
return null;
}
}
附上运行截图:这里密码是z3
这里张三是有相应的角色和权限的,在输出台可以看到