分享 Shiro 学习过程中遇到的一些问题
最近在学习 shiro 安全框架后,自己手写了一个小的管理系统 web 项目,并使用 shiro 作为安全管理框架。接下来分享一下在这过程中,遇到的一些问题以及自己的解决思路和方法。
一、Log out 之后再次登录,出现 403 forbidden
这个问题不一定所有朋友都会碰到,出现的原因是我的 webapp 根目录下没有 index 页面(我的 index 页面放在 /WEB-INF/view/ 下),先看 ShiroFilterFactoryBean的配置代码。
<!--配置 shiro 框架的过滤器-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!--注入安全管理器-->
<property name="securityManager" ref="securityManager"/>
<!--默认的认证成功后跳转的页面-->
<property name="successUrl" value="/index"/>
<!--认证失败、登录访问的页面-->
<property name="loginUrl" value="/login"/>
<!--没有权限访问时跳转的页面-->
<property name="unauthorizedUrl" value="/unauthorized"/> <!--注入自定义 filter-->
<property name="filters">
<map>
<entry key="authc" value-ref="myFormAuthenticationFilter"/>
</map>
</property> <!--配置过滤器链-->
<property name="filterChainDefinitions">
<value>
<!--静态资源不需要验证,放行-->
/lib/** = anon
/static/** = anon
/verifyCode.jsp = anon
/checkVerifyCode = anon <!--退出登录-->
/logout = logout <!--其它所有页面都需要验证访问-->
/** = authc
</value>
</property>
</bean>
以上的配置是没有问题的,但是当你的 webapp 下没有 index 页面(或者没有配置 web.xml 的 <welcome-file-list>),就会出现标题所描述的问题。
我们先来分析一下各项配置的具体含义,只针对几个容易出现错误的配置项。
successUrl
这是一个容易令人引起误解的配置,让人以为登录成功后就一定会跳转到这个页面。实际上在 shiro 的底层,这是一个验证成功后默认的跳转页面,但是 shiro 底层会记录你的上次访问页面,当你登陆成功后会跳转到上次访问被拒绝的页面。
举个例子:当你打开浏览器,访问一个需要授权的页面(/** = authc),例如“user/list”页面,此时会因为没有授权,而跳转到配置中的“loginUrl”也就是登录界面,在你登录成功后,则会跳转到“user/list”页面,而不是“successUrl”。
那 successUrl 什么时候生效呢?当我们直接访问的就是“/login”页面时,登录成功后就会跳转到这个默认的验证成功的 “successUrl”页面。
loginUrl
这个配置的值为当用户访问需要授权的页面时,shiro 判断没有授权时跳转的页面。需要注意的是,在我们设计登录页面时,登录的表单提交的地址,也要和这个地址一样。
例如当我们访问“/login”控制器进入登陆页面,点击登录后,表单提交到的地址也应该是“/login”,否则登录不成功,继续跳转到登陆页面。我的猜测这种情况是因为,只有当表单提交的地址和 loginUrl 的地址相同时,请求才会走 FormAuthenticationFilter 过滤器进行登录验证。
当我们登陆失败时,会继续跳转到 loginUrl 这个页面。基于这种情况,我们可以在“/login”的控制器上同时获得登陆失败的异常,这个异常被 shiro 封装在 request 的属性中,key 为 “shiroLoginFailure”。接着可以根据异常信息,返回错误提示给前端显示。
@RequestMapping("/login")
public String login(HttpServletRequest request, Model m) {
//获取认证失败的错误信息,在Shiro框架的 FormAuthenticationFilter 过滤器中共享
// 共享的属性名称 shiroLoginFailure , 通过 request 获取
// 共享的 shiro 异常的字节码
String shiroLogininFailure = String.valueOf(request.getAttribute("shiroLoginFailure")); if (UnknownAccountException.class.getName().equals(shiroLogininFailure)) {
m.addAttribute("errorMsg", "账户不存在");
} else if (IncorrectCredentialsException.class.getName().equals(shiroLogininFailure)) {
m.addAttribute("errorMsg", "密码错误");
} System.out.println("异常类型:" + shiroLogininFailure);
return "login";
}
unauthorizedUrl
当我们给 filterChainDefinitions 添加了权限管理时,没有权限访问这个页面,就会跳入 unauthorizedUrl。需要注意的是,如果我们用注解的方式添加权限管理,不会走这个页面,这个页面只对 filterChainDefinitions 内的配置有效
/logout = logout
当我们访问这个地址时,就会经过 LogoutFilter ,这个过滤器会将我们当前用户退出,源码如下图。
同时,这个过滤器会重定向到“/”这个路径,这就是我们题目所述问题的根源。
接下来的过程就是:
- 我们登出之后重定向到“/”,“/”符合 “/** = authc”这个配置,需要验证才能访问;
- 然后我们进入了 loginUrl ,进行登录;
- 登录验证成功后,会跳转到上次访问失败的页面,也就是“/”地址;
- 当我们访问根路径时,tomcat 会默认调用 index.html 等类似的静态资源,或者根据 web.xml 中配置的 <welcome-file-list> 进行访问,若这些都没有资源进行访问,就会报上述的 403 forbidden 错误。
解决方案
我们有两种解决方案
第一种,将页面路径加入 <welcome-file-list>,如
<welcome-file-list>
<welcome-file>/WEB-INF/view/index.jsp</welcome-file>
</welcome-file-list>
第二种,我们修改负责进入 index 页面的控制器的 RequestMapping,如
@RequestMapping(value = {"/index", ""})
public String index() {
return "index";
}
二、登录之后,不 logout,手动进入登陆界面再次登录
按照标题的方式操作时,出现的现象是,当前 subject 的 principal 没有变更,同时我们继续跳转至登录界面,可以说很不符合客户体验的需求。
出现这个现象的原因是:首先,当我们访问“/login”时,表单提交的地址也是“/login”,所以很正常我们继续停留在了此页面;另外,每次我们访问满足“/** = authc”的页面时,AuthenticationFilter 会先进行 isAllowedAccess 方法的判断,我们登录过后,这个方法会返回 true,我们就可以直接进入页面,不走验证流程。
于是我们可以新建一个类继承 FormAuthenticationFilter,并重写其 isAllowedAccess 方法,在判断请求时指向登陆页面,并有表单提交时,如果当前有用户通过验证了,将当前用户 log out,再继续进行父类的验证。
subject.logout 会同时清空 session,所以我们登录成功后进入的是 successUrl 页面。完美的用户体验
public class MyFormAuthenticationFilter extends FormAuthenticationFilter { /**
* 重写 isAccessAllowed 方法,解决重复登录的问题
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { if (isLoginRequest(request, response)) {
if (isLoginSubmission(request, response)) { Subject subject = this.getSubject(request, response);
//判断当前是否已经登录
if (subject.getPrincipal() != null) {
System.out.println("log out:" + subject.getPrincipal());
//登出用户
subject.logout();
}
}
} return super.isAccessAllowed(request, response, mappedValue);
} }
这还没有完,我们需要在 ShiroFilterFactoryBean 的配置中,将我们的 filter 加入进去,替代 authc 进行验证
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 其他配置略 -->
<property name="filters">
<map>
<entry key="authc" value-ref="myFormAuthenticationFilter"/>
</map>
</property>
</bean>
三、注解模式下的权限配置,无法进入 unauthorizedUrl
注解模式下,无权访问的异常类型和在配置文件下的权限配置的不同,所以需要我们用 spring 手动捕捉,并跳转到需要显示的异常页面。需要注意的是,跳转地址受视图解析器影响。
<!-- 开启 spring 的异常拦截捕获权限异常 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="org.apache.shiro.authz.AuthorizationException">unauthorized</prop>
</props>
</property>
</bean>
key 就是异常全限定名,该配置跳转的地址是:/WEB-INF/view/unauthorized.jsp
四、开启 rememberMe 后,没有效果
rememberMe 要求 principal 对象是能够序列化的,也就是 实现 Serializable 接口。按照要求我把作为 principal 的 User 类实现了 Serializable 接口,但是依然失败,在测试记住我功能的时候,浏览器一直没有获得 Cookie。
出现这个问题的原因是,我的 User 类下,属性中还有一个 其他类的对象,该对象没有实现 Serializable 接口,所以导致了序列化失败。解决办法也很简单,就是让它也实现序列化接口。
不一定出现该问题都是这个原因,只是提醒大家这个点不要忽视了
分享 Shiro 学习过程中遇到的一些问题的更多相关文章
- 关于JDBC学习过程中的注意事项(分享自己犯过的错误,写给初学JDBC的小伙伴的八条建议)
关于JDBC学习过程中的注意事项(分享自己犯过的错误,写给初学JDBC的小伙伴的八条建议) 前言:最近在学习JDBC,总结了几个小问题,特地分享给大家,让大家不要犯这样的错误,也希望大家养成学会总结的 ...
- Java学习过程中的总结的小知识点(长期更新)
Java学习过程中的总结的小知识点 (主要是自己不会的知识和容易搞错的东西) 计算某个程序运行的时间 long stime=System.currentTimeMillis(); copy3(file ...
- 今天给大家分享一下Android中的资源与国际化的问题
摘要:该文章将向大家分享Android中的资源与国际化的问题. 今天给大家分享一下Android中的资源与国际化的问题,通常我们新建一个Android工程,目录结构如下图所示: 我们主要看一下layo ...
- 权限的分类(shiro项目中来的五)
第一种权限:菜单栏展示还是不展示的权限(粗颗粒) 实现方法,在SYS_ROLE表中添加一个字段rights,通过 public static BigInteger sumRights(String[] ...
- 谨以此篇献给DJANGO学习过程中遇到的问题
谨以此篇献给DJANGO学习过程中遇到的问题 一.Django数据同步过程中遇到的问题: 1.raise ImproperlyConfigured('mysqlclient 1.3.13 or new ...
- 关于《Selenium 2自动化测试实战 基于Python语言》学习过程中键盘的常用操作
下边是自己在学习过程中总结的一些常用键盘的操作
- Shiro登录中遇到了问题
Shiro登录中遇到了问题 记录二次开发中遇到的问题, 如果系统学习Shiro, 推荐跟我学Shrio. 问题 项目是要将验证从本地改为LDAP验证, 但是因为jeecms的验证和授权中, 用户和角色 ...
- Android应用之——微信微博第三方sdk登录分享使用过程中的一些常见问题
前言 近期在使用第三方登录和分享的过程中遇到了非常多问题,一方面能够归结为自己经验的不足,还有一方面事实上也说明了官方文档的含糊不清.这篇博文不会写关于怎样使用第三方登录分享,由于官方文档已经写明了步 ...
- 网易云信技术分享:IM中的万人群聊技术方案实践总结
本文来自网易云信团队的技术分享,原创发表于网易云信公众号,原文链接:mp.weixin.qq.com/s/LT2dASI7QVpcOVxDAsMeVg,收录时有改动. 1.引言 在不了解IM技术的人眼 ...
随机推荐
- kindeditor实现ctrl+v粘贴word图片并上传
Chrome+IE默认支持粘贴剪切板中的图片,但是我要发布的文章存在word里面,图片多达数十张,我总不能一张一张复制吧?Chrome高版本提供了可以将单张图片转换在BASE64字符串的功能.但是无法 ...
- [Luogu] 高斯消元法
https://www.luogu.org/problemnew/show/P3389 模拟,消元 #include <bits/stdc++.h> #define DB double ; ...
- luogu P4859 已经没有什么好害怕的了
嘟嘟嘟 题中给的\(k\)有点别扭,我们转换成\(a > b\)的对数是多少,这个用二元一次方程解出来是\(\frac{n + k}{2}\). 然后考虑dp,令\(dp[i][j]\)表示前\ ...
- P2119 魔法阵
原题链接 https://www.luogu.org/problemnew/show/P2119 YY同学今天上午给我们讲了这个题目,我觉得她的思路很好,特此写这篇博客整理一下. 50分:暴力枚举 ...
- xml详解
https://www.cnblogs.com/zhao1949/p/5652167.html https://www.cnblogs.com/cb0327/p/4967782.html
- POJ 3342 Party at Hali-Bula ——(树型DP)
一开始用pii保存dp类型,写的很长,还是WA了= =.. 然后参考了一下别人的博客,重新写了一发(似乎是岐哥的博客233). 代码如下: #include <stdio.h> #incl ...
- slax linux的定制
由于数据结构教学的需要,需要用到linux,要求就是小,启动快,可定制性强,恰好slax正好满足要求,以下就是定制slax linux的过程记录: 什么是Slax Slax是一个基于Linux的Liv ...
- Python颜色分类及格式
Python字符串颜色使用下面方式进行修改 \033[显示方式;字体色;背景色m 字符串 \033[0m 显示方式包括: 0 终端默认设置 1 高亮显示 4 使用下划线 5 闪烁 7 反白显 ...
- Ubuntu 18.04系统下arm-linux-gcc交叉编译器安装
Ubuntu 18.04系统: arm-linux-gcc 4.4.3版本. 安装arm-linux-gcc将压缩包arm-linux-gcc.tar.gz解压到arm-linux-gcc文件夹tar ...
- mongodb MongoDB C#/.NET driver version
The first column lists the driver version(s). C#/.NET Driver Version MongoDB 2.6 MongoDB 3.0 MongoDB ...