[Shiro] filter&realm绑定 多登陆入口,区分前后台

Shiro框架 filter&realm绑定

这种实现方式有问题,会影响到后续的角色验证。不建议看了

新的实现方式 : 自定义Token


  • shiro filter与Realm绑定
  • 使用Spring整合Shiro
  • 多登陆入口,成功后跳转到不同地址
  • 重写Realm获取方式

最近在项目中使用了apache的轻量级Shiro框架进行权限管理,使用了多个filter,Shiro会默认迭代Realm进行登陆,即使第一个Realm通过校验,也会继续迭代,造成性能的浪费,和数据库查询。又或者前Realm里做了一些校验,抛出了异常,也会被后面的无用的Realm校验失败抛出的一场覆盖。


不废话,上教程不废话,上教程
注意:这里filter统一指shiro 定义的filter
至于servlet的filter会写成 servlet filter

这是先前的配置

已经实现方式赋予Realm对应的role角色,已经有多入口,前后台分开的功能,

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- shiro 的核心安全接口 -->
        <property name="securityManager" ref="securityManager" />
        <!-- 未授权时要跳转的连接 -->
        <property name="unauthorizedUrl" value="/unauthorized.jsp" />
        <property name="filters">
            <map>
                <entry key="admin" value-ref="adminformAuthenticationFilter" />
                <entry key="authc" value-ref="formAuthenticationFilter" />
            </map>
        </property>
        <!-- shiro 连接约束配置 -->
        <property name="filterChainDefinitions">
            <value>
                /user.html = authc
                /admin.html =authc
                /login.html = authc
                /admin/login.html =admin
                /logout = logout
                /resource/** = anon
            </value>
        </property>
    </bean>

    <bean id="formAuthenticationFilter"
        class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
        <property name="loginUrl" value="/login.html" />
        <property name="successUrl" value="/ok.html" />
    </bean>
    <bean id="adminformAuthenticationFilter"
        class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
        <property name="loginUrl" value="/admin/login.html" />
        <property name="successUrl" value="/admin/ok.html" />
    </bean>
    <bean id="AdminRealm" class="cc.yihy.realm.AdminRealm">
        <property name="userService" ref="UserService" />
    </bean>
    <bean id="UserRealm" class="cc.yihy.realm.UserRealm">
        <property name="userService" ref="UserService" />
    </bean>

    <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <!-- 默认的Realm验证 -->
        <property name="realms">
            <list>
                <ref bean="UserRealm" />
                <ref bean="AdminRealm"></ref>
            </list>
        </property>
    </bean>
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

在我们点击登陆后,Shiro会使用我们配置的FormAuthenticationFilter进行验证。

执行

//父类里实现的方法
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
    //这里创建用户的令牌
        AuthenticationToken token = createToken(request, response);
        if (token == null) {
            String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +
                    "must be created in order to execute a login attempt.";
            throw new IllegalStateException(msg);
        }
        try {
            Subject subject = getSubject(request, response);
            //在这里进行登陆验证
            subject.login(token);
            return onLoginSuccess(token, subject, request, response);
        } catch (AuthenticationException e) {
            return onLoginFailure(token, e, request, response);
        }
    }

      protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
        String username = getUsername(request);
        String password = getPassword(request);
        return createToken(username, password, request, response);
    }
    //父类的创建令牌的实现
     protected AuthenticationToken createToken(String username, String password,
                                              ServletRequest request, ServletResponse response) {
        boolean rememberMe = isRememberMe(request);
        String host = getHost(request);
        return createToken(username, password, rememberMe, host);
    }
    //父类的创建令牌的实现
   protected AuthenticationToken createToken(String username, String password,
                                              boolean rememberMe, String host) {
        return new UsernamePasswordToken(username, password, rememberMe, host);
    }

subject.login(token);的实现类里,又调用了安全管理器的login。传的对象是一个Token令牌。

再看看安全管理器里面是怎么进行登录的

Subject subject = securityManager.login(this, token);

 //安全管理器实现类DefaultSecurityManager里面的login
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
      //
        AuthenticationInfo info;
        try {
            info = authenticate(token);
        } catch (AuthenticationException ae) {
            try {
                onFailedLogin(token, ae, subject);
            } catch (Exception e) {
                if (log.isInfoEnabled()) {
                    log.info("onFailedLogin method threw an " +
                            "exception.  Logging and propagating original AuthenticationException.", e);
                }
            }
            throw ae; //propagate
        }

        Subject loggedIn = createSubject(token, info, subject);

        onSuccessfulLogin(token, info, loggedIn);

        return loggedIn;
    }
  //这是它的父类里面 
   private Authenticator authenticator;

   public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
       //真正进行登陆Realm的在这里
        return this.authenticator.authenticate(token);
    }

还是看下Authenticator接口实现类(ModularRealmAuthenticator)的源码

 这是配置的Realm集合
private Collection<Realm> realms;
//迭代Realm进行验证
   protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        assertRealmsConfigured();
        Collection<Realm> realms = getRealms();
        if (realms.size() == 1) {
            return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
        } else {
            return doMultiRealmAuthentication(realms, authenticationToken);
        }
    }

到这里,filter调用realm进行验证的流程基本已经清晰了。

filter创建一个Token对象,传到安全管理器(securityManager),让安全管理器调用Authenticator属性进行迭代Realm。
怎么让Filter找到对应权限的realm呢?他们中间传递一一个对象Token令牌。
Token里放了这些基本信息
new UsernamePasswordToken(username, password, rememberMe, host);

从filter到realm并没有角色名的传递。
filter的name属性里放的是角色名。这点在Spring创建ShiroFilter 可以看到。

//getFilters()拿到的是xml配置上的filters
        Map<String, Filter> filters = getFilters();
        if (!CollectionUtils.isEmpty(filters)) {
            for (Map.Entry<String, Filter> entry : filters.entrySet()) {
                String name = entry.getKey();
                Filter filter = entry.getValue();
                applyGlobalPropertiesIfNecessary(filter);
                if (filter instanceof Nameable) {
                    ((Nameable) filter).setName(name);
                }
                //'init' argument is false, since Spring-configured filters should be initialized
                //in Spring (i.e. 'init-method=blah') or implement InitializingBean:
                manager.addFilter(name, filter, false);
            }
        }

现在想要把filter的信息传给realm就需要在他们之间传递的对象动手脚了。让Token把Filter对象带过去就行了。
那那边怎么处理呢。

实例安全管理器的时候,会实例一个ModularRealmAuthorizer对象,并可以设置为我们自定义的。

 private Authorizer authorizer;
//安全管理器实现类的父类,在实例安全管理器的时候,会创建一个ModularRealmAuthorizer对象,迭代Realm验证,就是ModularRealmAuthorizer做的工作
    /**
     * Default no-arg constructor that initializes an internal default
     * {@link org.apache.shiro.authz.ModularRealmAuthorizer ModularRealmAuthorizer}.
     */
    public AuthorizingSecurityManager() {
        super();
        this.authorizer = new ModularRealmAuthorizer();
    }

   public void setAuthorizer(Authorizer authorizer) {
        if (authorizer == null) {
            String msg = "Authorizer argument cannot be null.";
            throw new IllegalArgumentException(msg);
        }
        this.authorizer = authorizer;
    }

到这里,Filter&Realm绑定需要修改的地方基本都知道了。
重写FormAuthenticationFilter的createToken(String username, String password,boolean rememberMe, String host)方法自定义一个Token,可以存放Filter重写ModularRealmAuthorizer对象中获取Realm的方法。

下面是实现代码

NewFormAuthenticationFilter 实现

public class NewFormAuthenticationFilter extends FormAuthenticationFilter {
    /**
     * 创建自定义的令牌,加入当前filter
     */
    @Override
    protected AuthenticationToken createToken(String username, String password,
            boolean rememberMe, String host) {
        return new UsernamePasswordAndFilterToken(username, password,
                rememberMe, host, this);
    }

    /**
     * 获取当前Filter的名字(角色名)扩大访问范围
     */
    @Override
    public String getName() {
        return super.getName();
    }

}

UsernamePasswordAndFilterToken 实现

public class UsernamePasswordAndFilterToken extends UsernamePasswordToken {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    /**
     * 存放当前Filter
     */
    private NameableFilter loginFilter;

    public UsernamePasswordAndFilterToken() {
        super();
    }

    public UsernamePasswordAndFilterToken(final String username,
            final char[] password, final boolean rememberMe, final String host,
            final AdviceFilter loginFilter) {
        super(username, password, rememberMe, host);
        this.loginFilter = loginFilter;
    }

    public UsernamePasswordAndFilterToken(final String username,
            final String password, final boolean rememberMe, final String host,
            final AdviceFilter loginFilter) {
        super(username, password, rememberMe, host);
        this.loginFilter = loginFilter;
    }

    public NameableFilter getLoginFilter() {
        return loginFilter;
    }

    public void setLoginFilter(NameableFilter loginFilter) {
        this.loginFilter = loginFilter;
    }
}

DefineModularRealmAuthenticator 实现

public class DefineModularRealmAuthenticator extends ModularRealmAuthenticator {

    private static final Logger log = LoggerFactory
            .getLogger(DefineModularRealmAuthenticator.class);
    
    private Map<String, Realm> defineRealms;

    /**
     * 判断Realm是不是null
     */
    @Override
    protected void assertRealmsConfigured() throws IllegalStateException {
        defineRealms = getDefineRealms();
        if (CollectionUtils.isEmpty(defineRealms)) {
            String msg = "Configuration error:  No realms have been configured!  One or more realms must be "
                    + "present to execute an authentication attempt.";
            throw new IllegalStateException(msg);
        }
    }
    /**
     * 根据filter的name 取对应realm进行登录
     */
    @Override
    protected AuthenticationInfo doAuthenticate(
            AuthenticationToken authenticationToken)
            throws AuthenticationException {
        assertRealmsConfigured();
    
        /**
         * authenticationToken 如果是自定义的UsernamePasswordAndFilterToken,调用单个realm
         * 否则 使用默认的迭代realm方式
         */
        if(authenticationToken instanceof UsernamePasswordAndFilterToken){
                NewFormAuthenticationFilter loginFilter = (NewFormAuthenticationFilter)((UsernamePasswordAndFilterToken) authenticationToken).getLoginFilter();

                Realm realm = defineRealms.get(loginFilter.getName());
                if(realm==null){
                    log.error("没有配置NewFormAuthenticationFilter对应的Realm");
                    throw new RuntimeException("没有配置NewFormAuthenticationFilter对应的Realm");
                };
                return doSingleRealmAuthentication(realm,authenticationToken);
        }else {
            return oldDoAuthenticate(authenticationToken);
        }
            

    }

    private  AuthenticationInfo oldDoAuthenticate(AuthenticationToken authenticationToken)throws AuthenticationException{
        
        Collection<Realm> realms = defineRealms.values();
        if (realms.size() == 1) {
            return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
        } else {
            return doMultiRealmAuthentication(realms, authenticationToken);
        }
    }
    
    public void setDefineRealms(Map<String, Realm> defineRealms) {
        this.defineRealms = defineRealms;
    }

    public Map<String, Realm> getDefineRealms() {
        return defineRealms;
    }
}

在xml中的配置修改为

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- shiro 的核心安全接口 -->
        <property name="securityManager" ref="securityManager" />
        <!-- 未授权时要跳转的连接 -->
        <property name="unauthorizedUrl" value="/unauthorized.jsp" />
        <property name="filters">
            <map>
                <entry key="admin" value-ref="adminformAuthenticationFilter" />
                <entry key="authc" value-ref="formAuthenticationFilter" />
            </map>
        </property>
        <!-- shiro 连接约束配置 -->
        <property name="filterChainDefinitions">
            <value>
                /user.html = authc
                /admin.html =authc
                /login.html = authc
                /admin/login.html =admin
                /logout = logout
                /resource/** = anon
            </value>
        </property>
    </bean>
    <!-- filter&realm绑定 -->
    <bean id="DefineModularRealmAuthenticator"
        class="cc.yihy.shiro.authc.pam.DefineModularRealmAuthenticator">
        <property name="defineRealms">
            <map>
                <entry key="authc" value-ref="UserRealm" />
                <entry key="admin" value-ref="AdminRealm" />
            </map>
        </property>
    </bean>
    <bean id="formAuthenticationFilter"
        class="cc.yihy.shiro.filter.NewFormAuthenticationFilter">
        <property name="loginUrl" value="/login.html" />
        <property name="successUrl" value="/ok.html" />
    </bean>
    <bean id="adminformAuthenticationFilter"
        class="cc.yihy.shiro.filter.NewFormAuthenticationFilter">
        <property name="loginUrl" value="/admin/login.html" />
        <property name="successUrl" value="/admin/ok.html" />
    </bean>
    <bean id="AdminRealm" class="cc.yihy.realm.AdminRealm">
        <property name="userService" ref="UserService" />
    </bean>
    <bean id="UserRealm" class="cc.yihy.realm.UserRealm">
        <property name="userService" ref="UserService" />
    </bean>
    <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <!-- filter&realm绑定 -->
        <property name="authenticator" ref="DefineModularRealmAuthenticator" />
    </bean>
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

到此,绑定工作完成。。。

欢迎拍砖提问。
用MarkDown写博客感觉还不错。
表示差点把Shiro开始创建执行的过程写上去。

在后面进行页面上权限校验会报错,原因出在改变了realm的注入方式。解决办法,近期我会整理出来。

权限校验问题修复

权限校验会报错是因为修改了注入realm的位置,导致授权解释器拿不到realm集合,现在把授权解释器重写了,并注入realms,使其恢复正常



<!-- realms Map -->

    <bean id="defineRealms" class="org.springframework.beans.factory.config.MapFactoryBean">

        <property name="sourceMap">

            <map>

                <entry key="authc" value-ref="SSOUserRealm"/>

                <!--<entry key="NormalUser" value-ref="UserRealm"/>-->

                <!--<entry key="authUser" value-ref="UserRealm"/>-->

                <entry key="SSOUser" value-ref="SSOUserRealm"/>


                <entry key="mxUser" value-ref="AUserRealm"/>

                <entry key="normalMx" value-ref="AUserRealm"/>

                <entry key="mxAdmin" value-ref="AUserRealm"/>

                <entry key="subMxAdmin" value-ref="AUserRealm"/>


                <entry key="xhAdmin" value-ref="XhUserRealm"/>

                <entry key="xhUser" value-ref="XhUserRealm"/>

                <entry key="subXhUser" value-ref="XhUserRealm"/>

            </map>

        </property>

    </bean>

 <!-- filter&realm绑定 -->
    <!--认证解释器-->
    <bean id="DefineModularRealmAuthenticator"

          class="cc.yihy.shiro.authc.pam.DefineModularRealmAuthenticator">

        <property name="defineRealms" ref="defineRealms"/>

    </bean>


    <!--授权解释器-->
    <bean id="DefineModularRealmAuthorizer"

          class="cc.yihy.shiro.authz.DefineModularRealmAuthorizer">

        <property name="defineRealms" ref="defineRealms"/>

    </bean>

 <!-- securityManager安全管理器 -->

    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">


        <!-- filter&realm绑定 登录 -->

        <property name="authenticator" ref="DefineModularRealmAuthenticator"/>

        <!-- filter&realm绑定 角色权限验证 -->

        <property name="authorizer" ref="DefineModularRealmAuthorizer"/>

        <!-- 注入缓存管理器 -->

        <property name="cacheManager" ref="ShiroCacheManager"/>

        <!-- 注入session管理器 -->

        <property name="sessionManager" ref="sessionManager"/>

        <!-- 记住我 -->

        <property name="rememberMeManager" ref="rememberMeManager"/>

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

推荐阅读更多精彩内容

  • 文章转载自:http://blog.csdn.net/w1196726224/article/details/53...
    wangzaiplus阅读 3,393评论 0 3
  • 构建一个互联网应用,权限校验管理是很重要的安全措施,这其中主要包含: 认证 - 用户身份识别,即登录 授权 - 访...
    zhuke阅读 3,495评论 0 30
  • 一、架构 要学习如何使用Shiro必须先从它的架构谈起,作为一款安全框架Shiro的设计相当精妙。Shiro的应用...
    ITsupuerlady阅读 3,528评论 4 32
  • Shiro的使用方法参见跟我学shiro Shiro的验证过程分析如下: 获取SecurityManager 并绑...
    王兆阳阅读 1,555评论 0 0
  • 劳动节,我们需要休息,不劳动啦,带着小朋友去玩耍。 不到公园不知道小朋友有多淘,先是各种疯跑,到湖边后就各种要下水...
    涡孩缇阅读 103评论 0 0