[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角色,已经有多入口,前后台分开的功能,


        
        
        
        
        
            
                
                
            
        
        
        
            
                /user.html = authc
                /admin.html =authc
                /login.html = authc
                /admin/login.html =admin
                /logout = logout
                /resource/** = anon
            
        
    

    
        
        
    
    
        
        
    
    
        
    
    
        
    

    
    
    
        
            
                
                
            
        
    
    

在我们点击登陆后,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 realms;
//迭代Realm进行验证
   protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        assertRealmsConfigured();
        Collection 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 filters = getFilters();
        if (!CollectionUtils.isEmpty(filters)) {
            for (Map.Entry 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 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 realms = defineRealms.values();
        if (realms.size() == 1) {
            return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
        } else {
            return doMultiRealmAuthentication(realms, authenticationToken);
        }
    }
    
    public void setDefineRealms(Map defineRealms) {
        this.defineRealms = defineRealms;
    }

    public Map getDefineRealms() {
        return defineRealms;
    }
}

在xml中的配置修改为


        
        
        
        
        
            
                
                
            
        
        
        
            
                /user.html = authc
                /admin.html =authc
                /login.html = authc
                /admin/login.html =admin
                /logout = logout
                /resource/** = anon
            
        
    
    
    
        
            
                
                
            
        
    
    
        
        
    
    
        
        
    
    
        
    
    
        
    
    
    
    
        
    
    

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

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

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

权限校验问题修复

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





    

        

            

                

                

                

                


                

                

                

                


                

                

                

            

        

    

 
    
    

        

    


    
    

        

    

 

    


        

        

        

        

        

        

        

        

        

        

    

你可能感兴趣的:([Shiro] filter&realm绑定 多登陆入口,区分前后台)