(1)認證:對用戶的身份進行驗證。html
.NET基於的RBS(參考1)的認證和受權相關的核心是2個接口System.Security.Principal.IPrincipal和System.Security.Principal.IIdentity。咱們本身實現認證過程,經過Thread.CurrentPrincipal來設置和讀取認證結果。認證成功後設置認證狀態和標識。java
Java內置了的JAAS(參考2),核心是javax.security.auth.Subject類和javax.security.Principal接口。java對認證過程也提供了2個類型,javax.security.auth.login.LoginContext類和javax.security.auth.spi.LoginModule接口。咱們本身實現認證過程,但只要實現了LoginModule就能夠經過LoginContext使用一致的語法。web
(2)受權:對用戶的權限進行驗證,一般使用Role(角色)管理權限。apache
.NET的支持基於角色的受權。.NET的IPrincipal接口的IsInRole方法是受權的核心。有兩種方式使用:1.使用內置的IPrincipal對象(如GenericIdentity),在認證的同時加載用戶的角色roles。2.自定義IPrincipal實現,實現本身的IsInRole邏輯。ASP.NET中實現的的RolePrincipal就經過將邏輯轉發給System.Web.Security.Roles靜態類。Roles依賴System.Web.Security.RoleProvider接口實現角色的查詢,咱們能夠經過web.config的相關節點來配置自定義的RoleProvider。tomcat
JAAS的IPrincipal接口沒有提供IsInRole方法,咱們有2個選擇,要麼經過多個IPrincipal表示角色,要麼自定義實現IPrincipal添加角色支持。Tomcat容器實現的org.apache.catalina.realm.GenericPrincipal就和.NET中的System.Security.Principal.GenericIdentity十分相似的角色實現。服務器
Java的Subject類和IPrincipal接口與.NET的IPrincipal接口和IIdentity的接口不容易對應。爲了便於統一理解.NET和Java的核心類型,咱們能夠從成員的理解,能夠認爲Java的Principal類型至關於.NET中的IPrincipa和IIdentity兩個類型的做用。Subject只是做爲Principal的聚合根。以前提到的Tomcat容器中的GenericPrincipal就即提供了hasRole和getName成員。Tomcat實現的HttpServletRequest對應的成員就是經過GenericPrincipal實現的。cookie
# | 成員 | .NET | Java |
認證類型 | AuthenticationType | System.Security.Principal.IIdentity.AuthenticationType | javax.servlet.http.HttpServletRequest.getAuthType() |
標識名稱 | Name | System.Security.Principal.IIdentity.Name | java.security.Principal.getName() |
角色驗證 | IsInRole | System.Security.Principal.IPrincipal.IsInRole | javax.servlet.http.HttpServletRequest.isUserInRole() |
ASP.NET Forms認證主採用RolePrincipal主體,未認證用戶設置爲GenericIdentity標識,已認證用戶設置爲FormsIdentity。RolePrincipal在驗證角色時將使用Roles靜態類經過RoleProvider進行角色驗證。經過HttpContext.User能夠直接調用主體。數據結構
實現一個最簡單的自定義RoleProvider只須要繼承並實現GetRolesForUser和IsUserInRole兩個方法,一般可使用委託在Application_Start中注入的方式實現通用的RoleProvider。oracle
ASP.NET的forms驗證經過FormsAuthentication發送和註銷用於認證的token,經過配置web.config可讓不一樣的Web服務器以相同的方式對token加密和解密以適應Web服務器負載均衡。不適用cookie承載token時,能夠自定義認證邏輯,好比經過url參數方式承載token配合ssl用於app客戶端驗證等。app
.NET的認證和受權示意圖:
自定義RoleProvider的示例,省略了不須要實現的部分代碼,GetRolesForUserDelegate和IsUserInRoleDelegate在Application_Start中注入便可完全實現RoleProvider和應用服務代碼的解耦:
public class SimpleRoleProvider : RoleProvider
{
public static Func<string, string[]> GetRolesForUserDelegate; public static Func<string, string, bool> IsUserInRoleDelegate; public override string[] GetRolesForUser(string username) { return GetRolesForUserDelegate(username); } public override bool IsUserInRole(string username, string roleName) { return IsUserInRoleDelegate(username, roleName); } }
Forms身份驗證和RoleProvider的分別定義在web.config配置文件中。ASP.NET的配置文件示例(省略了其餘配置):
<?xml version="1.0" encoding="utf-8"?> <configuration> <system.web> <authentication mode="Forms"> <forms loginUrl="~/Home/Login" cookieless="UseCookies" slidingExpiration="true" /> </authentication> <roleManager defaultProvider="SimpleRoleProvider" enabled="true"> <providers> <clear /> <add name="SimpleRoleProvider" type="Onion.Web.SimpleRoleProvider" /> </providers> </roleManager> </system.web> </configuration>
.NET中還有用於配置Pricipal的兩個方法System.AppDomain.SetThreadPrincipal和System.AppDomain.SetPrincipalPolicy以及控制訪問的兩個類型System.Security.Permissions.PrincipalPermission和System.Security.Permissions.PrincipalPermissionAttribute。
HttpServletRequest接口定義了6個驗證和受權相關的方法getAuthType()、login()、logout()、getRemoteUser()、isUserInRole()、getUserPrincipal()。相似ASP.NET,Forms身份驗證也在配置文件中進行配置。但因爲Java熱衷於定義一堆接口將實現推遲到容器級別,LoginContext依賴的具體的LoginModule的配置也必須在容器中進行配置。所以除了web.xml,還須要配置在容器中配置JAAS的配置文件。JAAS的示意圖:
(1)JAAS 內置的登陸模塊使用:NTLoginModule:
配置:
first{
com.sun.security.auth.module.NTLoginModule Required debug=true;
};
代碼
package com.test.jaas1; import java.security.Principal; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; public class App { public static void main(String[] args) { System.setProperty("java.security.auth.login.config", Thread.currentThread().getContextClassLoader().getResource("jaas.config").getPath()); try { LoginContext lc = new LoginContext("first"); lc.login(); System.out.println(lc.getSubject().getPrincipals().size()); for (Principal item : lc.getSubject().getPrincipals()) { System.out.println(String.format("%s principal:%s", item.getClass().getTypeName(), item.getName())); } lc.logout(); System.out.println(lc.getSubject().getPrincipals().size()); } catch (LoginException e) { e.printStackTrace(); } } }
(2)JAAS Tomcat容器下的登陸模塊使用(參考3):
用於配置Forms認證:web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <display-name>Archetype Created Web Application</display-name> <security-constraint> <web-resource-collection> <web-resource-name>Admin</web-resource-name> <url-pattern>/admin/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>admin</role-name> </auth-constraint> </security-constraint> <security-role> <role-name>admin</role-name> </security-role> <login-config> <auth-method>FORM</auth-method> <form-login-config> <form-login-page>/login.html</form-login-page> <form-error-page>/error.html</form-error-page> </form-login-config> </login-config> </web-app>
用於JAAS的LoginModule的配置:/main/java/resources/jaas.config
MyLogin{
MyLoginModule Required debug=true;
};
用於Tomcat的配置:/main/webapp/META-INF/context.xml配置(這個依賴至少還在項目內,不須要修改tomcat):
<?xml version="1.0" encoding="UTF-8"?> <Context> <Realm className="org.apache.catalina.realm.JAASRealm" appName="MyLogin" userClassNames="UserPrincipal" roleClassNames="RolePrincipal" /> </Context>
用於Tomcat的UserClass和RoleClass代碼:
import java.security.Principal; public class UserPrincipal implements Principal { private String _name; public UserPrincipal(String name) { this._name = name; } @Override public String getName() { return this._name; } }
RoleClass:
import java.security.Principal; public class RolePrincipal implements Principal { private String _name; public RolePrincipal(String name) { this._name = name; } @Override public String getName() { return this._name; } }
用於JAAS配置文件初始化的代碼(爲了依賴tomcat的配置,在Filter中設置配置文件):
import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; @WebFilter("/*") public class MyFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { // TODO 自動生成的方法存根 System.setProperty("java.security.auth.login.config", Thread.currentThread().getContextClassLoader().getResource("jaas.config").getPath()); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { chain.doFilter(new MyRequest((HttpServletRequest) request), response); } @Override public void destroy() { // TODO 自動生成的方法存根 } }
用於登陸的login.html:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <form method="post" action="j_security_check"> <table> <tr><td><label>UserName</label></td><td><input type="text" name="j_username"></td></tr> <tr><td><label>Password</label></td><td><input type="password" name="j_password"></td></tr> <tr><td></td><td><input type="submit" value="Login"></td></tr> </table> </form> </body> </html>
MyLoginModule實現:其中的三個name屬性必須是固定值:j_security_check、j_username和j_password。
import java.io.IOException; import java.util.Map; import javax.security.auth.Subject; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; public class MyLoginModule implements LoginModule { private CallbackHandler handler; private Subject subject; @Override public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) { handler = callbackHandler; this.subject = subject; } @Override public boolean login() throws LoginException { Callback[] callbacks = new Callback[2]; callbacks[0] = new NameCallback("login"); callbacks[1] = new PasswordCallback("password", true); try { handler.handle(callbacks); String name = ((NameCallback) callbacks[0]).getName(); String password = String.valueOf(((PasswordCallback) callbacks[1]).getPassword()); if (name != null && name.equals("user123") && password != null && password.equals("pass123")) { return true; } // If credentials are NOT OK we throw a LoginException throw new LoginException("Authentication failed"); } catch (IOException e) { throw new LoginException(e.getMessage()); } catch (UnsupportedCallbackException e) { throw new LoginException(e.getMessage()); } } @Override public boolean commit() throws LoginException { subject.getPrincipals().add(new UserPrincipal("user123")); subject.getPrincipals().add(new RolePrincipal("admin")); return true; } @Override public boolean abort() throws LoginException { // TODO 自動生成的方法存根 return false; } @Override public boolean logout() throws LoginException { subject.getPrincipals().clear(); return true; } }
在.NET的RBS基礎上實現RBAC(參考4)是可行的,但JAAS....。JAAS只要用過的人都對其印象深入。
(1)https://msdn.microsoft.com/en-us/library/52kd59t0(v=vs.90).aspx
(2)http://docs.oracle.com/javase/8/docs/technotes/guides/security/jaas/JAASRefGuide.html
(3)http://www.byteslounge.com/tutorials/jaas-form-based-authentication-in-tomcat-example
(4)http://csrc.nist.gov/groups/SNS/rbac/
JAAS抽象的不切實際,實現又全靠容器,不一樣容器的實現還不一致,IPrincipal又不能直接支持Servlet認證和受權相關的方法。至少應該像.NET同樣提供數據結構級別的角色認證類型,而不是跑偏成如今這樣。容器要麼本身擴展IPrincipal支持角色,要麼經過配置傳入指定類型的IPrincipal子類來區分角色和用戶。只能但願Apache Shiro和Spring等第三方提供的一些實現能有更高的可用性。