SpringBoot集成Shiro前后端分离使用redis做缓存

文章目录

  • 一 、shiro介绍
    • 1、基础介绍
    • 2、基本功能点
    • 3、基本流程图
  • 二、 常用的权限管理表关系
    • 2.1. 表组成
    • 2.2. 表结构
  • 三、实战案例
    • 3.1. 案例介绍
    • 3.2. 依赖
    • 3.3. Shiro全局配置
    • 3.4. 自定义ShiroRealm
    • 3.5. ShiroUtils
    • 3.6. 自定义SessionManager
    • 3.7. 登录/出主方法
    • 3.8. 测试主方法
  • 四、 前后端分离需要注意的点
  • 五、测试验链接
  • 六、测试验证
    • 6.1. 登录测试
      • 6.1. 1. 链接
      • 6.1. 2. 参数
      • 6.1. 3. 登陆后的token
    • 6.2. 获取用户列表
      • 6.2. 1. 链接
      • 6.2. 2. 参数
    • 6.3. 获取用户详情
      • 6.3. 1. 链接
      • 6.3. 2. 参数
    • 6.4. 添加用户
      • 6.4. 1. 链接
      • 6.4. 2. 参数
    • 6.5. 删除用户
      • 6.5. 1. 链接
      • 6.5. 2. 参数
    • 6.6. 注销登录
      • 6.6. 1. 链接
      • 6.6. 2. 参数

技术选型

框架说明版本
环境JDK1.8
后台SpringBoot2.1.7.RELEASE
权限控制Shiro1.4.0
数据库Mysql8.0.17
数据源druid1.1.10
持久层mybatis1.3.2
缓存shiro-redis3.1.0

一 、shiro介绍

1、基础介绍

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。作为一款安全框架Shiro的设计相当巧妙。Shiro的应用不依赖任何容器,它不仅可以在JavaEE下使用,还可以应用在JavaSE环境中。

2、基本功能点

在这里插入图片描述

1、Authentication:身份认证/登录,验证用户是不是拥有相应的身份。

2、Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限。

3、Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的。

4、Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储。

5、Web Support:Web支持,可以非常容易的集成到Web环境。

6、Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率。

7、Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去。

8、Testing:提供测试支持。

9、Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问。

10、Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

3、基本流程图

在这里插入图片描述
在这里插入图片描述

Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者。

SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器。

Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。

    流程如下:步骤一:Shiro把用户的数据封装成标识token,token一般封装着用户名,密码等信息。步骤二:使用Subject门面获取到封装着用户的数据的标识token步骤三:Subject把标识token交给SecurityManager,在SecurityManager安全中心中,SecurityManager把标识token委托给认证器Authenticator进行身份验证。认证器的作用一般是用来指定如何验证,它规定本次认证用到哪些Realm。步骤四:认证器Authenticator将传入的标识token,与数据源Realm对比,验证token是否合法。

二、 常用的权限管理表关系

2.1. 表组成

  • 5张表,也就是现在流行的权限设计模型RBAC
    分别是:用户表 ,角色表,菜单(权限)表 , 用户和角色关联表,角色和菜单关联表

在这里插入图片描述

2.2. 表结构

-- 表结构总览--
-- 权限表--
DROP TABLE IF EXISTS `menu`;CREATE TABLE `menu` (`menu_id` bigint(20) NOT NULL AUTO_INCREMENT,`parent_id` bigint(20) DEFAULT NULL COMMENT '父菜单ID,一级菜单为0',`name` varchar(50) DEFAULT NULL COMMENT '菜单名称',`url` varchar(200) DEFAULT NULL COMMENT '菜单URL',`perms` varchar(500) DEFAULT NULL COMMENT '授权(多个用逗号分隔,如:user:list,user:create)',`type` int(11) DEFAULT NULL COMMENT '类型   0:目录   1:菜单   2:按钮',`icon` varchar(50) DEFAULT NULL COMMENT '菜单图标',`order_num` int(11) DEFAULT NULL COMMENT '排序',`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`menu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='菜单管理';LOCK TABLES `menu` WRITE;INSERT INTO `menu` (`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`, `create_time`, `update_time`)
VALUES(1,0,'权限菜单','menu/list','menu:list',0,'system',0,'2019-08-30 03:06:59','2019-08-30 07:38:01'),(2,0,'用户列表','user/list','user:list',0,'user',0,'2019-08-30 07:38:46','2019-08-30 07:38:56'),(3,0,'用户详情','user/detail','user:detail',0,'user',0,'2019-08-30 07:38:52','2019-08-30 07:39:43'),(4,0,'添加用户','user/add','user:add',0,'user',0,'2019-08-30 07:38:52','2019-08-30 07:42:54');UNLOCK TABLES;-- 角色表表--
DROP TABLE IF EXISTS `role`;CREATE TABLE `role` (`role_id` bigint(20) NOT NULL AUTO_INCREMENT,`role_name` varchar(100) DEFAULT NULL COMMENT '角色名称',`remark` varchar(100) DEFAULT NULL COMMENT '备注',`create_user_id` bigint(20) DEFAULT NULL COMMENT '创建者ID',`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色';LOCK TABLES `role` WRITE;INSERT INTO `role` (`role_id`, `role_name`, `remark`, `create_user_id`, `create_time`, `update_time`)
VALUES
(1,'admin','超级管理员',1,'2019-08-30 07:41:08','2019-08-30 07:41:08');UNLOCK TABLES;-- 角色和权限的关系表--
DROP TABLE IF EXISTS `role_menu`;CREATE TABLE `role_menu` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`role_id` bigint(20) DEFAULT NULL COMMENT '角色ID',`menu_id` bigint(20) DEFAULT NULL COMMENT '菜单ID',`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色与菜单对应关系';LOCK TABLES `role_menu` WRITE;INSERT INTO `role_menu` (`id`, `role_id`, `menu_id`, `create_time`, `update_time`)
VALUES(1,1,1,'2019-08-30 07:41:16','2019-08-30 07:41:16'),(3,1,3,'2019-08-30 07:41:28','2019-08-30 07:41:28'),(4,1,4,'2019-08-30 07:46:14','2019-08-30 07:46:14');UNLOCK TABLES;-- 用户表--
DROP TABLE IF EXISTS `user`;CREATE TABLE `user` (`user_id` bigint(20) NOT NULL AUTO_INCREMENT,`username` varchar(50) NOT NULL COMMENT '用户名',`password` varchar(100) DEFAULT NULL COMMENT '密码',`salt` varchar(20) DEFAULT NULL COMMENT '盐',`email` varchar(100) DEFAULT NULL COMMENT '邮箱',`mobile` varchar(100) DEFAULT NULL COMMENT '手机号',`name` varchar(100) DEFAULT NULL COMMENT '姓名',`status` tinyint(4) DEFAULT NULL COMMENT '状态  0:禁用   1:正常',`create_user_id` bigint(20) DEFAULT NULL COMMENT '创建者ID',`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`user_id`),UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='系统用户';LOCK TABLES `user` WRITE;INSERT INTO `user` (`user_id`, `username`, `password`, `salt`, `email`, `mobile`, `name`, `status`, `create_user_id`, `create_time`, `update_time`)
VALUES(1,'admin','3743a4c09a17e6f2829febd09ca54e627810001cf255ddcae9dabd288a949c4a','123','123@gblfy.com','18967835678',NULL,1,1,'2019-01-18 11:11:11','2019-01-18 11:11:11');UNLOCK TABLES;-- 用户和角色关系表
DROP TABLE IF EXISTS `user_role`;CREATE TABLE `user_role` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',`role_id` bigint(20) DEFAULT NULL COMMENT '角色ID',`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户与角色对应关系';LOCK TABLES `user_role` WRITE;INSERT INTO `user_role` (`id`, `user_id`, `role_id`, `create_time`, `update_time`)
VALUES(1,1,1,'2019-08-30 07:40:51','2019-08-30 07:40:51');UNLOCK TABLES;

注:建表语句在项目中

三、实战案例

3.1. 案例介绍

3.2. 依赖

 <!--springboot web启动器--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--mybatis--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>${mybatis.version}</version></dependency><!--数据源--><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>${druid.version}</version></dependency><!--数据库--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><!--lombok插件--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!--  shiro核心包      --><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>${shiro-spring.version}</version></dependency><!--大神的开源插件--><dependency><groupId>org.crazycake</groupId><artifactId>shiro-redis</artifactId><version>${shiro-redis.version}</version></dependency>

3.3. Shiro全局配置

package com.gblfy.shiro.config;import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.Map;/*** @Author: http://gblfy.com* @Version 1.0.0*/
@Configuration
@Slf4j
@Data
@ConfigurationProperties(prefix = "spring.redis")
public class ShiroConfig {private String host = "localhost";private int port = 6379;private Duration timeout;/*** Filter工厂,设置对应的过滤条件和跳转条件** @return ShiroFilterFactoryBean*/@Beanpublic ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();shiroFilterFactoryBean.setSecurityManager(securityManager);// 过滤器链定义映射Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();/** anon:所有url都都可以匿名访问,authc:所有url都必须认证通过才可以访问;* 过滤链定义,从上向下顺序执行,authc 应放在 anon 下面* */filterChainDefinitionMap.put("/login", "anon");// 配置不会被拦截的链接 顺序判断,如果前端模板采用了thymeleaf,这里不能直接使用 ("/static/**", "anon")来配置匿名访问,必须配置到每个静态目录filterChainDefinitionMap.put("/css/**", "anon");filterChainDefinitionMap.put("/fonts/**", "anon");filterChainDefinitionMap.put("/img/**", "anon");filterChainDefinitionMap.put("/js/**", "anon");filterChainDefinitionMap.put("/html/**", "anon");// 所有url都必须认证通过才可以访问filterChainDefinitionMap.put("/**", "authc");// 配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了, 位置放在 anon、authc下面filterChainDefinitionMap.put("/logout", "logout");// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面// 配器shirot认登录累面地址,前后端分离中登录累面跳转应由前端路由控制,后台仅返回json数据, 对应LoginController中unauth请求shiroFilterFactoryBean.setLoginUrl("/un_auth");// 登录成功后要跳转的链接, 此项目是前后端分离,故此行注释掉,登录成功之后返回用户基本信息及token给前端// shiroFilterFactoryBean.setSuccessUrl("/index");// 未授权界面, 对应LoginController中 unauthorized 请求shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean;}/*** RedisSessionDAO shiro sessionDao层的实现 通过redis, 使用的是shiro-redis开源插件** @return RedisSessionDAO*/@Beanpublic RedisSessionDAO redisSessionDAO() {RedisSessionDAO redisSessionDAO = new RedisSessionDAO();redisSessionDAO.setRedisManager(redisManager());redisSessionDAO.setSessionIdGenerator(sessionIdGenerator());redisSessionDAO.setExpire(1800);return redisSessionDAO;}/*** Session ID 生成器** @return JavaUuidSessionIdGenerator*/@Beanpublic JavaUuidSessionIdGenerator sessionIdGenerator() {return new JavaUuidSessionIdGenerator();}/*** 自定义sessionManager** @return SessionManager*/@Beanpublic SessionManager sessionManager() {MySessionManager mySessionManager = new MySessionManager();mySessionManager.setSessionDAO(redisSessionDAO());return mySessionManager;}/*** 配置shiro redisManager, 使用的是shiro-redis开源插件** @return RedisManager*/private RedisManager redisManager() {RedisManager redisManager = new RedisManager();redisManager.setHost(host);redisManager.setPort(port);redisManager.setTimeout((int) timeout.toMillis());return redisManager;}/*** cacheManager 缓存 redis实现, 使用的是shiro-redis开源插件** @return RedisCacheManager*/@Beanpublic RedisCacheManager cacheManager() {RedisCacheManager redisCacheManager = new RedisCacheManager();redisCacheManager.setRedisManager(redisManager());// 必须要设置主键名称,shiro-redis 插件用过这个缓存用户信息redisCacheManager.setPrincipalIdFieldName("userId");return redisCacheManager;}/*** 权限管理,配置主要是Realm的管理认证** @return SecurityManager*/@Beanpublic SecurityManager securityManager(ShiroRealm shiroRealm) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();securityManager.setRealm(shiroRealm);// 自定义session管理 使用redissecurityManager.setSessionManager(sessionManager());// 自定义缓存实现 使用redissecurityManager.setCacheManager(cacheManager());return securityManager;}/** 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证* 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能*/@Beanpublic DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();advisorAutoProxyCreator.setProxyTargetClass(true);return advisorAutoProxyCreator;}@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);return authorizationAttributeSourceAdvisor;}@Beanpublic SimpleCookie cookie() {// cookie的name,对应的默认是 JSESSIONIDSimpleCookie cookie = new SimpleCookie("SHARE_JSESSIONID");cookie.setHttpOnly(true);//  path为 / 用于多个系统共享 JSESSIONIDcookie.setPath("/");return cookie;}/* 此项目使用 shiro 场景为前后端分离项目,这里先注释掉,统一异常处理已在 GlobalExceptionHand.java 中实现 */}

3.4. 自定义ShiroRealm

package com.gblfy.shiro.config;import com.gblfy.shiro.entity.Role;
import com.gblfy.shiro.util.ShiroUtils;
import com.gblfy.shiro.entity.Menu;
import com.gblfy.shiro.entity.User;
import com.gblfy.shiro.mapper.MenuMapper;
import com.gblfy.shiro.mapper.RoleMapper;
import com.gblfy.shiro.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
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;
import java.util.Objects;/*** @Author: http://gblfy.com* @Version 1.0.0*/
@Slf4j
@Component
public class ShiroRealm extends AuthorizingRealm {private UserMapper userMapper;private RoleMapper roleMapper;private MenuMapper menuMapper;@Autowired@SuppressWarnings("all")public ShiroRealm(UserMapper userMapper, RoleMapper roleMapper, MenuMapper menuMapper) {this.userMapper = userMapper;this.roleMapper = roleMapper;this.menuMapper = menuMapper;}/*** 授权** @param principalCollection* @return*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {log.info("开始执行授权操作.......");SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();/*** 查询用户角色* 如果身份认证的时候没有传入User对象,这里只能取到userName* 也就是SimpleAuthenticationInfo构造的时候第一个参数传递需要User对象*/User user = (User) principalCollection.getPrimaryPrincipal();if (user == null) {log.error("用户不存在");throw new UnknownAccountException("用户不存在");}//TODO 是否为超级管理员   是  全部菜单权限/*** 查询用户角色*/List<Role> roles = this.roleMapper.listRoleByUserId(user.getUserId());if(CollectionUtils.isNotEmpty(roles)){for (Role role : roles) {authorizationInfo.addRole(role.getRoleName());// 根据角色查询权限List<Menu> menus = this.menuMapper.listMenuByRoleId(role.getRoleId());for (Menu m : menus) {authorizationInfo.addStringPermission(m.getPerms());}}}return authorizationInfo;}/*** 认证** @param authenticationToken* @return* @throws AuthenticationException*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {log.info("开始进行身份认证......");//获取用户的输入的账号.String username = (String) authenticationToken.getPrincipal();//通过username从数据库中查找 User对象.//实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法User user = userMapper.findByUsername(username);if (Objects.isNull(user)) {return null;}return new SimpleAuthenticationInfo(// 这里传入的是user对象,比对的是用户名,直接传入用户名也没错,但是在授权部分就需要自己重新从数据库里取权限user,// 密码user.getPassword(),// salt = username + saltByteSource.Util.bytes(user.getSalt()),// realm namegetName());}/*** 将自己的验证方式加入容器** 凭证匹配器(由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了)** @param credentialsMatcher*/@Overridepublic void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();/*** 散列算法:这里可以使用MD5算法 也可以使用SHA-256*/hashedCredentialsMatcher.setHashAlgorithmName(ShiroUtils.hashAlgorithmName);// 散列的次数,比如散列16次,相当于 md5(md5(""));hashedCredentialsMatcher.setHashIterations(ShiroUtils.hashIterations);super.setCredentialsMatcher(hashedCredentialsMatcher);}}

3.5. ShiroUtils

package com.gblfy.shiro.util;import com.gblfy.shiro.entity.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;/*** Shiro工具类*/
public class ShiroUtils {/**  加密算法 */public final static String hashAlgorithmName = "SHA-256";/**  循环次数 */public final static int hashIterations = 16;public static String sha256(String password, String salt) {return new SimpleHash(hashAlgorithmName, password, salt, hashIterations).toString();}// 获取一个测试账号 adminpublic static void main(String[] args) {// 3743a4c09a17e6f2829febd09ca54e627810001cf255ddcae9dabd288a949c4aSystem.out.println(sha256("admin","123")) ;}/*** 获取会话*/public static Session getSession() {return SecurityUtils.getSubject().getSession();}/*** Subject:主体,代表了当前“用户”*/public static Subject getSubject() {return SecurityUtils.getSubject();}public static User getUserEntity() {return (User)SecurityUtils.getSubject().getPrincipal();}public static Long getUserId() {return getUserEntity().getUserId();}public static void setSessionAttribute(Object key, Object value) {getSession().setAttribute(key, value);}public static Object getSessionAttribute(Object key) {return getSession().getAttribute(key);}public static boolean isLogin() {return SecurityUtils.getSubject().getPrincipal() != null;}public static void logout() {SecurityUtils.getSubject().logout();}
}

3.6. 自定义SessionManager

package com.gblfy.shiro.config;import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;/**** @Author: http://gblfy.com* @Version 1.0.0** 自定义session管理* <br/>* 传统结构项目中,shiro从cookie中读取sessionId以此来维持会话,在前后端分离的项目中(也可在移动APP项目使用),* 我们选择在ajax的请求头中传递sessionId,因此需要重写shiro获取sessionId的方式。* 自定义MySessionManager类继承DefaultWebSessionManager类,重写getSessionId方法*/
public class MySessionManager extends DefaultWebSessionManager {private static final String AUTHORIZATION = "Authorization";private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";@Overrideprotected Serializable getSessionId(ServletRequest request, ServletResponse response) {String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);//如果请求头中有 Authorization 则其值为sessionIdif (!StringUtils.isEmpty(id)) {request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);return id;} else {//否则按默认规则从cookie取sessionIdreturn super.getSessionId(request, response);}}}

3.7. 登录/出主方法

package com.gblfy.shiro.controller;import com.gblfy.shiro.util.CacheUser;
import com.gblfy.shiro.util.Response;
import com.gblfy.shiro.entity.User;
import com.gblfy.shiro.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** @author http://gblfy.com* @Description 登录* @Date 2019/9/14 15:34* @version1.0*/
@Slf4j
@RestController
public class LoginController {private Response response;private UserService userService;@Autowired@SuppressWarnings("all")public LoginController(Response response, UserService userService) {this.response = response;this.userService = userService;}/*** description: 登录** @return 登录结果*/@PostMapping("/login")public Response login(User user) {log.warn("进入登录.....");String username = user.getUsername();String password = user.getPassword();if (StringUtils.isBlank(username)) {return response.failure("用户名为空!");}if (StringUtils.isBlank(password)) {return response.failure("密码为空!");}CacheUser loginUser = userService.login(username, password);// 登录成功返回用户信息return response.success("登录成功!", loginUser);}/*** description: 登出*/@RequestMapping("/logout")public Response logOut() {userService.logout();return response.success("登出成功!");}/*** 未登录,shiro应重定向到登录界面,此处返回未登录状态信息由前端控制跳转页面* @return*/@RequestMapping("/un_auth")public Response unAuth() {return response.failure(HttpStatus.UNAUTHORIZED, "用户未登录!", null);}/*** 未授权,无权限,此处返回未授权状态信息由前端控制跳转页面* @return*/@RequestMapping("/unauthorized")public Response unauthorized() {return response.failure(HttpStatus.FORBIDDEN, "用户无权限!", null);}}

3.8. 测试主方法

package com.gblfy.shiro.controller;import com.gblfy.shiro.util.Response;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;/*** @author http://gblfy.com* @Description 测试主方法* @Date 2019/9/14 15:34* @version1.0*/
@RestController
@Slf4j
@RequestMapping("user")
public class UserController {@Autowiredprivate Response response;@GetMapping("list")@RequiresPermissions("user:list")public Response listUser() {return response.success("用户列表");}@GetMapping("{userId}")@RequiresPermissions("user:detail")public Response detailUser(@PathVariable("userId") Long userId) {return response.success("用户详情");}@PostMapping("add")@RequiresRoles("admin")@RequiresPermissions("user:add")public Response addUser() {return response.success("添加用户成功");}@DeleteMapping("del")@RequiresRoles("role")public Response delUser() {return response.success("删除用户");}
}

四、 前后端分离需要注意的点

传统结构项目中,shiro从cookie中读取sessionId以此来维持会话,在前后端分离的项目中(也可在移动APP项目使用),我们选择在ajax的请求头中传递sessionId,因此需要重写shiro获取sessionId的方式。自定义MySessionManager类继承DefaultWebSessionManager类,重写getSessionId方法
登入失败,登入地址,前后端分离,不应该直接跳转页面,而是返回响应结果

五、测试验链接

说明请求方式url参数
登录POSTlocalhost:80/login?username=admin&password=admin
获取用户列表GETlocalhost:80/user/listAuthorization
获取用户详情GETlocalhost:80/user/1Authorization
添加用户POSTlocalhost:80/user/addAuthorization
删除用户DELETElocalhost:80/user/delAuthorization
退出登入 查询redis中数据是否清除缓存信息GETlocalhost:80/logoutAuthorization

六、测试验证

6.1. 登录测试

6.1. 1. 链接

localhost:80/login?username=admin&password=admin

6.1. 2. 参数

6.1. 3. 登陆后的token

025e0f13-ba78-47ff-bd2a-3fb6f459f102

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6.2. 获取用户列表

6.2. 1. 链接

localhost:80/user/list

6.2. 2. 参数

key:Authorization 
#登录后的token
values: 025e0f13-ba78-47ff-bd2a-3fb6f459f102

在这里插入图片描述
在这里插入图片描述

6.3. 获取用户详情

6.3. 1. 链接

localhost:80/user/1

6.3. 2. 参数

key:Authorization 
#登录后的token
values: 025e0f13-ba78-47ff-bd2a-3fb6f459f102

在这里插入图片描述
在这里插入图片描述

6.4. 添加用户

6.4. 1. 链接

localhost:80/user/add

6.4. 2. 参数

key:Authorization 
#登录后的token
values: 025e0f13-ba78-47ff-bd2a-3fb6f459f102

在这里插入图片描述
在这里插入图片描述

6.5. 删除用户

6.5. 1. 链接

localhost:80/user/del

6.5. 2. 参数

key:Authorization 
#登录后的token
values: 025e0f13-ba78-47ff-bd2a-3fb6f459f102

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

6.6. 注销登录

6.6. 1. 链接

localhost:80/logout

6.6. 2. 参数

key:Authorization 
#登录后的token
values: 025e0f13-ba78-47ff-bd2a-3fb6f459f102

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
从redis中,再次查看,token消失了,简言之,用户登录信息,销毁了

注:不填写登录的token或者填写token不正确,会提示用户未登录
在这里插入图片描述
再次查询详情


gitlab项目链接:
https://gitlab.com/gb-heima/springoot-shiro-redis
git下载方式:

git clone git@gitlab.com:gb-heima/springoot-shiro-redis.git

zip下载方式:
https://gitlab.com/gb-heima/springoot-shiro-redis/-/archive/master/springoot-shiro-redis-master.zip

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/523303.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

边缘计算容器化是否有必要?

戳蓝字“CSDN云计算”关注我们哦&#xff01;作者 | Steve来源 | 边缘计算中文社区简要由于容器有轻量级、安全性、秒级启动等优秀的特性&#xff0c;容器天然的轻量化和可移植性&#xff0c;非常适合边缘计算的场景&#xff0c;这一点边缘计算的厂家和开发者们都心知肚明。而且…

ipcp协议 Linux,Linux命令Man解释:PPPD(8) :点对点daemon协议

名称pppd - 点对点协定隐形程式(Point to Point Protocol daemon)语法pppd [ 选项 ] [ 终端设备名称(tty_name) ] [ 速率 ]描述这个点对点协定 (PPP) 提供一种在点对点串列线路上传输资料流(datagrams) 的方法。PPP 是由三个部份所组成的&#xff1a;一个在串列线路上封装(enca…

linux查看每个文件夹占空间大小

文章目录进入所在目录&#xff0c;执行以下命令&#xff1a;例&#xff1a;查看/app目录下面&#xff0c;每个目录的空间磁盘占比情况cd /app du -sh *

KubeCon 、 CloudNativeCon、Open Source Summit 2019三会交融,看点不断!

2019年6月24日&#xff0c;由CNCF和Linux基金会共同举办的KubeCon CloudNativeCon Open Source Summit 2019大会在上海世博中心盛大召开。来自全球各地的开源及云原生社区的采用者和技术专家齐聚于此&#xff0c;与参会者进一步探讨了云原生的教育及发展问题。 第一天大会以同…

c语言20152016真题及答案,2016年计算机二级《C语言》基础练习题及答案(15)

11[单选题]有以下程序程序运行后的输出结果是A.3B.9C.OD.-12参考答案&#xff1a;D参考解析&#xff1a;本题考查目的是运算符的结合性和优先级。首先计算a*a&#xff0c;结果为9&#xff0c;然后执行aa-9&#xff0c;即3-9&#xff0c;结果为-6&#xff0c;然后执行a(-6)(-6)&…

SpringBoot入门到精通_第2篇 _1分钟实战需求项目

接上一篇&#xff1a;SpringBoot入门到精通_第1篇 _核心概念 https://blog.csdn.net/weixin_40816738/article/details/94916051 文章目录一、实战SpringBoot项目1. 使用Spring Initializr快速创建Spring Boot应用2. 在线版本(任选其一即可)3. SpringBoot整合Spring MVC4. 创建…

云数据库精华问答 | 云数据库与其他数据库的关系

戳蓝字“CSDN云计算”关注我们哦&#xff01;云数据库是部署和虚拟化在云计算环境中的数据库。云数据库是在云计算的大背景下发展起来的一种新兴的共享基础架构的方法&#xff0c;它极大地增强了数据库的存储能力,今天我们就一起来看看云数据库的精华问答&#xff01;1Q&#x…

企业如何快速响应用户需求 且看云徙“数据+业务”双中台化简为繁

戳蓝字“CSDN云计算”关注我们哦&#xff01;作者 |刘丹“自然界生存下来的&#xff0c;既不是四肢最强壮的&#xff0c;也不是头脑最聪明的&#xff0c;而是最有能力适应变化的物种。”这句至理名言放在商业文明的今天依然得以适用&#xff0c;有能力适应技术快速发展、业务需…

微服务架构之「 下一代微服务 Service Mesh 」

戳蓝字“CSDN云计算”关注我们哦&#xff01;作者 | 奎哥来源 | 不止思考Service Mesh 被大家称为下一代的微服务&#xff0c;是微服务领域的一颗新星&#xff0c;被大家讨论的非常多。我在大家的讨论中&#xff0c;还看到有人说 “目前的微服务架构我都没学会呢&#xff0c;现…

C语言编程中线性表的顺序表示,数据结构C语言实现----线性表的顺序表示和实现...

线性表的顺序表示指的是用一组地址连续的存储单元依次存储线性表的数据元素。在高级程序设计语言中&#xff0c;通常都用数组来描述数据结构中的顺序存储结构。同时&#xff0c;由于线性表的长度可变&#xff0c;且所需最大存储空间随问题不同而不同&#xff0c;在C语言中可用动…

SpringBoot入门到精通_第5篇 _SpringBoot Actuator监控

接上一篇&#xff1a;SpringBoot入门到精通_第4篇 _开发三板斧 https://blog.csdn.net/weixin_40816738/article/details/101097161 文章目录一、 SpringBoot Actuator 概念1. 是什么&#xff1f;2. 如何整合SpringBoot Actuator&#xff1f;二、 SpringBoot Actuator 实战2.1.…

反转!美光、英特尔等多家美企恢复对华为供货;首例云服务器存储侵权案改判,阿里云不担责;英国政府拟严格审查Libra……...

戳蓝字“CSDN云计算”关注我们哦&#xff01;嗨&#xff0c;大家好&#xff0c;重磅君带来的【云重磅】特别栏目&#xff0c;如期而至&#xff0c;每周五第一时间为大家带来重磅新闻。把握技术风向标&#xff0c;了解行业应用与实践&#xff0c;就交给我重磅君吧&#xff01;重…

从达标到卓越 —— API 设计之道

摘要&#xff1a; 新技术层出不穷&#xff0c;长江后浪推前浪。在浪潮褪去后&#xff0c;能留下来的&#xff0c;是一些经典的设计思想。 在前端界&#xff0c;以前有远近闻名的 jQuery&#xff0c;近来有声名鹊起的 Vue.js。这两者叫好又叫座的原因固然有很多&#xff0c;但是…

SpringBoot入门到精通_第6篇 _必知必会

接上一篇&#xff1a;SpringBoot入门到精通_第5篇 _SpringBoot Actuator监控 https://blog.csdn.net/weixin_40816738/article/details/101097428 文章目录一、SpringBoot 配置管理1. 配置管理3种方式1.1. 以.properties为后缀名1.2. 以.yml/.yaml为后缀名(建议使用)2. Spring …

“刺激的”2017双11 阿里安全工程师首度揭秘智能风控平台MTEE3

摘要&#xff1a; “太刺激了&#xff0c;太刺激了&#xff01;如果那个48%真出问题&#xff0c;整个安全部的双11就可能是3.25&#xff01;” “太刺激了&#xff0c;太刺激了&#xff01;如果那个48%真出问题&#xff0c;整个安全部的双11就可能是3.25&#xff01;”知命推了…

你相信逛B站也能学编程吗?

戳蓝字“CSDN云计算”关注我们哦&#xff01;作者 | 徐麟来源 | 数据森麟作者&#xff1a;徐麟&#xff0c;某互联网公司数据分析狮&#xff0c;个人公众号数据森麟&#xff08;id&#xff1a;shujusenlin&#xff09;前言很多人提到B站&#xff0c;首先想到的就会是二次元或者…

创建小程序

注册账号&#xff1a;https://mp.weixin.qq.com/

spring boot集成mybatis和springsecurity实现权限控制功能

上一篇已经实现了登录认证功能&#xff0c;这一篇继续实现权限控制功能&#xff0c;文中代码只贴出来和上一篇不一样的修改的地方&#xff0c;完整代码可结合上一篇一起整理spring boot集成mybatis和springsecurity实现登录认证功能-CSDN博客 数据库建表 权限控制的意思就是根…

基于云上分布式NoSQL的海量气象数据存储和查询方案

摘要&#xff1a; 气象数据是一类典型的大数据&#xff0c;具有数据量大、时效性高、数据种类丰富等特点&#xff0c;每天产生的数据量常在几十TB到上百TB的规模&#xff0c;且在爆发性增长。如何存储和高效的查询这些气象数据越来越成为一个难题&#xff0c;本文针对气象领域中…

现代IM系统中消息推送和存储架构的实现

摘要&#xff1a; 前言 IM全称是『Instant Messaging』&#xff0c;中文名是即时通讯。在这个高度信息化的移动互联网时代&#xff0c;生活中IM类产品已经成为必备品&#xff0c;比较有名的如钉钉、微信、QQ等以IM为核心功能的产品。当然目前微信已经成长为一个生态型产品&…