zoukankan      html  css  js  c++  java
  • Shiro Web集成及拦截器机制(四)

    Shiro与 Web 集

    Shiro 提供了与 Web 集成的支持,其通过一个 ShiroFilter 入口来拦截需要安全控制的 URL,然后进行相应的控制,ShiroFilter 类似于如 Strut2/SpringMVC 这种 web 框架的前端控制器,其是安全控制的入口点,其负责读取配置(如 ini 配置文件),然后判断 URL 是否需要登录 / 权限等工作。

    ShiroFilter 入口

    Shiro 1.2 及以后版本的配置方式

    从 Shiro 1.2 开始引入了 Environment/WebEnvironment 的概念,即由它们的实现提供相应的 SecurityManager 及其相应的依赖。ShiroFilter 会自动找到 Environment 然后获取相应的依赖。

    <listener>
       <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
    </listener>

    通过 EnvironmentLoaderListener 来创建相应的 WebEnvironment,并自动绑定到 ServletContext,默认使用 IniWebEnvironment 实现。

    可以通过如下配置修改默认实现及其加载的配置文件位置:

    <context-param>
       <param-name>shiroEnvironmentClass</param-name>
       <param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value>
    </context-param>
        <context-param>
            <param-name>shiroConfigLocations</param-name>
            <param-value>classpath:shiro.ini</param-value>
        </context-param>

    shiroConfigLocations 默认是 “/WEB-INF/shiro.ini”,IniWebEnvironment 默认是先从 / WEB-INF/shiro.ini 加载,如果没有就默认加载 classpath:shiro.ini。

    与 Spring 集成

    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    Web INI 配置

    [main]
    #默认是/login.jsp
    authc.loginUrl=/login
    roles.unauthorizedUrl=/unauthorized
    perms.unauthorizedUrl=/unauthorized
    [users]
    zhang=123,admin
    wang=123
    [roles]
    admin=user:*,menu:*
    [urls]
    /login=anon
    /unauthorized=anon
    /static/**=anon
    /authenticated=authc
    /role=authc,roles[admin]
    /permission=authc,perms["user:create"]

    其中最重要的就是 [urls] 部分的配置,其格式是: “url = 拦截器 [参数],拦截器[参数]”;即如果当前请求的 url 匹配[urls] 部分的某个 url 模式,将会执行其配置的拦截器。比如 anon 拦截器表示匿名访问(即不需要登录即可访问);authc 拦截器表示需要身份认证通过后才能访问;roles[admin]拦截器表示需要有 admin 角色授权才能访问;而 perms["user:create"]拦截器表示需要有 “user:create” 权限才能访问。

    url 模式使用 Ant 风格模式
    Ant 路径通配符支持?、、,注意通配符匹配不包括目录分隔符 “/”:
    ?:匹配一个字符,如”/admin?” 将匹配 / admin1,但不匹配 / admin 或 / admin2;
    :匹配零个或多个字符串,如 / admin * 将匹配 / admin、/admin123,但不匹配 / admin/1;
    **:匹配路径中的零个或多个路径
    ,如 / admin/** 将匹配 / admin/a 或 / admin/a/b。

    url 模式匹配顺序

    url 模式匹配顺序是按照在配置中的声明顺序匹配,即从头开始使用第一个匹配的 url 模式对应的拦截器链。如:

    /bb/**=filter1
    /bb/aa=filter2
    /**=filter3

    如果请求的 url 是 “/bb/aa”,因为按照声明顺序进行匹配,那么将使用 filter1 进行拦截。

    拦截器将在下一节详细介绍。接着我们来看看身份验证、授权及退出在 web 中如何实现。

    身份验证(登录)

    首先配置需要身份验证的 url

    /authenticated=authc
    /role=authc,roles[admin]
    /permission=authc,perms["user:create"]

    即访问这些地址时会首先判断用户有没有登录,如果没有登录默会跳转到登录页面,默认是 / login.jsp,可以通过在 [main] 部分通过如下配置修改:

    authc.loginUrl=/login

    Shiro 拦截器机制

    拦截器机制

    拦截器介绍

    Shiro 使用了与 Servlet 一样的 Filter 接口进行扩展;所以如果对 Filter 不熟悉可以参考《Servlet 3.1 规范》http://www.iteye.com/blogs/subjects/Servlet-3-1了解 Filter 的工作原理。首先下图是 Shiro 拦截器的基础类图:

    1、NameableFilter
    NameableFilter 给 Filter 起个名字,如果没有设置默认就是 FilterName;还记得之前的如 authc 吗?当我们组装拦截器链时会根据这个名字找到相应的拦截器实例;

    2、OncePerRequestFilter
    OncePerRequestFilter 用于防止多次执行 Filter 的;也就是说一次请求只会走一次拦截器链;另外提供 enabled 属性,表示是否开启该拦截器实例,默认 enabled=true 表示开启,如果不想让某个拦截器工作,可以设置为 false 即可。

    3、ShiroFilter
    ShiroFilter 是整个 Shiro 的入口点,用于拦截需要安全控制的请求进行处理,这个之前已经用过了。

    4、AdviceFilter
    AdviceFilter 提供了 AOP 风格的支持,类似于 SpringMVC 中的 Interceptor:

    • preHandler:类似于 AOP 中的前置增强;在拦截器链执行之前执行;如果返回 true 则继续拦截器链;否则中断后续的拦截器链的执行直接返回;进行预处理(如基于表单的身份验证、授权)
    • postHandle:类似于 AOP 中的后置返回增强;在拦截器链执行完成后执行;进行后处理(如记录执行时间之类的);
    • afterCompletion:类似于 AOP 中的后置最终增强;即不管有没有异常都会执行;可以进行清理资源(如接触 Subject 与线程的绑定之类的);

    5、PathMatchingFilter

    PathMatchingFilter 提供了基于 Ant 风格的请求路径匹配功能及拦截器参数解析的功能,如“roles[admin,user]”自动根据“,”分割解析到一个路径参数配置并绑定到相应的路径:

    pathsMatch:该方法用于 path 与请求路径进行匹配的方法;如果匹配返回 true;
    onPreHandle:在 preHandle 中,当 pathsMatch 匹配一个路径后,会调用 opPreHandler 方法并将路径绑定参数配置传给 mappedValue;然后可以在这个方法中进行一些验证(如角色授权),如果验证失败可以返回 false 中断流程;默认返回 true;也就是说子类可以只实现 onPreHandle 即可,无须实现 preHandle。如果没有 path 与请求路径匹配,默认是通过的(即 preHandle 返回 true)。

    6、AccessControlFilter

    AccessControlFilter 提供了访问控制的基础功能;比如是否允许访问/当访问拒绝时如何处理等:

    isAccessAllowed:表示是否允许访问;mappedValue 就是[urls]配置中拦截器参数部分,如果允许访问返回 true,否则 false;

    onAccessDenied:表示当访问拒绝时是否已经处理了;如果返回 true 表示需要继续处理;如果返回 false 表示该拦截器实例已经处理了,将直接返回即可。

    onPreHandle 会自动调用这两个方法决定是否继续处理:

    boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
    }

    另外 AccessControlFilter 还提供了如下方法用于处理如登录成功后/重定向到上一个请求:

    void setLoginUrl(String loginUrl) //身份验证时使用,默认/login.jsp
    String getLoginUrl()
    Subject getSubject(ServletRequest request, ServletResponse response) //获取Subject 实例
    boolean isLoginRequest(ServletRequest request, ServletResponse response)//当前请求是否是登录请求
    void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException //将当前请求保存起来并重定向到登录页面
    void saveRequest(ServletRequest request) //将请求保存起来,如登录成功后再重定向回该请求
    void redirectToLogin(ServletRequest request, ServletResponse response) //重定向到登录页面

    比如基于表单的身份验证就需要使用这些功能。

    到此基本的拦截器就完事了,如果我们想进行访问访问的控制就可以继承 AccessControlFilter;如果我们要添加一些通用数据我们可以直接继承 PathMatchingFilter。

    基于表单登录拦截器

    之前我们已经使用过 Shiro 内置的基于表单登录的拦截器了,此处自己做一个类似的基于表单登录的拦截器。

    public class FormLoginFilter extends PathMatchingFilter {
        private String loginUrl = "/login.jsp";
        private String successUrl = "/";
        @Override
        protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
            if(SecurityUtils.getSubject().isAuthenticated()) {
                return true;//已经登录过
            }
            HttpServletRequest req = (HttpServletRequest) request;
            HttpServletResponse resp = (HttpServletResponse) response;
            if(isLoginRequest(req)) {
                if("post".equalsIgnoreCase(req.getMethod())) {//form表单提交
                    boolean loginSuccess = login(req); //登录
                    if(loginSuccess) {
                        return redirectToSuccessUrl(req, resp);
                    }
                }
                return true;//继续过滤器链
            } else {//保存当前地址并重定向到登录界面
                saveRequestAndRedirectToLogin(req, resp);
                return false;
            }
        }
        private boolean redirectToSuccessUrl(HttpServletRequest req, HttpServletResponse resp) throws IOException {
            WebUtils.redirectToSavedRequest(req, resp, successUrl);
            return false;
        }
        private void saveRequestAndRedirectToLogin(HttpServletRequest req, HttpServletResponse resp) throws IOException {
            WebUtils.saveRequest(req);
            WebUtils.issueRedirect(req, resp, loginUrl);
        }
        private boolean login(HttpServletRequest req) {
            String username = req.getParameter("username");
            String password = req.getParameter("password");
            try {
                SecurityUtils.getSubject().login(new UsernamePasswordToken(username, password));
            } catch (Exception e) {
                req.setAttribute("shiroLoginFailure", e.getClass());
                return false;
            }
            return true;
        }
        private boolean isLoginRequest(HttpServletRequest req) {
            return pathsMatch(loginUrl, WebUtils.getPathWithinApplication(req));
        }
    }

    onPreHandle 主要流程:

    1. 首先判断是否已经登录过了,如果已经登录过了继续拦截器链即可;
    2. 如果没有登录,看看是否是登录请求,如果是 get 方法的登录页面请求,则继续拦截器链(到请求页面),否则如果是 get 方法的其他页面请求则保存当前请求并重定向到登录页面;
    3. 如果是 post 方法的登录页面表单提交请求,则收集用户名 / 密码登录即可,如果失败了保存错误消息到 “shiroLoginFailure” 并返回到登录页面;
    4. 如果登录成功了,且之前有保存的请求,则重定向到之前的这个请求,否则到默认的成功页面。

    shiro.ini 配置

    [filters]
    formLogin=com.github.zhangkaitao.shiro.chapter8.web.filter.FormLoginFilter
    [urls]
    /test.jsp=formLogin
    /login.jsp=formLogin

    任意角色授权拦截器

    Shiro 提供 roles 拦截器,其验证用户拥有所有角色,没有提供验证用户拥有任意角色的拦截器。

    public class AnyRolesFilter extends AccessControlFilter {
        private String unauthorizedUrl = "/unauthorized.jsp";
        private String loginUrl = "/login.jsp";
        protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
            String[] roles = (String[])mappedValue;
            if(roles == null) {
                return true;//如果没有设置角色参数,默认成功
            }
            for(String role : roles) {
                if(getSubject(request, response).hasRole(role)) {
                    return true;
                }
            }
            return false;//跳到onAccessDenied处理
        }
        @Override
        protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
            Subject subject = getSubject(request, response);
            if (subject.getPrincipal() == null) {//表示没有登录,重定向到登录页面
                saveRequest(request);
                WebUtils.issueRedirect(request, response, loginUrl);
            } else {
                if (StringUtils.hasText(unauthorizedUrl)) {//如果有未授权页面跳转过去
                    WebUtils.issueRedirect(request, response, unauthorizedUrl);
                } else {//否则返回401未授权状态码
                    WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
                }
            }
            return false;
        }
    } 

    流程:

    1. 首先判断用户有没有任意角色,如果没有返回 false,将到 onAccessDenied 进行处理;
    2. 如果用户没有角色,接着判断用户有没有登录,如果没有登录先重定向到登录;
    3. 如果用户没有角色且设置了未授权页面(unauthorizedUrl),那么重定向到未授权页面;否则直接返回 401 未授权错误码。

    shiro.ini 配置

    [filters]
    anyRoles=com.github.zhangkaitao.shiro.chapter8.web.filter.AnyRolesFilter
    [urls]
    /test.jsp=formLogin,anyRoles[admin,user]
    /login.jsp=formLogin

    默认拦截器

    Shiro 内置了很多默认的拦截器,比如身份验证、授权等相关的。默认拦截器可以参考 org.apache.shiro.web.filter.mgt.DefaultFilter 中的枚举拦截器:

    默认拦截器名                                                                                              

    拦截器类                                                                                                                                                                                

    说明(括号里的表示默认值)

    身份验证相关的

    authc

    org.apache.shiro.web.filter.authc

    .FormAuthenticationFilter

    基于表单的拦截器;如 “`/**=authc`”,如果没有登录会跳到相应的登录页面登录;主要属性:usernameParam:表单提交的用户名参数名( username);  passwordParam:表单提交的密码参数名(password); rememberMeParam:表单提交的密码参数名(rememberMe);  loginUrl:登录页面地址(/login.jsp);successUrl:登录成功后的默认重定向地址; failureKeyAttribute:登录失败后错误信息存储 key(shiroLoginFailure);

    authcBasic

    org.apache.shiro.web.filter.authc

    .BasicHttpAuthenticationFilter

    Basic HTTP 身份验证拦截器,主要属性: applicationName:弹出登录框显示的信息(application);

    logout

    org.apache.shiro.web.filter.authc

    .LogoutFilter

    退出拦截器,主要属性:redirectUrl:退出成功后重定向的地址(/); 示例 “/logout=logout”

    user

    org.apache.shiro.web.filter.authc

    .UserFilter

    用户拦截器,用户已经身份验证 / 记住我登录的都可;示例 “/**=user”

    anon

    org.apache.shiro.web.filter.authc

    .AnonymousFilter

    匿名拦截器,即不需要登录即可访问;一般用于静态资源过滤;示例 “/static/**=anon”

    授权相关的

    roles

    org.apache.shiro.web.filter.authz

    .RolesAuthorizationFilter

    角色授权拦截器,验证用户是否拥有所有角色;主要属性: loginUrl:登录页面地址(/login.jsp);unauthorizedUrl:未授权后重定向的地址;示例 “/admin/**=roles[admin]”

    perms

    org.apache.shiro.web.filter.authz

    .PermissionsAuthorizationFilter

    权限授权拦截器,验证用户是否拥有所有权限;属性和 roles 一样;示例 “/user/**=perms["user:create"]”

    port

    org.apache.shiro.web.filter.authz

    .PortFilter

    端口拦截器,主要属性:port(80):可以通过的端口;示例 “/test= port[80]”,如果用户访问该页面是非 80,将自动将请求端口改为 80 并重定向到该 80 端口,其他路径 / 参数等都一样

    rest

    org.apache.shiro.web.filter.authz

    .HttpMethodPermissionFilter

    rest 风格拦截器,自动根据请求方法构建权限字符串(GET=read, POST=create,PUT=update,DELETE=delete,HEAD=read,TRACE=read,OPTIONS=read, MKCOL=create)构建权限字符串;示例 “/users=rest[user]”,会自动拼出“user:read,user:create,user:update,user:delete” 权限字符串进行权限匹配(所有都得匹配,isPermittedAll);

    ssl

    org.apache.shiro.web.filter.authz

    .SslFilter

    SSL 拦截器,只有请求协议是 https 才能通过;否则自动跳转会 https 端口(443);其他和 port 拦截器一样;

    其他

    noSessionCreation

    org.apache.shiro.web.filter.session

    .NoSessionCreationFilter

    不创建会话拦截器,调用 subject.getSession(false) 不会有什么问题,但是如果 subject.getSession(true) 将抛出 DisabledSessionException 异常;

  • 相关阅读:
    babel缓存 非常实用(8)
    source-map 非常实用(7)
    webpack -HMR-非常实用(6)
    eslint 语法检查(5)
    对css 的处理(4)
    python之再学习----简单的字符串
    windows下安装django的具体步骤和各种问题
    Linux常用命令
    转:程序中得到SVN的版本号
    vue实战(1):准备与资料整理
  • 原文地址:https://www.cnblogs.com/wangxiayun/p/9181994.html
Copyright © 2011-2022 走看看