2013-05-03 32 views
7

我加入阿帕奇四郎到我的應用程序,我想知道,如果出現以下錯誤消息是真正準確:Shiro中未綁定的SecurityManager真的是無效的應用程序配置嗎?

org.apache.shiro.UnavailableSecurityManagerException:沒有安全管理器訪問到調用代碼,無論是綁定到org.apache.shiro.util.ThreadContext或者作爲一個vm靜態單例。這是一個無效的應用程序配置。

我已經通過源代碼中的位和的印象,我得到的是,只要我不使用SecurityUtils,我願意通過一個SecurityManager到需要它,我不看了組件實際上不需要將SecurityManager分配給SecurityUtils使用的靜態單例。

我想避免的具體事情是讓Shiro將任何東西放入ThreadLocal或讓Shiro使用它的ThreadContext支持類。我正在使用Apache Thrift,不想讓自己參與每個請求的單線程網絡設計。我對Shiro的要求很少,所以我會在下面展示我正在做的事情。

我使用吉斯在我的應用程序,但我用shiro-guice因爲四郎AOP的東西依賴於具有與ThreadContext相關的Subject。相反,我從一個非常簡單的Guice模塊開始。

public class ShiroIniModule extends AbstractModule { 
    @Override 
    protected void configure() {} 

    @Provides 
    @Singleton 
    public SecurityManager provideSecurityManager() { 
     return new DefaultSecurityManager(new IniRealm("classpath:shiro.ini")); 
    } 
} 

這不完全是一個生產質量領域/安全管理器設置,但它足以讓我測試。接下來,我創建了我自己的管理員類,其範圍非常有限,我的應用程序組件使用它。我有兩個;一個ThriftAuthenticationManager和一個ThriftAuthorizationManager。這裏是前者:

@Singleton 
public class ThriftAuthenticationManager { 
    private final Logger log = LoggerFactory.getLogger(ThriftAuthenticationManager.class); 

    private final SecurityManager securityManager; 

    @Inject 
    public ThriftAuthenticationManager(SecurityManager securityManager) { 
     this.securityManager = securityManager; 
    } 

    public String authenticate(String username, String password) throws TException { 
     try { 
      Subject currentUser = new Subject.Builder(securityManager).buildSubject(); 

      if (!currentUser.isAuthenticated()) { 
       currentUser.login(new UsernamePasswordToken(username, password)); 
      } 

      String authToken = currentUser.getSession().getId().toString(); 
      Preconditions.checkState(!Strings.isNullOrEmpty(authToken)); 
      return authToken; 
     } 
     catch (AuthenticationException e) { 
      throw Exceptions.security(SecurityExceptions.AUTHENTICATION_EXCEPTION); 
     } 
     catch(Throwable t) { 
      log.error("Unexpected error during authentication.", t); 
      throw new TException("Unexpected error during authentication.", t); 
     } 

    } 
} 

而後者:

@Singleton 
public class ThriftAuthorizationManager { 
    private final Logger log = LoggerFactory.getLogger(ThriftAuthorizationManager.class); 

    private final SecurityManager securityManager; 

    @Inject 
    public ThriftAuthorizationManager(SecurityManager securityManager) { 
     this.securityManager = securityManager; 
    } 

    public void checkPermissions(final String authToken, final String permissions) 
      throws TException { 
     withThriftExceptions(new Callable<Void>() { 
      @Override 
      public Void call() throws Exception { 
       securityManager.checkPermission(getPrincipals(authToken), permissions); 
       return null; 
      } 
     }); 
    } 

    public void checkPermission(final String authToken, final Permission permission) 
      throws TException { 
     withThriftExceptions(new Callable<Void>() { 
      @Override 
      public Void call() throws Exception { 
       securityManager.checkPermission(getPrincipals(authToken), permission); 
       return null; 
      } 
     }); 
    } 

    private Subject getSubject(String authToken) { 
     return new Subject.Builder(securityManager).sessionId(authToken).buildSubject(); 
    } 

    private PrincipalCollection getPrincipals(String authToken) { 
     return getSubject(authToken).getPrincipals(); 
    } 

    private void withThriftExceptions(Callable<Void> callable) throws TException { 
     try { 
      callable.call(); 
     } 
     catch(SessionException e) { 
      throw Exceptions.security(SecurityExceptions.SESSION_EXCEPTION); 
     } 
     catch(UnauthenticatedException e) { 
      throw Exceptions.security(SecurityExceptions.UNAUTHENTICATED_EXCEPTION); 
     } 
     catch(AuthorizationException e) { 
      throw Exceptions.security(SecurityExceptions.AUTHORIZATION_EXCEPTION); 
     } 
     catch(ShiroException e) { 
      throw Exceptions.security(SecurityExceptions.SECURITY_EXCEPTION); 
     } 
     catch(Throwable t) { 
      log.error("An unexpected error occurred during authorization.", t); 
      throw new TException("Unexpected error during authorization.", t); 
     } 
    } 
} 

我節儉服務使用上述兩類用於身份驗證和授權。例如:

@Singleton 
public class EchoServiceImpl implements EchoService.Iface { 
    private final Logger log = LoggerFactory.getLogger(EchoServiceImpl.class); 

    private final ThriftAuthorizationManager authorizor; 

    @Inject 
    public EchoServiceImpl(ThriftAuthorizationManager authorizor) { 
     this.authorizor = authorizor; 
    } 

    @Override 
    public Echo echo(String authToken, Echo echo) throws TException { 
     authorizor.checkPermissions(authToken, "echo"); 
     return echo; 
    } 
} 

所以,我想我其實有幾個任務。

  1. 我引用的錯誤實際上是一個錯誤還是僅僅是過度熱情的日誌消息?

  2. 如果我從不使用ShiroUtils,我需要擔心Shiro依賴ThreadContext中的任何內容嗎?

  3. 如果我無法保證單線程每請求的環境,那麼使用SecurityUtils#setSecurityManager會有什麼壞處嗎?

  4. 我還沒有嘗試使用Shiro的高級權限(org.apache.shiro.authz.Permission)呢。他們是否依賴ThreadContext中的任何東西,或做任何奇怪的事情,我應該早點看看?

  5. 我做了其他任何可能會導致我出現問題的事情,還是我可以改進任何事情?

回答

7
  1. 引用的錯誤僅僅是,如果你想打電話SecurityUtils.getSecurityManager()錯誤。您不僅可以手動傳遞它,也可以將它作爲SecurityUtils用法之外的依賴注入。

  2. SecurityUtils對於那些使用Shiro的用戶來說,如果他們想要使用它們,大多是一種方便。 Shiro中只有幾件事實際上明確地稱之爲:一些Shiro的AspectJ集成稱之爲,一些Shiro的Web支持稱它爲(Servlet Filters,JSP和JSF taglibs)。然而,在Shiro使用它的情況下,它(我認爲)總是通過模板方法進行調用,如果需要,您可以重寫該方法以從其他位置獲取主題。

  3. 只要您對整個JVM的單個SecurityManager實例感到滿意,就不會調用SecurityUtils.setSecurityManager。如果您有多個Shiro應用在同一個JVM中使用該方法,則可能會導致問題。即便如此但是,因爲靜態存儲器調用和全局狀態是evil,如果你能找到引用的SecurityManager的甚至另一種方式,那會更好(如依賴注入)

  4. 四郎權限不依賴於任何ThreadContext相關。許可檢查委託給一個或多個Realm,他們最終決定什麼是允許或不允許的。大多數領域反過來使用授權Cache來確保權限查找保持良好和響應。但這不是線程狀態 - 它是(非靜態的)應用程序單例狀態。

  5. 你的代碼看起來不錯。 ThriftAuthorizationManager授權檢查的一個建議是直接委託給主體(主體又委託給SecurityManager)。這個我覺得是一點點比目前的做法更好的自我記錄IMO更快:

    return getSubject(authToken).checkPermission(permission); 
    

感謝您分享您的問題。它總是很高興看到框架的非Web用途,因爲它是專爲所有工作負載。

4

這是我發現,當我遇到了同樣的問題來了:我加入了四郎篩選語句到我的web.xml文件,然後直接叫我的豆主題。 我說:

<filter> 
     <filter-name>ShiroFilter</filter-name> 
     <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class> 
</filter> 
<filter-mapping> 
     <filter-name>ShiroFilter</filter-name> 
     <url-pattern>/*</url-pattern> 
     <dispatcher>REQUEST</dispatcher> 
     <dispatcher>FORWARD</dispatcher> 
     <dispatcher>INCLUDE</dispatcher> 
     <dispatcher>ERROR</dispatcher> 
</filter-mapping> 

並在那之後我就寫去只是下面的語句:

Subject subject = SecurityUtils.getSubject(); 
相關問題