1. 引入需要的依赖

我使用的是原生jwt的依赖包,在maven仓库中有好多衍生的jwt依赖包,可自己在maven仓库中选择,实现大同小异。

    <dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>${shiro.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>${shiro-redis.version}</version>
<exclusions>
<exclusion>
<artifactId>shiro-core</artifactId>
<groupId>org.apache.shiro</groupId>
</exclusion>
</exclusions>
</dependency>
<!--JWT依赖-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>${jwt.version}</version>
</dependency>

2. 配置shiro信息

2.1. 配置文件增加属性值配置

# shiro 配置
shiro:
filter-chain-map:
# 用户登录
'[/login/**]': origin
# 获取api token
'[/api/token/**]': anon
# api接口权限配置
'[/api/**]': api
# 用户权限控制
'[/**]': origin, jwt
# 设置权限缓存时间
cache-timeout: 60

2.2. shiro 配置类

package com.example.shiro.configuration;

import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SessionStorageEvaluator;
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.mgt.DefaultWebSessionStorageEvaluator;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy; import javax.servlet.Filter;
import java.util.HashMap;
import java.util.Map; /**
* shiro配置文件
*
* @author xsshu
* @date 2020-07-18 16:22
*/
@Configuration
@AutoConfigureAfter(ShiroProperties.class)
public class ShiroConfig { @Autowired
private ShiroProperties shiroProperties; @Value("${spring.redis.host}:${spring.redis.port}")
private String host;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.database}")
private int database;
/**
* shiroFilter
*
* @param securityManager
* @return
*/
@Bean("shiroFilter")
public ShiroFilterFactoryBean factory(@Qualifier("webSecurityManager") @Lazy DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setFilterChainDefinitionMap(shiroProperties.getFilterChainMap());
Map<String, Filter> filters = new HashMap<>(3);
// 跨域拦截
OriginFilter originFilter = new OriginFilter();
filters.put("origin", originFilter);
// 用户请求拦截
filters.put("jwt", new UserJwtFilter());
// API请求拦截
filters.put("api", new AppJwtFilter());
shiroFilterFactoryBean.setFilters(filters);
return shiroFilterFactoryBean;
} /**
* redis配置
*
* @return
*/
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPassword(password);
redisManager.setDatabase(database);
return redisManager;
} @Bean("redisCacheManager")
public RedisCacheManager cacheManager() {
RedisCacheManager cacheManager = new RedisCacheManager();
cacheManager.setRedisManager(redisManager());
// redis key默认 = shiro:cache:com.unionticketing.auth.interceptor.realm.MyRealm.authorizationCache:用户ID
// redis key = shiro:cache:com.unionticketing.auth.interceptor.realm.MyRealm.authorizationCache:token值
cacheManager.setPrincipalIdFieldName("token");
cacheManager.setExpire(shiroProperties.getCacheTimeout());
return cacheManager;
} /**
* 禁用session, 不保存用户登录状态。保证每次请求都重新认证。
* 需要注意的是,如果用户代码里调用Subject.getSession()还是可以用session
*/
@Bean
protected SessionStorageEvaluator sessionStorageEvaluator(){
DefaultWebSessionStorageEvaluator sessionStorageEvaluator = new DefaultWebSessionStorageEvaluator();
sessionStorageEvaluator.setSessionStorageEnabled(false);
return sessionStorageEvaluator;
}
/**
* 配置webSecurityManager
*
* @param
* @return
**/
@Bean("webSecurityManager")
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm());
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
securityManager.setSubjectDAO(subjectDAO);
securityManager.setCacheManager(cacheManager());
return securityManager;
} @Bean("myRealm")
public MyRealm myRealm() {
return new MyRealm();
} /**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions)
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("webSecurityManager") @Lazy
DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}

2.3. MyRealm

package com.example.shiro.configuration;

import com.example.shiro.service.TokenService;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.subject.SimplePrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import java.util.ArrayList;
import java.util.List; /**
* 自定义Realm
*
* @author xsshu
* @date 2020-07-18 16:33:12
*/
@Slf4j
@NoArgsConstructor
public class MyRealm extends AuthorizingRealm { /**
* 增加@Lazy注解 是TokenService为低优先级注入的bean,为防止项目启动时报Bean 'xxx' of type [com.xx.xxx.xxxx.xxxxx$$EnhancerBySpringCGLIB$$babebd0] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
*/
@Autowired
@Lazy
private TokenService tokenService; /**
* 定义自己的认证匹配方式
*
* @param jwtCredentialsMatcher
*/
public MyRealm(JwtCredentialsMatcher jwtCredentialsMatcher) {
super(jwtCredentialsMatcher);
} /**
* 添加支持自定义token
*
* @param token token
* @return 是否支持
*/
@Override
public boolean supports(AuthenticationToken token) {
if (token instanceof JwtToken) {
return true;
}
return super.supports(token);
} /**
* 清除权限缓存
*
* @param principals
*/
@Override
protected void clearCachedAuthenticationInfo(PrincipalCollection principals) {
super.clearCachedAuthenticationInfo(new SimplePrincipalCollection(principals, getName()));
} /**
* 授权
*
* @param authenticationToken 请求的token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
if (authenticationToken instanceof JwtToken) {
JwtToken jwtToken = (JwtToken) authenticationToken;
// 用户TOKEN 授权
String tokenType = jwtToken.getTokenType();
try {
if (JwtToken.USER_TYPE.equals(tokenType) || JwtToken.API_TYPE.equals(tokenType)) {
tokenService.validateToken(jwtToken);
} else {
log.error("不合法的token");
throw new AuthenticationException("不合法的token");
}
} catch (Exception e) {
log.error("tokenType:{} 校验异常:{}", tokenType, e.getMessage());
throw new AuthenticationException("token校验失败", e);
}
return new SimpleAuthenticationInfo(jwtToken, authenticationToken, getName());
}
throw new AuthenticationException("token不合法.");
} /**
* 设置权限信息
*
* @param principals
* @return 设置权限
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
Object primaryPrincipal = principals.getPrimaryPrincipal();
List<String> permissionList = new ArrayList<>();
if (primaryPrincipal instanceof JwtToken) {
JwtToken jwtToken = (JwtToken) primaryPrincipal;
// TOKEN 授权
String token = jwtToken.getToken();
String tokenType = jwtToken.getTokenType();
if (tokenType.equals(JwtToken.USER_TYPE)) {
// 根据token解析用户信息查询用户所拥有的的权限列表,这里只是测试数据
permissionList.add("demo:user:query");
} else {
// 获取接口的权限列表,这里只是测试数据
permissionList.add("demo:api:test:add");
permissionList.add("demo:api:test:delete");
}
}
SimpleAuthorizationInfo authInfo = new SimpleAuthorizationInfo();
authInfo.addStringPermissions(permissionList);
return authInfo;
} }

说明

  1. MyReam类中用到了@Lazy注解,该注解的作用是:增加@Lazy注解 是TokenService为低优先级注入的bean,为防止项目启动时报Bean 'xxx' of type [com.xx.xxx.xxxx.xxxxx$$EnhancerBySpringCGLIB$$babebd0] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
  2. 关于权限的设置,如果权限存在包含关系,那么配置了大范围的权限后,即使没有配置范围小的权限,也是可以访问的。比如:父菜单-用户权限管理,对应权限编码为:demo:user:auth;功能菜单用户管理查询对应权限编码为:demo:user:auth:query;那么配置了demo:user:auth后,即使没有配置demo:user:auth:query,去访问相应的带权限查询接口的时候依然可以访问到。避免上述问题的解决方案具体操作如下:

    1. 父菜单-用户权限管理,对应权限编码为:demo:user:auth:manager;子菜单-用户管理查询,对应权限编码为:demo:user:auth:query
    2. 查询菜单的时候排除掉 父菜单-用户权限管理这种非功能性菜单权限

3. 测试

3.1. 登录测试

POST http://localhost:6666/login
Content-Type: application/json;utf-8 Body
{"account": "admin", "password": "admin"} 返回数据
{"ret":0,"code":0,"msg":"success","data":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiYXVkIjoidXNlciIsIm5iZiI6MTU5NTIyNzg5MywiaXNzIjoiYWRtaW4iLCJpYXQiOjE1OTUyMjc4OTMsImFjY291bnQiOiJhZG1pbiIsImp0aSI6ImExYjEyYjkyLWM1YjQtNGRmZC05ZjI5LWNjNTRiOGNkZjU4YyJ9.H4SGEHKo6f9SwrRYEYacKKJfR9GxKhYFO3zGmCv_f5k"}

3.2. 用户查询(权限编码:demo:user:query)

GET http://localhost:6666/user/query
Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiYXVkIjoidXNlciIsIm5iZiI6MTU5NTIyNzg5MywiaXNzIjoiYWRtaW4iLCJpYXQiOjE1OTUyMjc4OTMsImFjY291bnQiOiJhZG1pbiIsImp0aSI6ImExYjEyYjkyLWM1YjQtNGRmZC05ZjI5LWNjNTRiOGNkZjU4YyJ9.H4SGEHKo6f9SwrRYEYacKKJfR9GxKhYFO3zGmCv_f5k 返回数据
{"ret":0,"code":0,"msg":"success","data":"hell query"}

demo地址:https://gitee.com/xsshu/shiro-demo.git

如果有什么表述不对的地方,还请各位大佬纠正。

SpringBoot+Shiro+JWT前后端分离实现用户权限和接口权限控制的更多相关文章

  1. SpringBoot+Vue前后端分离,使用SpringSecurity完美处理权限问题

    原文链接:https://segmentfault.com/a/1190000012879279 当前后端分离时,权限问题的处理也和我们传统的处理方式有一点差异.笔者前几天刚好在负责一个项目的权限管理 ...

  2. 【SpringSecurity系列2】基于SpringSecurity实现前后端分离无状态Rest API的权限控制原理分析

    源码传送门: https://github.com/ningzuoxin/zxning-springsecurity-demos/tree/master/01-springsecurity-state ...

  3. shiro,基于springboot,基于前后端分离,从登录认证到鉴权,从入门到放弃

    这个demo是基于springboot项目的. 名词介绍: ShiroShiro 主要分为 安全认证 和 接口授权 两个部分,其中的核心组件为 Subject. SecurityManager. Re ...

  4. Springboot + Vue + shiro 实现前后端分离、权限控制

    本文总结自实习中对项目对重构.原先项目采用Springboot+freemarker模版,开发过程中觉得前端逻辑写的实在恶心,后端Controller层还必须返回Freemarker模版的ModelA ...

  5. SpringBoot 和Vue前后端分离入门教程(附源码)

    作者:梁小生0101 juejin.im/post/5c622fb5e51d457f9f2c2381 推荐阅读(点击即可跳转阅读) 1. SpringBoot内容聚合 2. 面试题内容聚合 3. 设计 ...

  6. 实战!spring Boot security+JWT 前后端分离架构认证登录!

    大家好,我是不才陈某~ 认证.授权是实战项目中必不可少的部分,而Spring Security则将作为首选安全组件,因此陈某新开了 <Spring Security 进阶> 这个专栏,写一 ...

  7. shiro vue 前后端分离中模拟登录遇到的坑

    系统采用jeeplus框架(ssm+redis+shiro+mongodb+redis),默认是了JSP未做前后端分离,由于业务需要已经多终端使用的需求(H5.小程序等),需要实现前后端分离.但是由于 ...

  8. Spring Boot + Vue + Shiro 实现前后端分离、权限控制

    本文总结自实习中对项目的重构.原先项目采用Springboot+freemarker模版,开发过程中觉得前端逻辑写的实在恶心,后端Controller层还必须返回Freemarker模版的ModelA ...

  9. laravel前后端分离的用户登陆 退出 中间件的接口与session的使用

    在项目开发的过程中,需要有用户的登陆 退出 还有校验用户是否登陆的中间件; 基本思路: 登陆: 前端请求接口的参数校验 用户名 密码规则的校验 用户名密码是否正确的校验; 如果上面的校验都通过的了,把 ...

随机推荐

  1. UDP/TCP 流程与总结

    1 UDP流程 前序:可以借助网络调试助手工具进行使用 1 UDP 发送方 1 创建UDP套接字 2 准备目标(发送方) IP和端口 3 需要发送的数据内容 4 关闭套接字 from socket i ...

  2. day28 封装

    目录 一.什么是封装 二.将封装的属性进行隐藏操作 1 如何隐藏: 1.1 强行访问: 1.2 内部逻辑 三.为何要封装 一.什么是封装 封装是面向对象的三大特性中最核心的一个特性 封装<==& ...

  3. java 基本语法(十二) 数组(五)Arrays工具类的使用

    1.理解:① 定义在java.util包下.② Arrays:提供了很多操作数组的方法. 2.使用: //1.boolean equals(int[] a,int[] b):判断两个数组是否相等. i ...

  4. 06 flask源码剖析之路由加载

    06 Flask源码之:路由加载 目录 06 Flask源码之:路由加载 1.示例代码 2.路由加载源码分析 1.示例代码 from flask import Flask app = Flask(__ ...

  5. bzoj3375[Usaco2004 Mar]Paranoid Cows 发疯的奶牛*

    bzoj3375[Usaco2004 Mar]Paranoid Cows 发疯的奶牛 题意: 依次给出n只奶牛的产奶时间段,求最大的k使得前k只奶牛不存在一个时间段被另一个时间段完全覆盖的情况.n≤1 ...

  6. Spring Boot Redis 实现分布式锁,真香!!

    之前看很多人手写分布式锁,其实 Spring Boot 现在已经做的足够好了,开箱即用,支持主流的 Redis.Zookeeper 中间件,另外还支持 JDBC. 本篇栈长以 Redis 为例(这也是 ...

  7. java中同步异步阻塞和非阻塞的区别

    同步 所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回. 按照这个定义,其实绝大多数函数都是同步调用(例如sin, isdigit等).但是一般而言,我们在说同步.异步的时候,特 ...

  8. 利用CloudFlare自动DDNS

    注意要 仅限 DNS 获取咱的Key https://dash.cloudflare.com/profile 先在控制面板找到咱的目前IP,然后到Cloudflare中新建一个A记录,如:ddns.y ...

  9. JAVA集合二:HashMap和Hashtable

    参考链接: HOW2J.CN HashMap HashMap实现了JAVA的Map接口,类似于C++的STL框架的Map,是存储键值对的数据结构.键(key)是唯一的,但值(value)可以重复,如果 ...

  10. TLV通信协议

    基础 TLV协议是BER编码的一种,全称是Tag.length.value.该协议简单高效,能适用于各种通信场景,且具有良好的可扩展性.TLV协议的基本格式如下: 其中,Tag占2个字节,是报文的唯一 ...