shiro(10)-shiroFilter(url上的权限控制)

shiro安全控制目录

Shiro既然作为一个安全框架,必然有一个核心的Filter来过滤处理请求。若是在web.xml配置繁杂的shiroFilter过滤器链,不仅会依赖底层容器,而且不能利用Spring容器的特性。于是通过DelegatingFilterProxy类,使用Spring来管理Servlet Filter,并且还可以在其中定义一系列的Shiro Filter。

1. shiro Filter

拦截器名 拦截器类 说明
认证
authc FormAuthenticationFilter 基于表单的拦截器,没有认证会跳到相应的登录页面登录;
authcBasic BasicHttpAuthenticationFilter Basic HTTP身份验证拦截器,Realm进行认证;
logout LogoutFilter 登出拦截器;
user UserFilter 用户拦截器,用户认证后和RememberMe都可通过;shiro authc和user的区别
授权
roles RolesAuthorizationFilter 角色授权拦截器,验证用户是否拥有所有角色;
perms PermissionsAuthorizationFilter 权限授权拦截器,验证用户是否拥有所有权限;
port PortFilter 端口拦截器
rest HttpMethodPermissionFilter rest风格拦截器,自动根据请求方法构建权限字符串,示例“/users=rest[user]”,会自动拼出“user:read,user:create,user:update,user:delete”权限字符串进行权限匹配(所有都得匹配,isPermittedAll);
ssl authz.SslFilter SSL拦截器,只有请求协议是https才能通过;否则自动跳转会https端口(443);其他和port拦截器一样;

一般来说,用户的登录/注册请求不进行拦截,故使用anon拦截器。当调用Realm完成认证后,可以对url路径进行认证/授权过滤器配置(其作用可以替代方法上的角色/权限注解),例如对/**=authc配置,即对所有路径都会都会校验是否完成认证操作。

1.2 拦截器的书写风格

  • 支持Ant风格模式;
  • ?:匹配一个字符;
  • *:匹配零个或多个字符串;
  • **:匹配零个或多个路径;

1.3 拦截器匹配顺序

采用的是第一次匹配优先的方式。即使用第一个匹配的url拦截器链。
如:
-/bb/**=filter1
-/bb/aa=filter2
-/**=filter3
实际上请求的url是"\bb\aa",那么将使用filter1进行拦截。

1.4 过滤器的意义

1.4.1. 无参过滤器

  • /admin/** = anon :无参,表示可匿名访问
  • /admin/user/** = authc :无参,表示需要认证才能访问
  • /admin/user/** = authcBasic :无参,表示需要httpBasic认证才能访问
  • /admin/user/** = ssl :无参,表示需要安全的URL请求,协议为https
  • /home = user :表示用户不一定需要通过认证,只要曾被 Shiro 记住过登录状态就可以正常发起 /home 请求。【即结束会话后,但是shiro记住了登录状态,依旧可以登录】

1.4.2. 有参过滤器

  • /edit = authc,perms[admin:edit] :表示用户必需已通过认证,并拥有 admin:edit 权限才可以正常发起 /edit 请求。
  • /admin = authc,roles[admin] :表示用户必需已通过认证,并拥有 admin 角色才可以正常发起 /admin 请求。
  • /admin/user/** = port[8081] :当请求的URL端口不是8081时,跳转到schemal://serverName:8081?queryString
  • /admin/user/** = rest[user] :根据请求方式来识别,相当于 /admins/user/**=perms[user:get]或perms[user:post] 等等。
  • /admin** = roles["admin,guest"] :允许多个参数(逗号分隔),此时要全部通过才算通过,相当于hasAllRoles()。
    /admin** = perms["user:add:,user:del:"]:允许多个参数(逗号分隔),此时要全部通过才算通过,相当于isPermitedAll()

1.4.3. 退出登录

  • /logout = logout

1.5 拦截器的方法

1.5.1. AdviceFilter

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

 boolean preHandle(ServletRequest request, ServletResponse response) throws Exception  
 void postHandle(ServletRequest request, ServletResponse response) throws Exception  
 void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception;   

1.5.2. PathMatchingFilter

PathMatchingFilter提供了基于Ant风格的请求路径匹配功能以及拦截器参数解析的功能。

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

1.5.3 AccessControlFilter

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

abstract boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception;   boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception;  
abstract boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception;   
  • isAccessAllowed:表示是否允许访问,mappedValue就是[urls]配置的拦截器参数部分,如果允许则返回true,否则返回false。若返回false,则会调用onAccessDenied方法。
  • onAccessDenied:当拒绝访问时,请求是否处理;若返回true,则表示需要继续处理,若返回false,则证明该请求已被最终处理,直接返回给浏览器。

注:若是onAccessDenied [dɪˈnaɪd] 返回true,说明该请求通过该过滤器,会继续调用后面的过滤器链或者直接请求到对应资源。

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

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

若是我们想进行访问控制,可以继承AccessControlFilter;如果我们想添加一些通用数据,我们可以直接继承PathMatchingFilter。

2. shiro自定义过滤器项目实战

注:每一次请求,都会按照从上到下的配置调用shiro配置的过滤器链。

实战一:/admin** = roles[admin,guest]含义是用户拥有所有角色信息才能访问该URL地址,但是我们自定义一个过滤器,拥有某一角色,便可访问。

 <!-- Shiro主过滤器本身功能十分强大,其强大之处就在于它支持任何基于URL路径表达式的、自定义的过滤器的执行 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="filterChainDefinitions">
            <value>
                <!-- Shiro 过滤链的定义 -->
                /login/init/** = anon <!-- 对于登录相关不进行鉴权 -->
                /admin** =rolesOr[admin,guest]
                /** = user
            </value>
        </property>

        <property name="filters">
            <map>
                <entry key="user" value-ref="userFilter"/>
                <entry key="rolesOr" value-ref="rolesOrFilter"/>
            </map>
        </property>
    </bean>

可参考org.apache.shiro.web.filter.authz.RolesAuthorizationFilter实现自定义过滤器。

@Service("rolesOrFilter")
public class RolesOrFilter extends AuthorizationFilter {

    /**
     * 授权过滤器,是否允许登录
     *
     * @param request
     * @param response
     * @param mappedValue
     * @return
     * @throws Exception
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        //调用父类方法获取subject对象
        Subject subject = getSubject(request, response);
        //自定义拦截器会被配置为一个标签,我们会用标签控制访问路径
        String[] roles = (String[]) mappedValue;
        if (roles != null && roles.length != 0) {
            for (String role : roles) {
                if (subject.hasRole(role)) {
                    return true;
                }
            }
        }
        return false;
    }
}

案例二当session过期后,用户使用ajax调用时,返回特定的状态码,而不是shiro默认的重定向到登录页面。

SystemUserFilter继承org.apache.shiro.web.filter.authc.UserFilter类,当一个请求url匹配该shiro Filter,便执行isAccessAllowed方法(因为自定义类并未重写,故继承父类的方法),判断用户是否认证或设置RememberMe,若设置,则返回true放行。否则的话,执行onAccessDenied方法,若onAccessDenied依旧返回false,则拒绝该次请求。

@Service("userFilter")
public class SystemUserFilter extends UserFilter {
    
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        if (isAjaxRequest(httpRequest)) {
            httpResponse.setStatus(911); // ajax, session超时
        } else {
            saveRequestAndRedirectToLogin(request, response);
        }
        return false;
    }


    private boolean isAjaxRequest(HttpServletRequest httpRequest) {
        String accept = httpRequest.getHeader("accept");
        String xRequestedWith = httpRequest.getHeader("X-Requested-With");

        // 如果是异步请求
        return ((accept != null && accept.indexOf("application/json") != -1)
                || (xRequestedWith != null && xRequestedWith.indexOf("XMLHttpRequest") != -1));
    }
}

文章参考

shiro中Filter过滤器管理

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,809评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,189评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,290评论 0 359
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,399评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,425评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,116评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,710评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,629评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,155评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,261评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,399评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,068评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,758评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,252评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,381评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,747评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,402评论 2 358