java jaas_基于Java JAAS表单的身份验证
java jaas
使用JAAS實現登錄模塊是一個高級主題,而且大多數開發人員也很少有機會參與這種開發。 但是JAAS登錄模塊的基本實現不是那么難實現,這是因為我打算將其發布。 在這里,我正在解釋如何實現tomcat管理的身份驗證模塊。 此實現與容器無關。 我們可以將其與稍有更改的任何容器一起使用。 第一步,我們需要創建一個實現javax.security.auth.spi.LoginModule接口的登錄模塊類。 該接口提供了必須由我們的登錄模塊類實現的5種方法。 這些是initialize(),login(),commit(),abort(),logout() 。 initialize()方法傳遞了四個參數。 “主題”是什么,我們需要進行身份驗證。 主題可以代表單個登錄用戶的相關信息。 它可以表示“用戶名”,“密碼”等身份。此外,它還可以表示分配給用戶的角色。 所有這些身份都應表示為java.security.Principal。 因此,我們應該通過實現java.security.Principal創建單獨的類來區分這些實體。 在本教程中,我為用戶名,密碼和角色創建了單獨的類,分別為JAASUserPrincipal,JAASPasswordPrincipal和JAASRolePrincipal。 主題的getPrincipals()方法返回一組與主題關聯的java.security.Principal。 為了區別這些,為每個標識創建單獨的類很重要。 “ CallbackHandler ”用于與用戶通信。 通過登錄模塊對用戶進行身份驗證時,登錄模塊將調用CallbackHandler實例的handle()方法以獲取用戶名和密碼。 我們還不想擔心CallbackHandler實例。 因為容器設法提供所需的callbakcs。 Tomcat為此提供了JAASCallbackHandler 。 但是,如果我們要顯式調用身份驗證,則需要通過實現javax.security.auth.callback.CallbackHandler來創建自己的回調處理程序類。 我將在本教程的結尾進行解釋。 initialize()方法的下一個重要參數是'options'。 這些選項我們在' jass.config '文件中聲明。 通過初始化登錄模塊,提供了在“ jass.config”文件中聲明的選項的映射。 稍后我將解釋本教程的“ jaas.config”文件。 接下來,我將顯示我們的主體類的完整源代碼。JAASUserPrincipal.java
package com.rainyday.server.login;import java.io.Serializable;import java.security.Principal;/*** @author semika**/public class JAASUserPrincipal implements Principal, Serializable {private String name;/*** @param name*/public JAASUserPrincipal(String name) {if (name == null) {throw new NullPointerException("NULL user name");}this.name = name;}@Overridepublic String getName() {return name;}@Overridepublic String toString() {return "UserPrincipal [name=" + name + "]";}@Overridepublic int hashCode() {final int prime = 31;int result = 1;result = prime * result + ((name == null) ? 0 : name.hashCode());return result;}@Overridepublic boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;JAASUserPrincipal other = (JAASUserPrincipal) obj;if (name == null) {if (other.name != null)return false;} else if (!name.equals(other.name))return false;return true;} }JAASRolePrincipal.java
package com.rainyday.server.login;import java.io.Serializable;import java.security.Principal;/*** @author semika**/public class JAASRolePrincipal implements Principal, Serializable {private String name;/*** @param name*/public JAASRolePrincipal(String name) {if (name == null) {throw new NullPointerException("NULL role name");}this.name = name;}@Overridepublic String getName() {return name;}@Overridepublic String toString() {return "JASSRolePrincipal [name=" + name + "]";}@Overridepublic int hashCode() {final int prime = 31;int result = 1;result = prime * result + ((name == null) ? 0 : name.hashCode());return result;}@Overridepublic boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;JAASRolePrincipal other = (JAASRolePrincipal) obj;if (name == null) {if (other.name != null)return false;} else if (!name.equals(other.name))return false;return true;} }JAASPasswordPrincipal.java
package com.rainyday.server.login;import java.io.Serializable;import java.security.Principal;/*** @author semika**/public class JAASPasswordPrincipal implements Principal, Serializable {private String name;/*** @param name*/public JAASPasswordPrincipal(String name) {if (name == null) {throw new NullPointerException("NULL password.");}this.name = name;}@Overridepublic String getName() {return name;}@Overridepublic int hashCode() {final int prime = 31;int result = 1;result = prime * result + ((name == null) ? 0 : name.hashCode());return result;}@Overridepublic boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;JAASPasswordPrincipal other = (JAASPasswordPrincipal) obj;if (name == null) {if (other.name != null)return false;} else if (!name.equals(other.name))return false;return true;}@Overridepublic String toString() {return "JAASPasswordPrincipal [name=" + name + "]";}} 以上三個類是完全相似的。 但是,我們需要為每個主體創建單獨的類以區分它們。 登錄模塊的login()方法執行身份驗證。 這將驗證用戶輸入的數據庫用戶名和密碼。 現在,您可能有一個問題,用戶輸入的登錄詳細信息如何進入login()方法。 如前所述,回調處理程序將登錄標識帶入login()方法。 從login()方法中,login模塊通過將所需的回調傳遞到其中來調用回調處理程序的handle()方法。 然后handle()方法使用所需的信息填充這些回調,并使它們可用于login()方法。 成功驗證后,登錄模塊將調用commit()方法。 可以使用相關的主體填充主題。 例如,我們可以從數據庫中檢索用戶分配的角色,并將這些角色附加到主題中。 接下來,我將解釋運行此登錄模塊所需的配置。 我們應該創建“ jass.config”文件,并將其放置在“ $ CATALINA_HOME / conf ”下。 本教程的“ jass.config”文件如下。 rainyDay {com.rainyday.server.login.JAASLoginModule requireddbDriver="com.mysql.jdbc.Driver"dbURL="jdbc:mysql://localhost/rainyday"dbUser="root"dbPassword="abc123"userQuery="select username from secu_user where secu_user.username=? and secu_user.password=?"roleQuery="select secu_user_role.rolename from secu_user, secu_user_role?"+ "where secu_user.username=secu_user_role.username and secu_user.username=?"debug=true; };“ jass.config”文件應具有類似的格式。 除了登錄模塊聲明之外,您還可以根據需要聲明選項。 登錄模塊可以使用initialize()方法參數中的“ options”映射來使用這些選項。
另外,我們應該通過添加JAVA_OPTS環境變量的路徑來告訴tomcat,“ jaas.config”文件的位置。 我將其添加到$ CATALINA_HOME / bin下的'catalina.sh'文件中,如下所示。
JAVA_OPTS =” $ JAVA_OPTS -Djava.security.auth.login.config == .. / conf / jaas.config”
接下來,您需要聲明JAASRealm配置。 您可以在$ CATALINA_HOME / conf下的server.xml文件中添加一個新的“ Realm”條目。 在我們的教程中,“領域”條目如下。
<Realm className="org.apache.catalina.realm.JAASRealm"appName="rainyDay"userClassNames="com.rainyday.server.login.JASSUserPrincipal,com.rainyday.server.login.JAASPasswordPrincipal"roleClassNames="com.rainyday.server.login.JASSRolePrincipal"/>有關apache tomcat的領域配置,您可以查看此文檔 。 jaas登錄模塊的完整源代碼。
JAASLoginModule.java
package com.rainyday.server.login;import java.io.IOException; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; 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;import org.apache.log4j.Logger;/*** @author semika**/ public class JAASLoginModule implements LoginModule { private static Logger LOGGER = Logger.getLogger(JAASLoginModule.class); // initial stateprivate Subject subject;private CallbackHandler callbackHandler;private Map sharedState;private Map options;// configurable optionprivate boolean debug = false;// the authentication statusprivate boolean succeeded = false;private boolean commitSucceeded = false;//user credentialsprivate String username = null;private char[] password = null;//user principleprivate JAASUserPrincipal userPrincipal = null;private JAASPasswordPrincipal passwordPrincipal = null;public JAASLoginModule() {super();}@Overridepublic void initialize(Subject subject, CallbackHandler callbackHandler,Map<string, ?=""> sharedState, Map<string, ?=""> options) {this.subject = subject;this.callbackHandler = callbackHandler;this.sharedState = sharedState;this.options = options;debug = "true".equalsIgnoreCase((String)options.get("debug")); }@Overridepublic boolean login() throws LoginException {if (callbackHandler == null){throw new LoginException("Error: no CallbackHandler available " +"to garner authentication information from the user");}Callback[] callbacks = new Callback[2];callbacks[0] = new NameCallback("username");callbacks[1] = new PasswordCallback("password: ", false);try {callbackHandler.handle(callbacks);username = ((NameCallback)callbacks[0]).getName();password = ((PasswordCallback)callbacks[1]).getPassword();if (debug) {LOGGER.debug("Username :" + username);LOGGER.debug("Password : " + password);}if (username == null || password == null) {LOGGER.error("Callback handler does not return login data properly");throw new LoginException("Callback handler does not return login data properly"); }if (isValidUser()) { //validate user.succeeded = true;return true;} } catch (IOException e) { e.printStackTrace();} catch (UnsupportedCallbackException e) {e.printStackTrace();}return false;}@Overridepublic boolean commit() throws LoginException {if (succeeded == false) {return false;} else { userPrincipal = new JAASUserPrincipal(username);if (!subject.getPrincipals().contains(userPrincipal)) {subject.getPrincipals().add(userPrincipal);LOGGER.debug("User principal added:" + userPrincipal);}passwordPrincipal = new JAASPasswordPrincipal(new String(password)); if (!subject.getPrincipals().contains(passwordPrincipal)) {subject.getPrincipals().add(passwordPrincipal);LOGGER.debug("Password principal added: " + passwordPrincipal);}//populate subject with roles.List<string> roles = getRoles();for (String role: roles) {JAASRolePrincipal rolePrincipal = new JAASRolePrincipal(role);if (!subject.getPrincipals().contains(rolePrincipal)) {subject.getPrincipals().add(rolePrincipal); LOGGER.debug("Role principal added: " + rolePrincipal);}}commitSucceeded = true;LOGGER.info("Login subject were successfully populated with principals and roles"); return true;}}@Overridepublic boolean abort() throws LoginException {if (succeeded == false) {return false;} else if (succeeded == true && commitSucceeded == false) {succeeded = false;username = null;if (password != null) {password = null;}userPrincipal = null; } else {logout();}return true;}@Overridepublic boolean logout() throws LoginException {subject.getPrincipals().remove(userPrincipal);succeeded = false;succeeded = commitSucceeded;username = null;if (password != null) {for (int i = 0; i < password.length; i++){password[i] = ' ';password = null;}}userPrincipal = null;return true;}private boolean isValidUser() throws LoginException {String sql = (String)options.get("userQuery");Connection con = null;ResultSet rs = null;PreparedStatement stmt = null;try {con = getConnection();stmt = con.prepareStatement(sql);stmt.setString(1, username);stmt.setString(2, new String(password));rs = stmt.executeQuery();if (rs.next()) { //User exist with the given user name and password.return true;}} catch (Exception e) {LOGGER.error("Error when loading user from the database " + e);e.printStackTrace();} finally {try {rs.close();} catch (SQLException e) {LOGGER.error("Error when closing result set." + e);}try {stmt.close();} catch (SQLException e) {LOGGER.error("Error when closing statement." + e);}try {con.close();} catch (SQLException e) {LOGGER.error("Error when closing connection." + e);}}return false;}/*** Returns list of roles assigned to authenticated user.* @return*/private List<string> getRoles() { Connection con = null;ResultSet rs = null;PreparedStatement stmt = null;List<string> roleList = new ArrayList<string>(); try {con = getConnection();String sql = (String)options.get("roleQuery");stmt = con.prepareStatement(sql);stmt.setString(1, username);rs = stmt.executeQuery();if (rs.next()) { roleList.add(rs.getString("rolename")); }} catch (Exception e) {LOGGER.error("Error when loading user from the database " + e);e.printStackTrace();} finally {try {rs.close();} catch (SQLException e) {LOGGER.error("Error when closing result set." + e);}try {stmt.close();} catch (SQLException e) {LOGGER.error("Error when closing statement." + e);}try {con.close();} catch (SQLException e) {LOGGER.error("Error when closing connection." + e);}}return roleList;}/*** Returns JDBC connection* @return* @throws LoginException*/private Connection getConnection() throws LoginException {String dBUser = (String)options.get("dbUser");String dBPassword = (String)options.get("dbPassword");String dBUrl = (String)options.get("dbURL");String dBDriver = (String)options.get("dbDriver");Connection con = null;try {//loading driverClass.forName (dBDriver).newInstance();con = DriverManager.getConnection (dBUrl, dBUser, dBPassword);} catch (Exception e) {LOGGER.error("Error when creating database connection" + e);e.printStackTrace();} finally {}return con;} } 如果login()或commit()方法執行中出現問題,將調用login模塊的abort()方法。 在這種情況下,如果身份驗證過程失敗,我們不能說身份驗證過程已成功完成,并且可以在abort()方法內完成所需的清理操作。 我們可以利用在登錄模塊的initialize()方法中初始化的“選項”映射來獲取在“ jass.config”文件中聲明的配置信息。 您可以想出一種好的技術來獲取JDBC連接對象。 在本教程中,我并沒有專心于此,而只是想向您展示機制,即如何完成事情。 至此,我們已經完成了基本JAAS身份驗證模塊所需的工作。 接下來,我們應該在web.xml文件中配置安全約束。 <login-config><auth-method>FORM</auth-method><realm-name>rainyDay</realm-name><form-login-config><form-login-page>/login.jsp</form-login-page><form-error-page>/error.jsp</form-error-page></form-login-config> </login-config> <security-role><role-name>*</role-name> </security-role> <security-constraint><web-resource-collection><web-resource-name>Rainy day</web-resource-name><url-pattern>/</url-pattern><http-method>POST</http-method><http-method>GET</http-method></web-resource-collection><auth-constraint><role-name>*</role-name></auth-constraint> </security-constraint>由于上述安全限制,如果在沒有身份驗證的情況下對應用程序受保護區域中的特定資源提出了一些請求,則該請求將被重定向到“登錄”頁面。 接下來,我將向您展示簡單HTML表單,該表單將在表單提交后調用我們的登錄模塊。
<form id="loginForm" name="loginForm" method="post" action="j_security_check">User Name : <input id="username" type="text" name="j_username" class="textbox"></input>Password : <input id="password" type="password" name="j_password" class="textbox"></input><input name="login" type="submit" value="LOGIN" id="submit" class="button blue"></form> 到此,我們完成了。這是JAAS的非常基本的實現。 這種JAAS模塊的優點是,我們只需更改一次配置即可更改為其他登錄模塊實現,而無需對現有代碼進行任何修改。 而且這是與容器無關的。 如果要使用jBoss服務器而不是“ jass.config”來部署它,則可以使用jboss conf文件夾中的“ login-config.xml”文件。 正如我答應過的,這里就是如何顯式調用這種登錄模塊。 在某些情況下,我們需要以實用的方式對特定用戶進行身份驗證,但仍然應該使用已實現的登錄模塊。 在這種情況下,最大的問題是向我們的登錄模塊提供用戶身份(用戶名,密碼等)。 在上述情況下,我們使用了一個“ CallbackHandler”類,該類是apache catalina提供的“ JAASCallbackHander”。 但是,在這里我們不能使用和我們必須實現我們自己的回調處理程序類。JAASCallbackHandler.java
package com.rainyday.server.login;import java.io.IOException;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 org.apache.log4j.Logger;/*** @author semika**/public class JAASCallbackHandler implements CallbackHandler {private static final Logger LOGGER = Logger.getLogger(JAASCallbackHandler.class);private String username = null;private String password = null;/*** @param username* @param password*/public JAASCallbackHandler(String username, String password) {this.username = username;this.password = password;}@Overridepublic void handle(Callback[] callbacks) throws IOException,UnsupportedCallbackException {LOGGER.info("Callback Handler invoked ");for (int i = 0; i < callbacks.length; i++) {if (callbacks[i] instanceof NameCallback) {NameCallback nameCallback = (NameCallback) callbacks[i];nameCallback.setName(username);} else if (callbacks[i] instanceof PasswordCallback) {PasswordCallback passwordCallback = (PasswordCallback) callbacks[i];passwordCallback.setPassword(password.toCharArray());} else {throw new UnsupportedCallbackException(callbacks[i], "The submitted Callback is unsupported");}}} }接下來,我們必須創建一個' LoginContext'實例來顯式調用身份驗證。
LoginContext lc = null;try {lc = new LoginContext("rainyDay", new JAASCallbackHandler(username, password));lc.login();//get the subject.Subject subject = lc.getSubject();//get principalssubject.getPrincipals();LOGGER.info("established new logincontext");} catch (LoginException e) {LOGGER.error("Authentication failed " + e);} 如果我們最終無一例外地執行了以上代碼,則表明身份驗證已成功。 如果遇到異常,則認證失敗。 這就是本教程中的全部內容。 http://docs.oracle.com/javaee/1.4/tutorial/doc/Security5.html是我了解登錄身份驗證的很好的教程。參考:來自我們的JCG合作伙伴 Semika loku kaluge的基于Java表單的身份驗證 ,位于Code Box博客上。
翻譯自: https://www.javacodegeeks.com/2012/06/java-jaas-form-based-authentication.html
java jaas
總結
以上是生活随笔為你收集整理的java jaas_基于Java JAAS表单的身份验证的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机网络---数据链路层HDLC协议
- 下一篇: FILO微型计算机,IBM-PC微机组成