您现在的位置是:主页 > news > 2345导网址导航下载/博客程序seo

2345导网址导航下载/博客程序seo

admin2025/6/28 16:19:30news

简介2345导网址导航下载,博客程序seo,微信开发网站建设程序,建设银行有招投标网站吗点击上方“Java基基”,选择“设为星标”做积极的人,而不是积极废人!源码精品专栏 原创 | Java 2020 超神之路,很肝~中文详细注释的开源项目RPC 框架 Dubbo 源码解析网络应用框架 Netty 源码解析消息中间件 RocketMQ 源码解析数据库…

2345导网址导航下载,博客程序seo,微信开发网站建设程序,建设银行有招投标网站吗点击上方“Java基基”,选择“设为星标”做积极的人,而不是积极废人!源码精品专栏 原创 | Java 2020 超神之路,很肝~中文详细注释的开源项目RPC 框架 Dubbo 源码解析网络应用框架 Netty 源码解析消息中间件 RocketMQ 源码解析数据库…

点击上方“Java基基”,选择“设为星标”

做积极的人,而不是积极废人!

源码精品专栏

 
  • 原创 | Java 2020 超神之路,很肝~

  • 中文详细注释的开源项目

  • RPC 框架 Dubbo 源码解析

  • 网络应用框架 Netty 源码解析

  • 消息中间件 RocketMQ 源码解析

  • 数据库中间件 Sharding-JDBC 和 MyCAT 源码解析

  • 作业调度中间件 Elastic-Job 源码解析

  • 分布式事务中间件 TCC-Transaction 源码解析

  • Eureka 和 Hystrix 源码解析

  • Java 并发源码

来源: juejin.im/post/5d087d605188256de9779e64

  • 一。说明

  • 二。项目环境

  • 二。编写项目基础类

  • 三。编写Shiro核心类

  • 四。实现权限控制

  • 五.POSTMAN测试

  • 六。项目源码


一。说明

Shiro是一个安全框架,项目中主要用它做认证,授权,加密,以及用户的会话管理,虽然Shiro没有SpringSecurity功能更丰富,但是它轻量,简单,在项目中通常业务需求Shiro也能够胜任。

二。项目环境

MyBatis-Plus版本:3.1.0

SpringBoot版本:2.1.5

JDK版本:1.8

Shiro版本:1.4

Shiro-redis插件版本:3.1.0

数据表(SQL文件在项目中):数据库中测试号的密码进行了加密,密码皆为123456

数据表名中文表名备注说明
sys_user系统用户表基础表
sys_menu权限表基础表
sys_role角色表基础表
sys_role_menu角色与权限关系表中间表
sys_user_role用户与角色关系表中间表

Maven依赖如下:

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><!-- AOP依赖,一定要加,否则权限拦截验证不生效 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><!-- lombok插件 --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!-- Redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis-reactive</artifactId></dependency><!-- mybatisPlus 核心库 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.1.0</version></dependency><!-- 引入阿里数据库连接池 --><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.6</version></dependency><!-- Shiro 核心依赖 --><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.4.0</version></dependency><!-- Shiro-redis插件 --><dependency><groupId>org.crazycake</groupId><artifactId>shiro-redis</artifactId><version>3.1.0</version></dependency><!-- StringUtilS工具 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.5</version></dependency>
</dependencies>

配置如下:

# 配置端口
server:port: 8764
spring:# 配置数据源datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/my_shiro?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=falseusername: rootpassword: roottype: com.alibaba.druid.pool.DruidDataSource# Redis数据源redis:host: localhostport: 6379timeout: 6000password: 123456jedis:pool:max-active: 1000  # 连接池最大连接数(使用负值表示没有限制)max-wait: -1      # 连接池最大阻塞等待时间(使用负值表示没有限制)max-idle: 10      # 连接池中的最大空闲连接min-idle: 5       # 连接池中的最小空闲连接
# mybatis-plus相关配置
mybatis-plus:# xml扫描,多个目录用逗号或者分号分隔(告诉 Mapper 所对应的 XML 文件位置)mapper-locations: classpath:mapper/*.xml# 以下配置均有默认值,可以不设置global-config:db-config:#主键类型 AUTO:"数据库ID自增" INPUT:"用户输入ID",ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";id-type: auto#字段策略 IGNORED:"忽略判断"  NOT_NULL:"非 NULL 判断")  NOT_EMPTY:"非空判断"field-strategy: NOT_EMPTY#数据库类型db-type: MYSQLconfiguration:# 是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射map-underscore-to-camel-case: true# 返回map时true:当查询数据为空时字段返回为null,false:不加这个查询数据为空时,字段将被隐藏call-setters-on-nulls: true# 这个配置会将执行的sql打印出来,在开发或测试的时候可以用log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

二。编写项目基础类

用户实体,Dao,Service等在这里省略,请参考源码

编写异常类来处理Shiro权限拦截异常

/*** @Description 自定义异常* @Author Sans* @CreateTime 2019/6/15 22:56*/
@ControllerAdvice
public class MyShiroException {/*** 处理Shiro权限拦截异常* 如果返回JSON数据格式请加上 @ResponseBody注解* @Author Sans* @CreateTime 2019/6/15 13:35* @Return Map<Object> 返回结果集*/@ResponseBody@ExceptionHandler(value = AuthorizationException.class)public Map<String,Object> defaultErrorHandler(){Map<String,Object> map = new HashMap<>();map.put("403","权限不足");return map;}
}

创建SHA256Util加密工具

/*** @Description Sha-256加密工具* @Author Sans* @CreateTime 2019/6/12 9:27*/
public class SHA256Util {/**  私有构造器 **/private SHA256Util(){};/**  加密算法 **/public final static String HASH_ALGORITHM_NAME = "SHA-256";/**  循环次数 **/public final static int HASH_ITERATIONS = 15;/**  执行加密-采用SHA256和盐值加密 **/public static String sha256(String password, String salt) {return new SimpleHash(HASH_ALGORITHM_NAME, password, salt, HASH_ITERATIONS).toString();}
}

创建弹簧工具

/*** @Description Spring上下文工具类* @Author Sans* @CreateTime 2019/6/17 13:40*/
@Component
public class SpringUtil implements ApplicationContextAware {private static ApplicationContext context;/*** Spring在bean初始化后会判断是不是ApplicationContextAware的子类* 如果该类是,setApplicationContext()方法,会将容器中ApplicationContext作为参数传入进去* @Author Sans* @CreateTime 2019/6/17 16:58*/@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {context = applicationContext;}/*** 通过Name返回指定的Bean* @Author Sans* @CreateTime 2019/6/17 16:03*/public static <T> T getBean(Class<T> beanClass) {return context.getBean(beanClass);}
}

创建Shiro工具

/*** @Description Shiro工具类* @Author Sans* @CreateTime 2019/6/15 16:11*/
public class ShiroUtils {/** 私有构造器 **/private ShiroUtils(){}private static RedisSessionDAO redisSessionDAO = SpringUtil.getBean(RedisSessionDAO.class);/*** 获取当前用户Session* @Author Sans* @CreateTime 2019/6/17 17:03* @Return SysUserEntity 用户信息*/public static Session getSession() {return SecurityUtils.getSubject().getSession();}/*** 用户登出* @Author Sans* @CreateTime 2019/6/17 17:23*/public static void logout() {SecurityUtils.getSubject().logout();}/*** 获取当前用户信息* @Author Sans* @CreateTime 2019/6/17 17:03* @Return SysUserEntity 用户信息*/public static SysUserEntity getUserInfo() {return (SysUserEntity) SecurityUtils.getSubject().getPrincipal();}/*** 删除用户缓存信息* @Author Sans* @CreateTime 2019/6/17 13:57* @Param  username  用户名称* @Param  isRemoveSession 是否删除Session* @Return void*/public static void deleteCache(String username, boolean isRemoveSession){//从缓存中获取SessionSession session = null;Collection<Session> sessions = redisSessionDAO.getActiveSessions();SysUserEntity sysUserEntity;Object attribute = null;for(Session sessionInfo : sessions){//遍历Session,找到该用户名称对应的Sessionattribute = sessionInfo.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);if (attribute == null) {continue;}sysUserEntity = (SysUserEntity) ((SimplePrincipalCollection) attribute).getPrimaryPrincipal();if (sysUserEntity == null) {continue;}if (Objects.equals(sysUserEntity.getUsername(), username)) {session=sessionInfo;break;}}if (session == null||attribute == null) {return;}//删除sessionif (isRemoveSession) {redisSessionDAO.delete(session);}//删除Cache,在访问受限接口时会重新授权DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager();Authenticator authc = securityManager.getAuthenticator();((LogoutAware) authc).onLogout((SimplePrincipalCollection) attribute);}
}

创建Shiro的SessionId生成器

/*** @Description 自定义SessionId生成器* @Author Sans* @CreateTime 2019/6/11 11:48*/
public class ShiroSessionIdGenerator implements SessionIdGenerator {/*** 实现SessionId生成* @Author Sans* @CreateTime 2019/6/11 11:54*/@Overridepublic Serializable generateId(Session session) {Serializable sessionId = new JavaUuidSessionIdGenerator().generateId(session);return String.format("login_token_%s", sessionId);}
}

三。编写Shiro核心类

创建Realm用于授权和认证

/*** @Description Shiro权限匹配和账号密码匹配* @Author Sans* @CreateTime 2019/6/15 11:27*/
public class ShiroRealm extends AuthorizingRealm {@Autowiredprivate SysUserService sysUserService;@Autowiredprivate SysRoleService sysRoleService;@Autowiredprivate SysMenuService sysMenuService;/*** 授权权限* 用户进行权限验证时候Shiro会去缓存中找,如果查不到数据,会执行这个方法去查权限,并放入缓存中* @Author Sans* @CreateTime 2019/6/12 11:44*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();SysUserEntity sysUserEntity = (SysUserEntity) principalCollection.getPrimaryPrincipal();//获取用户IDLong userId =sysUserEntity.getUserId();//这里可以进行授权和处理Set<String> rolesSet = new HashSet<>();Set<String> permsSet = new HashSet<>();//查询角色和权限(这里根据业务自行查询)List<SysRoleEntity> sysRoleEntityList = sysRoleService.selectSysRoleByUserId(userId);for (SysRoleEntity sysRoleEntity:sysRoleEntityList) {rolesSet.add(sysRoleEntity.getRoleName());List<SysMenuEntity> sysMenuEntityList = sysMenuService.selectSysMenuByRoleId(sysRoleEntity.getRoleId());for (SysMenuEntity sysMenuEntity :sysMenuEntityList) {permsSet.add(sysMenuEntity.getPerms());}}//将查到的权限和角色分别传入authorizationInfo中authorizationInfo.setStringPermissions(permsSet);authorizationInfo.setRoles(rolesSet);return authorizationInfo;}/*** 身份认证* @Author Sans* @CreateTime 2019/6/12 12:36*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {//获取用户的输入的账号.String username = (String) authenticationToken.getPrincipal();//通过username从数据库中查找 User对象,如果找到进行验证//实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法SysUserEntity user = sysUserService.selectUserByName(username);//判断账号是否存在if (user == null) {throw new AuthenticationException();}//判断账号是否被冻结if (user.getState()==null||user.getState().equals("PROHIBIT")){throw new LockedAccountException();}//进行验证SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user,                                  //用户名user.getPassword(),                    //密码ByteSource.Util.bytes(user.getSalt()), //设置盐值getName());//验证成功开始踢人(清除缓存和Session)ShiroUtils.deleteCache(username,true);return authenticationInfo;}
}

创建SessionManager类

/*** @Description 自定义获取Token* @Author Sans* @CreateTime 2019/6/13 8:34*/
public class ShiroSessionManager extends DefaultWebSessionManager {//定义常量private static final String AUTHORIZATION = "Authorization";private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";//重写构造器public ShiroSessionManager() {super();this.setDeleteInvalidSessions(true);}/*** 重写方法实现从请求头获取Token便于接口统一* 每次请求进来,Shiro会去从请求头找Authorization这个key对应的Value(Token)* @Author Sans* @CreateTime 2019/6/13 8:47*/@Overridepublic Serializable getSessionId(ServletRequest request, ServletResponse response) {String token = WebUtils.toHttp(request).getHeader(AUTHORIZATION);//如果请求头中存在token 则从请求头中获取tokenif (!StringUtils.isEmpty(token)) {request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, token);request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);return token;} else {// 这里禁用掉Cookie获取方式// 按默认规则从Cookie取Token// return super.getSessionId(request, response);return null;}}
}

创建ShiroConfig配置类

/*** @Description Shiro配置类* @Author Sans* @CreateTime 2019/6/10 17:42*/
@Configuration
public class ShiroConfig {private final String CACHE_KEY = "shiro:cache:";private final String SESSION_KEY = "shiro:session:";private final int EXPIRE = 1800;//Redis配置@Value("${spring.redis.host}")private String host;@Value("${spring.redis.port}")private int port;@Value("${spring.redis.timeout}")private int timeout;@Value("${spring.redis.password}")private String password;/*** 开启Shiro-aop注解支持* @Attention 使用代理方式所以需要开启代码支持* @Author Sans* @CreateTime 2019/6/12 8:38*/@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);return authorizationAttributeSourceAdvisor;}/*** Shiro基础配置* @Author Sans* @CreateTime 2019/6/12 8:42*/@Beanpublic ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager){ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();shiroFilterFactoryBean.setSecurityManager(securityManager);Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();// 注意过滤器配置顺序不能颠倒// 配置过滤:不会被拦截的链接filterChainDefinitionMap.put("/static/**", "anon");filterChainDefinitionMap.put("/userLogin/**", "anon");filterChainDefinitionMap.put("/**", "authc");// 配置shiro默认登录界面地址,前后端分离中登录界面跳转应由前端路由控制,后台仅返回json数据shiroFilterFactoryBean.setLoginUrl("/userLogin/unauth");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean;}/*** 安全管理器* @Author Sans* @CreateTime 2019/6/12 10:34*/@Beanpublic SecurityManager securityManager() {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();// 自定义Ssession管理securityManager.setSessionManager(sessionManager());// 自定义Cache实现securityManager.setCacheManager(cacheManager());// 自定义Realm验证securityManager.setRealm(shiroRealm());return securityManager;}/*** 身份验证器* @Author Sans* @CreateTime 2019/6/12 10:37*/@Beanpublic ShiroRealm shiroRealm() {ShiroRealm shiroRealm = new ShiroRealm();shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());return shiroRealm;}/*** 凭证匹配器* 将密码校验交给Shiro的SimpleAuthenticationInfo进行处理,在这里做匹配配置* @Author Sans* @CreateTime 2019/6/12 10:48*/@Beanpublic HashedCredentialsMatcher hashedCredentialsMatcher() {HashedCredentialsMatcher shaCredentialsMatcher = new HashedCredentialsMatcher();// 散列算法:这里使用SHA256算法;shaCredentialsMatcher.setHashAlgorithmName(SHA256Util.HASH_ALGORITHM_NAME);// 散列的次数,比如散列两次,相当于 md5(md5(""));shaCredentialsMatcher.setHashIterations(SHA256Util.HASH_ITERATIONS);return shaCredentialsMatcher;}/*** 配置Redis管理器* @Attention 使用的是shiro-redis开源插件* @Author Sans* @CreateTime 2019/6/12 11:06*/@Beanpublic RedisManager redisManager() {RedisManager redisManager = new RedisManager();redisManager.setHost(host);redisManager.setPort(port);redisManager.setTimeout(timeout);redisManager.setPassword(password);return redisManager;}/*** 配置Cache管理器* 用于往Redis存储权限和角色标识* @Attention 使用的是shiro-redis开源插件* @Author Sans* @CreateTime 2019/6/12 12:37*/@Beanpublic RedisCacheManager cacheManager() {RedisCacheManager redisCacheManager = new RedisCacheManager();redisCacheManager.setRedisManager(redisManager());redisCacheManager.setKeyPrefix(CACHE_KEY);// 配置缓存的话要求放在session里面的实体类必须有个id标识redisCacheManager.setPrincipalIdFieldName("userId");return redisCacheManager;}/*** SessionID生成器* @Author Sans* @CreateTime 2019/6/12 13:12*/@Beanpublic ShiroSessionIdGenerator sessionIdGenerator(){return new ShiroSessionIdGenerator();}/*** 配置RedisSessionDAO* @Attention 使用的是shiro-redis开源插件* @Author Sans* @CreateTime 2019/6/12 13:44*/@Beanpublic RedisSessionDAO redisSessionDAO() {RedisSessionDAO redisSessionDAO = new RedisSessionDAO();redisSessionDAO.setRedisManager(redisManager());redisSessionDAO.setSessionIdGenerator(sessionIdGenerator());redisSessionDAO.setKeyPrefix(SESSION_KEY);redisSessionDAO.setExpire(expire);return redisSessionDAO;}/*** 配置Session管理器* @Author Sans* @CreateTime 2019/6/12 14:25*/@Beanpublic SessionManager sessionManager() {ShiroSessionManager shiroSessionManager = new ShiroSessionManager();shiroSessionManager.setSessionDAO(redisSessionDAO());return shiroSessionManager;}
}

四。实现权限控制

Shiro可以使用代码或注解来控制权限,通常我们使用注解控制,既简单方便,而且更加灵活。

注解名称说明
需要认证使用该注解标注的类,方法等在访问时,当前主题必须在当前会话中已经过认证。
需要客人使用该注解标注的类,方法等在访问时,当前主题可以是“ gust”身份,不需要经过认证或者在原先的会话中存在记录。
要求用户验证用户是否被记忆,有两种含义:一种是成功登录的(subject.isAuthenticated()结果为true);另一种是被记忆的(subject.isRemembered()结果为true)。
需要权限当前Subject需要拥有某些特定的权限时,才能执行被该注解注释的方法。如果没有权限,则方法将不会执行引发AuthorizationException异常。
需要角色当前Subject必须拥有所有指定的角色时,才能访问被该注解标注的方法。如果没有角色,则方法将不会执行引发抛出AuthorizationException异常。

一般情况下我们在项目中做权限控制,使用最多的是RequiresPermissions和RequiresRoles,允许存在多个角色和权限,成为逻辑是AND,也就是同时拥有这些才可以访问方法,可以在注解中以参数的形式设置成OR

示例
//拥有一个角色就可以访问
@RequiresRoles(value={"ADMIN","USER"},logical = Logical.OR)
//拥有所有权限才可以访问
@RequiresPermissions(value={"sys:user:info","sys:role:info"},logical = Logical.AND)

使用顺序:Shiro注解是存在顺序的,当多个注解在一个方法上的时候,会逐个检查,知道全部通过为止,交替拦截顺序是:RequiresRoles-> RequiresPermissions-> RequiresAuthentication-> RequiresUser-> RequiresGuest

示例
//拥有ADMIN角色同时还要有sys:role:info权限
@RequiresRoles(value={"ADMIN")
@RequiresPermissions("sys:role:info")

创建UserRoleController角色拦截测试类

/*** @Description 角色测试* @Author Sans* @CreateTime 2019/6/19 11:38*/
@RestController
@RequestMapping("/role")
public class UserRoleController {@Autowiredprivate SysUserService sysUserService;@Autowiredprivate SysRoleService sysRoleService;@Autowiredprivate SysMenuService sysMenuService;@Autowiredprivate SysRoleMenuService sysRoleMenuService;/*** 管理员角色测试接口* @Author Sans* @CreateTime 2019/6/19 10:38* @Return Map<String,Object> 返回结果*/@RequestMapping("/getAdminInfo")@RequiresRoles("ADMIN")public Map<String,Object> getAdminInfo(){Map<String,Object> map = new HashMap<>();map.put("code",200);map.put("msg","这里是只有管理员角色能访问的接口");return map;}/*** 用户角色测试接口* @Author Sans* @CreateTime 2019/6/19 10:38* @Return Map<String,Object> 返回结果*/@RequestMapping("/getUserInfo")@RequiresRoles("USER")public Map<String,Object> getUserInfo(){Map<String,Object> map = new HashMap<>();map.put("code",200);map.put("msg","这里是只有用户角色能访问的接口");return map;}/*** 角色测试接口* @Author Sans* @CreateTime 2019/6/19 10:38* @Return Map<String,Object> 返回结果*/@RequestMapping("/getRoleInfo")@RequiresRoles(value={"ADMIN","USER"},logical = Logical.OR)@RequiresUserpublic Map<String,Object> getRoleInfo(){Map<String,Object> map = new HashMap<>();map.put("code",200);map.put("msg","这里是只要有ADMIN或者USER角色能访问的接口");return map;}/*** 登出(测试登出)* @Author Sans* @CreateTime 2019/6/19 10:38* @Return Map<String,Object> 返回结果*/@RequestMapping("/getLogout")@RequiresUserpublic Map<String,Object> getLogout(){ShiroUtils.logout();Map<String,Object> map = new HashMap<>();map.put("code",200);map.put("msg","登出");return map;}
}

创建UserMenuController权限拦截测试类

/*** @Description 权限测试* @Author Sans* @CreateTime 2019/6/19 11:38*/
@RestController
@RequestMapping("/menu")
public class UserMenuController {@Autowiredprivate SysUserService sysUserService;@Autowiredprivate SysRoleService sysRoleService;@Autowiredprivate SysMenuService sysMenuService;@Autowiredprivate SysRoleMenuService sysRoleMenuService;/*** 获取用户信息集合* @Author Sans* @CreateTime 2019/6/19 10:36* @Return Map<String,Object> 返回结果*/@RequestMapping("/getUserInfoList")@RequiresPermissions("sys:user:info")public Map<String,Object> getUserInfoList(){Map<String,Object> map = new HashMap<>();List<SysUserEntity> sysUserEntityList = sysUserService.list();map.put("sysUserEntityList",sysUserEntityList);return map;}/*** 获取角色信息集合* @Author Sans* @CreateTime 2019/6/19 10:37* @Return Map<String,Object> 返回结果*/@RequestMapping("/getRoleInfoList")@RequiresPermissions("sys:role:info")public Map<String,Object> getRoleInfoList(){Map<String,Object> map = new HashMap<>();List<SysRoleEntity> sysRoleEntityList = sysRoleService.list();map.put("sysRoleEntityList",sysRoleEntityList);return map;}/*** 获取权限信息集合* @Author Sans* @CreateTime 2019/6/19 10:38* @Return Map<String,Object> 返回结果*/@RequestMapping("/getMenuInfoList")@RequiresPermissions("sys:menu:info")public Map<String,Object> getMenuInfoList(){Map<String,Object> map = new HashMap<>();List<SysMenuEntity> sysMenuEntityList = sysMenuService.list();map.put("sysMenuEntityList",sysMenuEntityList);return map;}/*** 获取所有数据* @Author Sans* @CreateTime 2019/6/19 10:38* @Return Map<String,Object> 返回结果*/@RequestMapping("/getInfoAll")@RequiresPermissions("sys:info:all")public Map<String,Object> getInfoAll(){Map<String,Object> map = new HashMap<>();List<SysUserEntity> sysUserEntityList = sysUserService.list();map.put("sysUserEntityList",sysUserEntityList);List<SysRoleEntity> sysRoleEntityList = sysRoleService.list();map.put("sysRoleEntityList",sysRoleEntityList);List<SysMenuEntity> sysMenuEntityList = sysMenuService.list();map.put("sysMenuEntityList",sysMenuEntityList);return map;}/*** 添加管理员角色权限(测试动态权限更新)* @Author Sans* @CreateTime 2019/6/19 10:39* @Param  username 用户ID* @Return Map<String,Object> 返回结果*/@RequestMapping("/addMenu")public Map<String,Object> addMenu(){//添加管理员角色权限SysRoleMenuEntity sysRoleMenuEntity = new SysRoleMenuEntity();sysRoleMenuEntity.setMenuId(4L);sysRoleMenuEntity.setRoleId(1L);sysRoleMenuService.save(sysRoleMenuEntity);//清除缓存String username = "admin";ShiroUtils.deleteCache(username,false);Map<String,Object> map = new HashMap<>();map.put("code",200);map.put("msg","权限添加成功");return map;}
}

创建UserLoginController登录类

/*** @Description 用户登录* @Author Sans* @CreateTime 2019/6/17 15:21*/
@RestController
@RequestMapping("/userLogin")
public class UserLoginController {/*** 登录* @Author Sans* @CreateTime 2019/6/20 9:21*/@RequestMapping("/login")public Map<String,Object> login(@RequestBody SysUserEntity sysUserEntity){Map<String,Object> map = new HashMap<>();//进行身份验证try{//验证身份和登录Subject subject = SecurityUtils.getSubject();UsernamePasswordToken token = new UsernamePasswordToken(sysUserEntity.getUsername(), sysUserEntity.getPassword());//验证成功进行登录操作subject.login(token);}catch (IncorrectCredentialsException e) {map.put("code",500);map.put("msg","用户不存在或者密码错误");return map;} catch (LockedAccountException e) {map.put("code",500);map.put("msg","登录失败,该用户已被冻结");return map;} catch (AuthenticationException e) {map.put("code",500);map.put("msg","该用户不存在");return map;} catch (Exception e) {map.put("code",500);map.put("msg","未知异常");return map;}map.put("code",0);map.put("msg","登录成功");map.put("token",ShiroUtils.getSession().getId().toString());return map;}/*** 未登录* @Author Sans* @CreateTime 2019/6/20 9:22*/@RequestMapping("/unauth")public Map<String,Object> unauth(){Map<String,Object> map = new HashMap<>();map.put("code",500);map.put("msg","未登录");return map;}
}

五.POSTMAN测试

登录成功后会返回TOKEN,因为是单点登录,再次登录的话会返回新的TOKEN,然后Redis的TOKEN就会失效了

当第一次访问接口后我们可以看到缓存中已经有权限数据了,在次访问接口的时候,Shiro会直接去缓存中拿取权限,注意访问接口时候要设置请求头。

ADMIN这个号现在没有sys:info:all这个权限的,所以无法访问getInfoAll接口,我们要动态分配权限后,要清掉缓存,在访问接口时候,Shiro会去重新执行授权方法,之后再次把权限和角色数据放入缓存中

访问添加权限测试接口,因为是测试,我把增加权限的用户ADMIN写死在里面了,权限添加后,调用工具类清掉缓存,我们可以发现,Redis中已经没有缓存了

首次访问getInfoAll接口,因为缓存中没有数据,Shiro会重新授权查询权限,拦截通过

六。项目源码

编码云:https : //gitee.com/liselotte/spring-boot-shiro-demo

GitHub:https : //github.com/xuyulong2017/my-java-demo



欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢

已在知识星球更新源码解析如下:

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 20 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。