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

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

1、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>),就会出现标题所描述的问题。web

咱们先来分析一下各项配置的具体含义,只针对几个容易出现错误的配置项。spring

successUrl

这是一个容易使人引发误解的配置,让人觉得登陆成功后就必定会跳转到这个页面。实际上在 shiro 的底层,这是一个验证成功后默认的跳转页面,可是 shiro 底层会记录你的上次访问页面,当你登录成功后会跳转到上次访问被拒绝的页面。apache

举个例子:当你打开浏览器,访问一个须要受权的页面(/** = authc),例如“user/list”页面,此时会由于没有受权,而跳转到配置中的“loginUrl”也就是登陆界面,在你登陆成功后,则会跳转到“user/list”页面,而不是“successUrl”。浏览器

那 successUrl 何时生效呢?当咱们直接访问的就是“/login”页面时,登陆成功后就会跳转到这个默认的验证成功的 “successUrl”页面。tomcat

loginUrl

这个配置的值为当用户访问须要受权的页面时,shiro 判断没有受权时跳转的页面。须要注意的是,在咱们设计登陆页面时,登陆的表单提交的地址,也要和这个地址同样。安全

例如当咱们访问“/login”控制器进入登录页面,点击登陆后,表单提交到的地址也应该是“/login”,不然登陆不成功,继续跳转到登录页面。个人猜想这种状况是由于,只有当表单提交的地址和 loginUrl 的地址相同时,请求才会走 FormAuthenticationFilter 过滤器进行登陆验证。session

当咱们登录失败时,会继续跳转到 loginUrl 这个页面。基于这种状况,咱们能够在“/login”的控制器上同时得到登录失败的异常,这个异常被 shiro 封装在 request 的属性中,key 为 “shiroLoginFailure”。接着能够根据异常信息,返回错误提示给前端显示。app

@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 ,这个过滤器会将咱们当前用户退出,源码以下图。

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

接下来的过程就是:

  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";
    }

2、登陆以后,不 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>

3、注解模式下的权限配置,没法进入 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

4、开启 rememberMe 后,没有效果

rememberMe 要求 principal 对象是可以序列化的,也就是 实现 Serializable 接口。按照要求我把做为 principal 的 User 类实现了 Serializable 接口,可是依然失败,在测试记住我功能的时候,浏览器一直没有得到 Cookie。

出现这个问题的缘由是,个人 User 类下,属性中还有一个 其余类的对象,该对象没有实现 Serializable 接口,因此致使了序列化失败。解决办法也很简单,就是让它也实现序列化接口。

不必定出现该问题都是这个缘由,只是提醒你们这个点不要忽视了
相关文章
相关标签/搜索