2012-10-26 275 views
9

Java安全是我在過去幾個星期的主要議題,我歸檔以下內容:JAAS - Java EE 6中的Java編程安全(無@DeclareRoles)

  • 定製閥門Authentificator(extends AuthenticatorBase
  • 自定義登錄模塊對於JBoss(extends UsernamePasswordLoginModule
  • 擔保端點(JAX-RS)

我的主要問題是,我的終點與註釋只能,如果我不使用它,我無法通過認證。詳細地說,方法AuthenticatorBase.invoke(來自org.apache.catalina.authenticator)調用方法RealmBase.hasResourcePermission,那裏的角色將被檢查。

由於我沒有使用任何預定義角色,所以檢查將失敗。

我的問題:有什麼辦法可以使用這樣的代碼:

@Path("/secure") 
@Stateless 
public class SecuredRestEndpoint { 

    @Resource 
    SessionContext ctx; 

    @GET 
    public Response performLogging() { 

     // Receive user information 
     Principal callerPrincipal = ctx.getCallerPrincipal(); 
     String userId = callerPrincipal.getName(); 

     if (ctx.isCallerInRole("ADMIN")) { 
      // return 200 if ok 
      return Response.status(Status.OK).entity(userId).build(); 
     } 
    ... 
    } 
} 

一些額外的背景:有使用反向代理服務器進行身份驗證只是用戶名被轉發(X-FORWARD-USER的要求)。這就是爲什麼我使用我自己的Authenticator類和自定義登錄模塊(我沒有任何密碼憑據)。但我認爲問題也出現在應用服務器本身的標準身份驗證方法中

回答

2

如果我找到了你的話,有兩個問題。

  1. 您不希望@DeclareRoles出現在您的代碼中。如果你不介意使用web.xml,看看這個:http://docs.oracle.com/cd/E19159-01/819-3669/bncbg/index.html

  2. 你想單獨使用用戶名來訪問你的休息資源,因爲其餘資源沒有安全威脅,但在同時剩下的資源需要知道誰在呼叫。

    1. 有多種方法可以做到這一點。爲了識別用戶,你只需要在你的http請求中提供用戶的標識,JAAS安全性過大。例如,提供URI:/user/bob的用戶ID,或通過URI參數提供:/user?id=bob

    2. 如果安全性是強制性的(如果我沒有弄錯,您使用的是javaee的標準安全組件),則需要處理角色,因爲java6的規範已經寫入了基於角色的認證/授權英寸

+0

感謝您的回答: 1.我不想在代碼中擁有'@ DeclareRoles'的原因是因爲它是靜態的,角色可以隨時間變化(維護)。 2.1我不想在REST代碼中擁有用戶名。想想更深層次的背景,我沒有名字,我不能總是提供他。我可以很容易地從reqeust標題中讀取參數,但那不是我的目標。 2.2我喜歡用'SesseionContext'和用戶角色來實現JEE安全,但爲什麼它必須是靜態的,而不僅僅是程序化的? –

+0

只是爲了「爲什麼它必須是靜態的而不僅僅是程序化的?」角色是靜態的,因爲一般來說,應用程序不應該在沒有重新部署的情況下動態增加其安全功能。儘管您可以決定是否在web.xml或bean中指定角色以簡化代碼維護,但最好不要避免代碼重新部署。 – Xiangyu

+0

這是一個很好的觀點,並在web.xml中改變它並不是什麼大不了的事情(不需要重新部署)。我一定會考慮它。從我的項目來看,如果某些事情發生變化,重要的是不要重新部署,因爲它是客戶的一種自定義代碼,不希望觸及代碼,也不會要求供應商始終這樣做。這就是爲什麼我正在尋找一個簡單的可配置解決方案。但除此之外,我認爲有一種方法可以實現我的方法,並且我會保持獎勵以獲得儘可能多的迴應:D –

6

因爲你的代碼是靜態的(意思是你有一個靜態組可確保資源的),我不明白你從增加訪問粒度增加安全角色休耕的要求。我可以在代碼不是靜態的模塊化環境中看到此需求,在這種情況下,您需要支持稍後部署聲明的其他安全角色。

這麼說,我不得不實施類似的東西,支持安全系統:

  • 增加(聲明)/重新部署不刪除角色;
  • 將用戶關聯到這些角色。

我會在高層次的抽象中描述我做了什麼,希望它會給你一些有用的想法。

首先,實施@EJB,這樣的事情:

@Singleton 
@LocalBean 
public class MySecurityDataManager { 

    public void declareRole(String roleName) { 
     ... 
    } 

    public void removeRole(String roleName) { 
     ... 
    } 

    /** 
    * Here, I do not know what your incoming user data looks like such as: 
    * do they have groups, attributes? In my case I could determine user's groups 
    * and then assign them to roles based on those. You may have some sort of 
    * other attribute or just plain username to security role association. 
    */ 
    public void associate(Object userAttribute, String roleName) { 
     ... 
    } 

    public void disassociate(Object userAttribute, String roleName) { 
     ... 
    } 

    /** 
    * Here basically you inspect whatever persistence method you chose and examine 
    * your existing associations to build a set of assigned security roles for a 
    * user based on the given attribute(s). 
    */ 
    public Set<String> determineSecurityRoles(Object userAttribute) { 
     ... 
    } 
} 

然後你實現自定義javax.security.auth.spi.LoginModule。我建議從頭開始實現它,除非你知道容器提供的抽象實現對你有用,它不適合我。另外,我建議你熟悉下,如果你不是,以便更好地瞭解我前往:


public class MyLoginModule implements LoginModule { 

    private MySecurityDataManager srm; 

    @Override 
    public void initialize(Subject subject, CallbackHandler callbackHandler, 
      Map<String, ?> sharedState, Map<String, ?> options) { 
     // make sure to save subject, callbackHandler, etc. 
     try { 
      InitialContext ctx = new InitialContext(); 
      this.srm = (MySecurityDataManager) ctx.lookup("java:global/${your specific module names go here}/MySecurityDataManager"); 
     } catch (NamingException e) { 
      // error logic 
     } 
    } 

    @Override 
    public boolean login() throws LoginException { 
     // authenticate your user, see links above 
    } 

    @Override 
    public boolean commit() throws LoginException { 
     // here is where user roles get assigned to the subject 
     Object userAttribute = yourLogicMethod(); 
     Set<String> roles = srm.determineSecurityRoles(userAttribute); 
     // implement this, it's easy, just make sure to include proper equals() and hashCode(), or just use the Jboss provided implementation. 
     Group rolesGroup = new SimpleGroup("Roles", roles); 
     // assuming you saved the subject 
     this.subject.getPrincipals().add(rolesGroup); 
    } 

    @Override 
    public boolean abort() throws LoginException { 
     // see links above 
    } 

    @Override 
    public boolean logout() throws LoginException { 
     // see links above 
    } 

} 

爲了允許動態配置(即聲明角色,關聯用戶),構建一個UI臨時表t使用相同的@EJB MySecurityDataManager來刪除登錄模塊用來確定安全角色的安全設置。

現在,您可以按照自己想要的方式進行打包,只需確保MyLoginModule可以查找MySecurityDataManager並將其部署到容器中。我曾經在JBoss上工作,並且提到了JBoss,所以這也適用於你。更健壯的實現將包括LoginModule配置中的查找字符串,然後您可以在運行時從initialize()方法的選項映射中讀取。下面是JBoss的一個示例配置:

<security-domain name="mydomain" cache-type="default"> 
    <authentication> 
     <login-module flag="required" 
         code="my.package.MyLoginModule" 
         module="deployment.${your deployment specific info goes here}"> 
      <module-option name="my.package.MySecurityDataManager" 
          value="java:global/${your deployment specific info goes here}/MySecurityDataManager"/> 
     </login-module> 
    </authentication> 
</security-domain> 

在這一點上,你可以使用這個安全域mydomain管理容器中的任何其他部署的安全性。

這裏有幾個使用場景:

  1. 部署新的.war,並將其分配給mydomain安全域。 .war在其代碼中帶有預定義的安全註釋。您的安全領域最初並沒有他們,所以沒有用戶可以登錄。但是在部署之後,由於安全角色有充分的文檔記錄,您可以打開您編寫的配置界面並聲明這些角色,然後爲其分配用戶。現在,他們可以登錄。
  2. 經過幾個月的部署,您不再希望用戶能夠訪問特定的戰爭參數。從您的mydomain中刪除與.war相關部分的安全角色,任何人都無法使用它。

最好的部分,特別是關於#2的是不重新部署。另外,不要編輯XML以覆蓋使用註釋聲明的默認安全設置(假設您的界面比這更好)。

乾杯! 我很樂意提供更多細節,但現在,這應該至少告訴你是否需要它們。

+0

感謝您的回答,不幸的是我正在度假,因此我無法正確回覆。在兩週內,我會嘗試閱讀你的答案。乾杯 –

+0

享受!我希望我也是。 – rdcrng

+0

謝謝,即使我使用Jetty代替JBoss,它也是一個很好的靈感來源。 – gouessej