spring boot + shiro 动态更新用户信息
spring boot + shiro 动态更新用户信息
场景
用户A在线,管理员在后台更改了用户A信息(资料或权限)之后;用户A再进行下一步操作时,会被拦截并退出登录状态,再登录才可以执行操作;来确保用户A的信息同步更新。
后台权限管理系统
技术实现
前篇:
- spring boot + mybatis + layui + shiro后台权限管理系统:https://blog.51cto.com/wyait/2082803
- springboot + shiro之登录人数限制、登录判断重定向、session时间设置:https://blog.51cto.com/wyait/2107423
基于前篇,新增功能:
- 新增用户表版本version字段;
- 更新用户操作时,通过version字段来保证数据一致;
- 新增通过拦截器实现动态更新用户信息(用户资料、用户权限);
- 新增登录成功后默认页面home.html;
- 页面操作细节优化
wyait-manage、wyait-manage-1.2.0源码都更新了以上功能!
以及新增了springboot项目,开发和线上jdk版本不一致导致项目无法启动、无法加载的问题的排查及解决思路。
后篇:
项目源码:(包含数据库源码)
github源码: https://github.com/wyait/manage.git
码云:https://gitee.com/wyait/manage.git
github对应项目源码目录:wyait-manage-1.2.0
码云对应项目源码目录:wyait-manage-1.2.0
动态更新用户权限实现
使用shiro可能会遇到修改了用户权限后,没有立即生效,需要等到用户重新登录后才能生效;不能立即同步更新,显然是不合理的。
解决方案
【实测无效】!!!
授权方法,是在shiro进行鉴权的时候才能触发。只是配置了authc/user/anon等,不会触发;
perms,port,rest,roles,ssl等,会触发授权方法doGetAuthorizationInfo。
- 自定义ShiroRealm中添加清除全部用户权限缓存的方法:
/** * 清除所有缓存 */ public void clearCachedAuth(){ this.clearCachedAuthorizationInfo(SecurityUtils.getSubject().getPrincipals()); }
- 在更新用户权限的地方调用该方法,清除缓存:
//清除ehcache中所有用户权限缓存 RealmSecurityManager rsm = (RealmSecurityManager)SecurityUtils.getSecurityManager(); ShiroRealm authRealm = (ShiroRealm)rsm.getRealms().iterator().next(); authRealm.clearCachedAuth();
- 想要达到的效果:在用户进行后台请求的时候,无缓存就会再次请求授权方法,更新自己的权限。
实测,无效!缓存依然存在!原因可能是使用ehcache导致的。
实际解决方案参考下文中的方案二!
动态更新用户信息(用户资料、用户权限)
在系统中,由管理员更改了用户A信息后,如果用户A在线,无法及时更新相关的改动;
更新用户资料、权限等信息,如果该用户在线,同步更新用户信息解决方案:
方案一【不推荐】:SessionDAO控制
在ShiroRealm中通过SessionDAO拿到所有在线的用户,
Collection<Session> sessions = sessionDAO.getActiveSessions();
遍历找到匹配的,根据情况,退出登录或更新用户信息:
@Autowired private SessionDAO sessionDAO; public void updateShiroUser(String loginName){ Collection<Session> sessions = sessionDAO.getActiveSessions(); for(Session session:sessions){ if(loginName.equals(String.valueOf(session.getAttribute(DefaultSubjeContext.PRINCIPALS_SESSION_KEY))) { //设置session立即失效,即将其踢出系统 session.setTimeout(0); //TODO 或更新下用户信息 break; } }
【不推荐理由】
- 用户数量太大的时候,效率问题。
方案二【推荐】:用户表新增版本控制
用户信息新增version版本标记,写个拦截器,每次请求判断version是否一致,如有改动,根据情况,退出或更新用户信息(本文统一做了退出登录处理,可以结合实际需求做相应调整)。
这个方案,基于乐观锁原理实现。同样可解决动态更新用户权限的问题。
user用户表新增字段version
ALTER TABLE `user` MODIFY COLUMN `id` int(10) NOT NULL AUTO_INCREMENT FIRST , ADD COLUMN `version` int(10) NULL DEFAULT 0 COMMENT '更新版本' AFTER `send_time`;
TODO【详见源码】
- 更新对应的pojo实体类和mapper文件
- 完善用户更新操作,版本控制;
新建拦截器类
- 自定义拦截器UserActionInterceptor实现接口HandlerInterceptor:
/** * * @项目名称:wyait-manage * @类名称:UserActionInterceptor * @类描述:判断用户信息是否已被后台更改,并根据更改的情况做对应的处理 * @创建人:wyait * @创建时间:2018年5月2日 上午9:36:43 * @version: */ public class UserActionInterceptor implements HandlerInterceptor { private static Logger logger = LoggerFactory .getLogger(UserActionInterceptor.class); @Autowired private UserService userService; ... ... @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object obj, Exception e) throws Exception { // TODO Auto-generated method stub } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object obj, ModelAndView mv) throws Exception { // TODO Auto-generated method stub } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object obj) throws Exception { // TODO Auto-generated method stub logger.debug("请求到达后台方法之前调用(controller之前)"); // 1. SecurityUtils获取session中的用户信息 // HttpSession session=request.getSession(); User user = (User) SecurityUtils.getSubject().getPrincipal(); if (user != null && StringUtils.isNotEmpty(user.getMobile()) && null != user.getVersion()) { // 2. 获取数据库中的用户数据 User dataUser = this.userService.findUserByMobile(user.getMobile()); // 3. 对比session中用户的version和数据库中的是否一致 if (dataUser != null && null != dataUser.getVersion() && String.valueOf(user.getVersion()).equals( String.valueOf(dataUser.getVersion()))) { // 3.1 一样,放行 return true; }else{ // 3.2 不一样,这里统一做退出登录处理;//TODO 使用redis缓存用户权限数据,根据用户更新、用户权限更新;做对应的处理。 SecurityUtils.getSubject().logout(); isAjaxResponse(request,response); } } return false; } ... ... }
- 自定义MyWebMvcConfig继承WebMvcConfigurerAdapter
/** * * @项目名称:wyait-manage * @类名称:MyWebMvcConfig * @类描述:自定义静态资源映射路径和静态资源存放路径 * @创建人:wyait * @修改时间:2018年5月3日09:55:23 * @version: */ @Configuration public class MyWebMvcConfig extends WebMvcConfigurerAdapter { /** * 添加拦截器 */ @Override public void addInterceptors(InterceptorRegistry registry) { // 路径根据后期项目的扩展,进行调整 registry.addInterceptor(new UserActionInterceptor()) .addPathPatterns("/user/**", "/auth/**") .excludePathPatterns("/user/sendMsg", "/user/login"); super.addInterceptors(registry); } }
- IStatusMessage接口和common.js中新增判断‘1102’状态码;【详见源码】
- 启动,测试
报错
错误信息:
java.lang.NullPointerException: null at com.wyait.manage.interceptor.UserActionInterceptor.preHandle(UserActionInterceptor.java:62) ~[classes/:?] at org.springframework.web.servlet.HandlerExecutionChain.applyPreHandle(HandlerExecutionChain.java:133) ~[spring-webmvc-4.3.13.RELEASE.jar:4.3.13.RELEASE] at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:962) ~[spring-webmvc-4.3.13.RELEASE.jar:4.3.13.RELEASE]
userService对象为null,无法注入UserService。
@Autowired private UserService userService;
- 解决方案:
在Spring添加拦截器之前先创建这个Spring Bean拦截器,这样就能在Spring映射这个拦截器前,把拦截器中的依赖注入的对象给初始化完成了。避免拦截器中注入的对象为null问题。
@Configuration public class MyWebMvcConfig extends WebMvcConfigurerAdapter { /** * * @描述:在Spring添加拦截器之前先创建拦截器对象,这样就能在Spring映射这个拦截器前,把拦截器中的依赖注入的对象给初始化完成了。 * </br>避免拦截器中注入的对象为null问题。 * @创建人:wyait * @创建时间:2018年5月3日 上午10:07:36 * @return */ @Bean public UserActionInterceptor userActionInterceptor(){ return new UserActionInterceptor(); } /** * 添加拦截器 */ @Override public void addInterceptors(InterceptorRegistry registry) { // 路径根据后期项目的扩展,进行调整 registry.addInterceptor(userActionInterceptor()) .addPathPatterns("/user/**", "/auth/**") .excludePathPatterns("/user/sendMsg", "/user/login"); super.addInterceptors(registry); } }
注意
分布式或集群的时候,需要解决session共享问题;相关的方案有:session持久化、redis或其他中间件、nginx的ip_hash、cookie实现、服务器间Session同步等;这时候处理动态更新用户信息,需要结合实际情况而定;
后期会更新redis版本。
jdk版本引发的错误
部署环境
linux 6.* 系统
jdk 1.7
tomcat 7.*
maven 3.3.3
线上tomcat启动logs
五月 08, 2018 11:49:23 上午 org.apache.catalina.startup.TaglibUriRule body 信息: TLD skipped. URI: http://shiro.apache.org/tags is already defined 五月 08, 2018 11:49:23 上午 org.apache.catalina.startup.TldConfig execute 信息: At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time. 五月 08, 2018 11:51:03 上午 org.apache.catalina.util.SessionIdGeneratorBase createSecureRandom 信息: Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [112,917] milliseconds. 五月 08, 2018 11:51:03 上午 org.apache.catalina.startup.HostConfig deployDirectory 信息: Deployment of web application directory /usr/tools/tomcat-9190/webapps/ROOT has finished in 117,517 ms 五月 08, 2018 11:51:03 上午 org.apache.catalina.startup.Catalina start 信息: Server startup in 117563 ms 五月 08, 2018 11:51:37 上午 org.apache.catalina.util.SessionIdGeneratorBase createSecureRandom 信息: Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [134,439] milliseconds. ...
无明显错误,但是项目没有加载成功,访问是404.
- 可能的问题点:
At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
对比本地tomcat7和线上tomcat7启动,会出现tomcat7和8版本冲突问题,本地解决了,线上依旧启动不成功;再百度、google方案,大致是tomcat版本问题而打印的日志信息,按照搜索的方案配置,均未解决。
考虑应该是环境配置的版本不一致导致的项目无法加载启动成功,确认并排查下开发、测试、线上的jdk、tomcat等版本是否一致。
原因及解决方案
- jdk版本
确认linux和window的jdk版本是否一致- windows查看jdk版本,dos窗口
java -version
jdk 1.8
- linux系统查看jdk版本
java -v
jdk 1.7
这就是导致项目在linux系统启动不起来的原因:开发和线上的jdk版本不一致!!!
将windows的jdk版本切换为jdk1.7,重新打开新的dos窗口:java -version;jdk显示为1.7.*。版本切换成功!
- maven重新打包,报错:
... [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3. 1:compile (default-compile) on project wyait-manage: Fatal error compiling: 无效的 目标发行版: 1.8 -> [Help 1] [ERROR] [ERROR] To see the full stack trace of the errors, re-run Maven with the -e swit ch. ...
- 查看setting配置文件,找到<profile>配置:
<!-- <profile> <id>jdk-1.8</id> <activation> <activeByDefault>true</activeByDefault> <jdk>1.8</jdk> </activation> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion> </properties> </profile> -->
注释掉这一段maven指定jdk为1.8版本的配置;重新打包,成功!
- 重新部署到linux系统,启动并访问成功!
问题解决。
总结
开发过程中,要确保开发、测试、线上配置的环境(jdk、maven、tomcat等开发依赖的环境支持)保持一致。避免出现由于开发环境中的版本不一致而出现问题,导致项目上线出问题和延迟项目上线时间!
前篇:
- spring boot + mybatis + layui + shiro后台权限管理系统:https://blog.51cto.com/wyait/2082803
- springboot + shiro之登录人数限制、登录判断重定向、session时间设置:https://blog.51cto.com/wyait/2107423
后篇:
项目源码:(包含数据库源码)
github源码: https://github.com/wyait/manage.git
码云:https://gitee.com/wyait/manage.git
github对应项目源码目录:wyait-manage-1.2.0
码云对应项目源码目录:wyait-manage-1.2.0
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
基于AWS-ELK部署系统日志告警系统
前言 运维故障排障速度往往与监控系统体系颗粒度成正比,监控到位才能快速排障 在部署这套系统之前,平台所有系统日志都由Graylog+Zabbix,针对日志出现的错误关键字进行告警,这种做法在运维工作开展过程中暴露出多个不足点,不详述;在考虑多方面原因后,最终对日志告警系统进行更换,选用的方案是:ELK + Kafka+ Filebeat + Elastalert 本文主要以两个需求为主轴做介绍 非工作时间服务器异常登录告警 系统日志出现错误关键字告警 架构 服务选型 name version info Amazon Elasticsearch Service v6.2 AWK官网部署教程 Logstash v6.2.3 选用与ES相同版本 Filebeat v6.2.3 选用与ES相同版本 Confluent(Kafka) v4.0 这里推荐 Confluent 的版本,Confluent 是 kafka 作者 Neha Narkhede 从 Linkedin 出来之后联合 LinkedIn 前员工创建的大数据公司,专注于 kafka 的企业应用。 Elastalert v0.1.29...
- 下一篇
一条命令搞垮MongoDB实例
一条命令搞垮MongoDB实例 背景 Part1:写在最前 在副本集架构中,我们会经常通过rs.add(),rs.remove()命令来调整后台数据库架构,在本案例中,我们异常的触发到了一个MongoDB的BUG,并尽快的找到了官方的人进行咨询。在生产环境中,我们做实例迁移,将研发自行维护的MongoDB副本集迁移至DBA管理,由于硬件和版本都不符合规范,因此我们对集群先进行了升级处理,又使用了rs.add()和rs.remove()来完成数据库的迁移工作。 Part2:背景 在研发自行维护的数据库版本为2.6版本,我们先将数据库升级至3.4版本,并利用3.4版本的特性实现0 downtime开启认证。在研发的数据库原有架构中,存在离线节点,即hidden和no-vote节点。也正是因为这一特性,触发了MongoDB宕机。 实战 Part1:整体架构 原有集群为7节点副本集架构,其中2台为hidden节点,并且配置了no-vote参数。 Part2:迁移原理 我们利用新的机器使用rs.add()加入到原有副本集,在原有副本集的基础上添加了新的节点,待同步完成后,rs.remove(...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Hadoop3单机部署,实现最简伪集群
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS8安装Docker,最新的服务器搭配容器使用
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS8编译安装MySQL8.0.19
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2更换Tomcat为Jetty,小型站点的福音