欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

分享 Shiro 学习过程中遇到的一些问题

程序员文章站 2022-08-22 08:47:22
最近在学习 shiro 安全框架后,自己手写了一个小的管理系统 web 项目,并使用 shiro 作为安全管理框架。接下来分享一下在这过程中,遇到的一些问题以及自己的解决思路和方法。 一、Log out 之后再次登录,出现 403 forbidden 这个问题不一定所有朋友都会碰到,出现的原因是我的 ......

最近在学习 shiro 安全框架后,自己手写了一个小的管理系统 web 项目,并使用 shiro 作为安全管理框架。接下来分享一下在这过程中,遇到的一些问题以及自己的解决思路和方法。

一、log out 之后再次登录,出现 403 forbidden

这个问题不一定所有朋友都会碰到,出现的原因是我的 webapp 根目录下没有 index 页面(我的 index 页面放在 /web-inf/view/ 下),先看 shirofilterfactorybean的配置代码。

 1 <!--配置 shiro 框架的过滤器-->
 2     <bean id="shirofilter" class="org.apache.shiro.spring.web.shirofilterfactorybean">
 3         <!--注入安全管理器-->
 4         <property name="securitymanager" ref="securitymanager"/>
 5         <!--默认的认证成功后跳转的页面-->
 6         <property name="successurl" value="/index"/>
 7         <!--认证失败、登录访问的页面-->
 8         <property name="loginurl" value="/login"/>
 9         <!--没有权限访问时跳转的页面-->
10         <property name="unauthorizedurl" value="/unauthorized"/>
11 
12         <!--注入自定义 filter-->
13         <property name="filters">
14             <map>
15                 <entry key="authc" value-ref="myformauthenticationfilter"/>
16             </map>
17         </property>
18 
19         <!--配置过滤器链-->
20         <property name="filterchaindefinitions">
21             <value>
22                 <!--静态资源不需要验证,放行-->
23                 /lib/** = anon
24                 /static/** = anon
25                 /verifycode.jsp = anon
26                 /checkverifycode = anon
27 
28                 <!--退出登录-->
29                 /logout = logout
30 
31                 <!--其它所有页面都需要验证访问-->
32                 /** = authc
33             </value>
34         </property>
35     </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 ,这个过滤器会将我们当前用户退出,源码如下图。

分享 Shiro 学习过程中遇到的一些问题

同时,这个过滤器会重定向到“/”这个路径,这就是我们题目所述问题的根源。

分享 Shiro 学习过程中遇到的一些问题

接下来的过程就是:

  1. 我们登出之后重定向到“/”,“/”符合 “/** = authc”这个配置,需要验证才能访问;
  2. 然后我们进入了 loginurl ,进行登录;
  3. 登录验证成功后,会跳转到上次访问失败的页面,也就是“/”地址;
  4. 当我们访问根路径时,tomcat 会默认调用 index.html 等类似的静态资源,或者根据 web.xml 中配置的 <welcome-file-list> 进行访问,若这些都没有资源进行访问,就会报上述的 403 forbidden 错误。

解决方案

我们有两种解决方案

第一种,将页面路径加入 <welcome-file-list>,如

1 <welcome-file-list>
2   <welcome-file>/web-inf/view/index.jsp</welcome-file> 
3 </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 接口,所以导致了序列化失败。解决办法也很简单,就是让它也实现序列化接口。

不一定出现该问题都是这个原因,只是提醒大家这个点不要忽视了