SpringBoot+Shiro+JWT权限管理

Shiro

  • Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。
  • 使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

三个核心组件:Subject, SecurityManagerRealms.

  • Subject代表了当前用户的安全操作,即“当前操作用户”。

  • SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。

  • Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。

  • ShiroBasicArchitecture

  • ShiroArchitecture

JWT

  • JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案
  • JSON Web令牌是一种开放的行业标准 RFC 7519方法,用于在双方之间安全地表示声明。

JWT 数据结构

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJodHRwczovL3NwcmluZ2Jvb3QucGx1cyIsIm5hbWUiOiJzcHJpbmctYm9vdC1wbHVzIiwiaWF0IjoxNTE2MjM5MDIyfQ.1Cm7Ej8oIy1P5pkpu8-Q0B7bTU254I1og-ZukEe84II

JWT有三部分组成:Header:头部,Payload:负载,Signature:签名

SpringBoot+Shiro+JWT

pom.xml Shiro依赖

<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.4.1</version>
</dependency>

pom.xml JWT依赖

<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.8.3</version>
</dependency>

ShiroConfig.java配置

@Slf4j
@Configuration
public class ShiroConfig { /**
* JWT过滤器名称
*/
private static final String JWT_FILTER_NAME = "jwtFilter";
/**
* Shiro过滤器名称
*/
private static final String SHIRO_FILTER_NAME = "shiroFilter"; @Bean
public CredentialsMatcher credentialsMatcher() {
return new JwtCredentialsMatcher();
} /**
* JWT数据源验证
*
* @return
*/
@Bean
public JwtRealm jwtRealm(LoginRedisService loginRedisService) {
JwtRealm jwtRealm = new JwtRealm(loginRedisService);
jwtRealm.setCachingEnabled(false);
jwtRealm.setCredentialsMatcher(credentialsMatcher());
return jwtRealm;
} /**
* 禁用session
*
* @return
*/
@Bean
public DefaultSessionManager sessionManager() {
DefaultSessionManager manager = new DefaultSessionManager();
manager.setSessionValidationSchedulerEnabled(false);
return manager;
} @Bean
public SessionStorageEvaluator sessionStorageEvaluator() {
DefaultSessionStorageEvaluator sessionStorageEvaluator = new DefaultWebSessionStorageEvaluator();
sessionStorageEvaluator.setSessionStorageEnabled(false);
return sessionStorageEvaluator;
} @Bean
public DefaultSubjectDAO subjectDAO() {
DefaultSubjectDAO defaultSubjectDAO = new DefaultSubjectDAO();
defaultSubjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator());
return defaultSubjectDAO;
} /**
* 安全管理器配置
*
* @return
*/
@Bean
public DefaultWebSecurityManager securityManager(LoginRedisService loginRedisService) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(jwtRealm(loginRedisService));
securityManager.setSubjectDAO(subjectDAO());
securityManager.setSessionManager(sessionManager());
SecurityUtils.setSecurityManager(securityManager);
return securityManager;
} /**
* ShiroFilterFactoryBean配置
*
* @param securityManager
* @param loginRedisService
* @param shiroProperties
* @param jwtProperties
* @return
*/
@Bean(SHIRO_FILTER_NAME)
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,
LoginService loginService,
LoginRedisService loginRedisService,
ShiroProperties shiroProperties,
JwtProperties jwtProperties) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, Filter> filterMap = new HashedMap();
filterMap.put(JWT_FILTER_NAME, new JwtFilter(loginService, loginRedisService, jwtProperties));
shiroFilterFactoryBean.setFilters(filterMap);
Map<String, String> filterChainMap = shiroFilterChainDefinition(shiroProperties).getFilterChainMap();
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
return shiroFilterFactoryBean;
} /**
* Shiro路径权限配置
*
* @return
*/
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition(ShiroProperties shiroProperties) {
DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
// 获取ini格式配置
String definitions = shiroProperties.getFilterChainDefinitions();
if (StringUtils.isNotBlank(definitions)) {
Map<String, String> section = IniUtil.parseIni(definitions);
log.debug("definitions:{}", JSON.toJSONString(section));
for (Map.Entry<String, String> entry : section.entrySet()) {
chainDefinition.addPathDefinition(entry.getKey(), entry.getValue());
}
} // 获取自定义权限路径配置集合
List<ShiroPermissionConfig> permissionConfigs = shiroProperties.getPermissionConfig();
log.debug("permissionConfigs:{}", JSON.toJSONString(permissionConfigs));
if (CollectionUtils.isNotEmpty(permissionConfigs)) {
for (ShiroPermissionConfig permissionConfig : permissionConfigs) {
String url = permissionConfig.getUrl();
String[] urls = permissionConfig.getUrls();
String permission = permissionConfig.getPermission();
if (StringUtils.isBlank(url) && ArrayUtils.isEmpty(urls)) {
throw new ShiroConfigException("shiro permission config 路径配置不能为空");
}
if (StringUtils.isBlank(permission)) {
throw new ShiroConfigException("shiro permission config permission不能为空");
} if (StringUtils.isNotBlank(url)) {
chainDefinition.addPathDefinition(url, permission);
}
if (ArrayUtils.isNotEmpty(urls)) {
for (String string : urls) {
chainDefinition.addPathDefinition(string, permission);
}
}
}
}
// 最后一个设置为JWTFilter
chainDefinition.addPathDefinition("/**", JWT_FILTER_NAME); Map<String, String> filterChainMap = chainDefinition.getFilterChainMap();
log.debug("filterChainMap:{}", JSON.toJSONString(filterChainMap)); return chainDefinition;
} /**
* ShiroFilter配置
*
* @return
*/
@Bean
public FilterRegistrationBean delegatingFilterProxy() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
DelegatingFilterProxy proxy = new DelegatingFilterProxy();
proxy.setTargetFilterLifecycle(true);
proxy.setTargetBeanName(SHIRO_FILTER_NAME);
filterRegistrationBean.setFilter(proxy);
filterRegistrationBean.setAsyncSupported(true);
filterRegistrationBean.setEnabled(true);
filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC);
return filterRegistrationBean;
} @Bean
public Authenticator authenticator(LoginRedisService loginRedisService) {
ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
authenticator.setRealms(Arrays.asList(jwtRealm(loginRedisService)));
authenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy());
return authenticator;
} /**
* Enabling Shiro Annotations
*
* @return
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
} /**
* depends-on lifecycleBeanPostProcessor
*
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
return defaultAdvisorAutoProxyCreator;
} @Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
} }

JWT过滤器配置

@Slf4j
public class JwtFilter extends AuthenticatingFilter { private LoginService loginService; private LoginRedisService loginRedisService; private JwtProperties jwtProperties; public JwtFilter(LoginService loginService, LoginRedisService loginRedisService, JwtProperties jwtProperties) {
this.loginService = loginService;
this.loginRedisService = loginRedisService;
this.jwtProperties = jwtProperties;
} /**
* 将JWT Token包装成AuthenticationToken
*
* @param servletRequest
* @param servletResponse
* @return
* @throws Exception
*/
@Override
protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
String token = JwtTokenUtil.getToken();
if (StringUtils.isBlank(token)) {
throw new AuthenticationException("token不能为空");
}
if (JwtUtil.isExpired(token)) {
throw new AuthenticationException("JWT Token已过期,token:" + token);
} // 如果开启redis二次校验,或者设置为单个用户token登陆,则先在redis中判断token是否存在
if (jwtProperties.isRedisCheck() || jwtProperties.isSingleLogin()) {
boolean redisExpired = loginRedisService.exists(token);
if (!redisExpired) {
throw new AuthenticationException("Redis Token不存在,token:" + token);
}
} String username = JwtUtil.getUsername(token);
String salt;
if (jwtProperties.isSaltCheck()){
salt = loginRedisService.getSalt(username);
}else{
salt = jwtProperties.getSecret();
}
return JwtToken.build(token, username, salt, jwtProperties.getExpireSecond());
} /**
* 访问失败处理
*
* @param request
* @param response
* @return
* @throws Exception
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
// 返回401
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
// 设置响应码为401或者直接输出消息
String url = httpServletRequest.getRequestURI();
log.error("onAccessDenied url:{}", url);
ApiResult apiResult = ApiResult.fail(ApiCode.UNAUTHORIZED);
HttpServletResponseUtil.printJSON(httpServletResponse, apiResult);
return false;
} /**
* 判断是否允许访问
*
* @param request
* @param response
* @param mappedValue
* @return
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
String url = WebUtils.toHttp(request).getRequestURI();
log.debug("isAccessAllowed url:{}", url);
if (this.isLoginRequest(request, response)) {
return true;
}
boolean allowed = false;
try {
allowed = executeLogin(request, response);
} catch (IllegalStateException e) { //not found any token
log.error("Token不能为空", e);
} catch (Exception e) {
log.error("访问错误", e);
}
return allowed || super.isPermissive(mappedValue);
} /**
* 登陆成功处理
*
* @param token
* @param subject
* @param request
* @param response
* @return
* @throws Exception
*/
@Override
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
String url = WebUtils.toHttp(request).getRequestURI();
log.debug("鉴权成功,token:{},url:{}", token, url);
// 刷新token
JwtToken jwtToken = (JwtToken) token;
HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
loginService.refreshToken(jwtToken, httpServletResponse);
return true;
} /**
* 登陆失败处理
*
* @param token
* @param e
* @param request
* @param response
* @return
*/
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
log.error("登陆失败,token:" + token + ",error:" + e.getMessage(), e);
return false;
}
}

JWT Realm配置

@Slf4j
public class JwtRealm extends AuthorizingRealm { private LoginRedisService loginRedisService; public JwtRealm(LoginRedisService loginRedisService) {
this.loginRedisService = loginRedisService;
} @Override
public boolean supports(AuthenticationToken token) {
return token != null && token instanceof JwtToken;
} /**
* 授权认证,设置角色/权限信息
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.debug("doGetAuthorizationInfo principalCollection...");
// 设置角色/权限信息
String token = principalCollection.toString();
// 获取username
String username = JwtUtil.getUsername(token);
// 获取登陆用户角色权限信息
LoginSysUserRedisVo loginSysUserRedisVo = loginRedisService.getLoginSysUserRedisVo(username);
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 设置角色
authorizationInfo.setRoles(loginSysUserRedisVo.getRoles());
// 设置权限
authorizationInfo.setStringPermissions(loginSysUserRedisVo.getPermissions());
return authorizationInfo;
} /**
* 登陆认证
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.debug("doGetAuthenticationInfo authenticationToken...");
// 校验token
JwtToken jwtToken = (JwtToken) authenticationToken;
if (jwtToken == null) {
throw new AuthenticationException("jwtToken不能为空");
}
String salt = jwtToken.getSalt();
if (StringUtils.isBlank(salt)) {
throw new AuthenticationException("salt不能为空");
}
return new SimpleAuthenticationInfo(
jwtToken,
salt,
getName()
); } }

更多配置:https://github.com/geekidea/spring-boot-plus

application.yml配置

############################## spring-boot-plus start ##############################
spring-boot-plus:
######################## Spring Shiro start ########################
shiro:
# shiro ini 多行字符串配置
filter-chain-definitions: |
/=anon
/static/**=anon
/templates/**=anon
# 权限配置
permission-config:
# 排除登陆登出相关
- urls: /login,/logout
permission: anon
# 排除静态资源
- urls: /static/**,/templates/**
permission: anon
# 排除Swagger
- urls: /docs,/swagger-ui.html, /webjars/springfox-swagger-ui/**,/swagger-resources/**,/v2/api-docs
permission: anon
# 排除SpringBootAdmin
- urls: /,/favicon.ico,/actuator/**,/instances/**,/assets/**,/sba-settings.js,/applications/**
permission: anon
# 测试
- url: /sysUser/getPageList
permission: anon
######################## Spring Shiro end ########################## ############################ JWT start #############################
jwt:
token-name: token
secret: 666666
issuer: spring-boot-plus
audience: web
# 默认过期时间1小时,单位:秒
expire-second: 3600
# 是否刷新token
refresh-token: true
# 刷新token的时间间隔,默认10分钟,单位:秒
refresh-token-countdown: 600
# redis校验jwt token是否存在,可选
redis-check: true
# true: 同一个账号只能是最后一次登陆token有效,false:同一个账号可多次登陆
single-login: false
# 盐值校验,如果不加自定义盐值,则使用secret校验
salt-check: true
############################ JWT end ############################### ############################### spring-boot-plus end ###############################

Redis存储信息

使用Redis缓存JWTToken和盐值:方便鉴权,token后台过期控制等

  • Redis二次校验和盐值校验是可选的
127.0.0.1:6379> keys *
1) "login:user:token:admin:0f2c5d670f9f5b00201c78293304b5b5"
2) "login:salt:admin"
3) "login:user:admin"
4) "login:token:0f2c5d670f9f5b00201c78293304b5b5"
  • Redis存储的JwtToken信息
127.0.0.1:6379> get login:token:0f2c5d670f9f5b00201c78293304b5b5
{
"@class": "io.geekidea.springbootplus.shiro.vo.JwtTokenRedisVo",
"host": "127.0.0.1",
"username": "admin",
"salt": "f80b2eed0110a7ea5a94c35cbea1fe003d9bb450803473428b74862cceb697f8",
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJ3ZWIiLCJpc3MiOiJzcHJpbmctYm9vdC1wbHVzIiwiZXhwIjoxNTcwMzU3ODY1LCJpYXQiOjE1NzAzNTQyNjUsImp0aSI6IjE2MWQ1MDQxZmUwZjRmYTBhOThjYmQ0ZjRlNDI1ZGQ3IiwidXNlcm5hbWUiOiJhZG1pbiJ9.0ExWSiniq7ThMXfqCOi9pCdonY8D1azeu78_vLNa2v0",
"createDate": [
"java.util.Date",
1570354265000
],
"expireSecond": 3600,
"expireDate": [
"java.util.Date",
1570357865000
]
}

Reference

Shiro

JWT

spring-boot-plus

spring-boot-plus集成Shiro+JWT权限管理的更多相关文章

  1. Dubbo学习系列之九(Shiro+JWT权限管理)

    村长让小王给村里各系统来一套SSO方案做整合,隔壁的陈家村流行使用Session+认证中心方法,但小王想尝试点新鲜的,于是想到了JWT方案,那JWT是啥呢?JavaWebToken简称JWT,就是一个 ...

  2. spring boot + mybatis + layui + shiro后台权限管理系统

    后台管理系统 版本更新 后续版本更新内容 链接入口: springboot + shiro之登录人数限制.登录判断重定向.session时间设置:https://blog.51cto.com/wyai ...

  3. Spring Boot 中集成 Shiro

    https://blog.csdn.net/taojin12/article/details/88343990

  4. Spring Boot集成Shrio实现权限管理

    Spring Boot集成Shrio实现权限管理   项目地址:https://gitee.com/dsxiecn/spring-boot-shiro.git   Apache Shiro是一个强大且 ...

  5. shiro 和 spring boot 的集成

    1 添加依赖 使用 shiro-spring-boot-web-starter 在 spring boot 中集成 shiro 只需要再添加一个依赖 <dependency> <gr ...

  6. spring boot(十四)shiro登录认证与权限管理

    这篇文章我们来学习如何使用Spring Boot集成Apache Shiro.安全应该是互联网公司的一道生命线,几乎任何的公司都会涉及到这方面的需求.在Java领域一般有Spring Security ...

  7. spring boot 2 + shiro 实现权限管理

    Shiro是一个功能强大且易于使用的Java安全框架,主要功能有身份验证.授权.加密和会话管理.看了网上一些文章,下面2篇文章写得不错.Springboot2.0 集成shiro权限管理 Spring ...

  8. spring boot 2 集成JWT实现api接口认证

    JSON Web Token(JWT)是目前流行的跨域身份验证解决方案.官网:https://jwt.io/本文使用spring boot 2 集成JWT实现api接口验证. 一.JWT的数据结构 J ...

  9. 细说shiro之五:在spring框架中集成shiro

    官网:https://shiro.apache.org/ 1. 下载在Maven项目中的依赖配置如下: <!-- shiro配置 --> <dependency> <gr ...

随机推荐

  1. MySQL:数据库名或者数据表名包含-

    [参考文章]:mysql数据库名称中包含短横线的对应方式 1. 现象 命令行下操作 名称包含 " - " 数据库或者数据表时,语句执行报错: 2. 解决方案: 使用 `` 字符(E ...

  2. 重读APUE(2)-read返回值少于要求读取字节数

    返回值: 成功返回读到的字节数,如果达到文件尾,则返回0:注意:如果有数据第一次读取会返回全部读到的字节数,下一次读取才会返回0: 出错返回-1: 返回值少于要求读取字节数的情况: 1. 读取普通文件 ...

  3. HTM概述

    Html: Hyper Text Markup Language 超文本标记语言 超文本: 超出纯文本的范畴 标记语言: 标记其实就是标签 标签的格式: <标签名称>

  4. 阶段5 3.微服务项目【学成在线】_day05 消息中间件RabbitMQ_7.RabbitMQ研究-工作模式-工作队列模式

    RabbitMQ有以下几种工作模式 : 1.Work queues 2.Publish/Subscribe 3.Routing 4.Topics 5.Header 6.RPC 1.Work queue ...

  5. 一百三十六:CMS系统之发布帖子后台逻辑

    模型 class PostModel(db.Model): __tablename__ = 'post' id = db.Column(db.Integer, primary_key=True, au ...

  6. Java NIO 学习笔记 读写结合补充

    小练习:nio读写文件,将fileread中的内容读取到filewrite中 try { //创建输入通道 FileInputStream fis = new FileInputStream(&quo ...

  7. SQLAlchemy如何筛选值为None的列?那么django呢

    示例 from sqlalchemy import create_engine, MetaData, and_, or_, TIMESTAMP Plugin.query.filter(and_(Plu ...

  8. opengl读取灰度图生成三维地形

    准备第三方库 glew.freeglut.glm.opencv 准备灰度图片和草地贴图 最终效果 代码包括主程序源文件mainApp.cpp.顶点着色器shader.vs.片元着色器shader.fs ...

  9. 详解Linux开源安全审计和渗透测试工具Lynis

    转载自FreeBuf.COM Lynis是一款Unix系统的安全审计以及加固工具,能够进行深层次的安全扫描,其目的是检测潜在的时间并对未来的系统加固提供建议.这款软件会扫描一般系统信息,脆弱软件包以及 ...

  10. Docker-compose容易忽略的使用细节

    Docker-compose是docker官方的开源项目,通过使用模版yaml文件,实现对docker容器集群的管理.具体教程可以通过官方地址进行实践.Docker-compose主要有两个重要的概念 ...