Java 认证和授权服务(JAAS)是一种用于验证用户身份以确定安全等级的 Tomcat Realm ( org.apache.catalina.Realm) 的实现。
Tomcat 7.0, MVC (推荐 Spring MVC)和数据库(推荐 Mysql)
appName
appName 属性的值将被传递给 LoginContext (javax.security.auth.login.LoginContext) 构造函数,以指定实现 LoginModule ( javax.security.auth.spi.LoginModule) 的实体名称。
LoginModule 是一个提供了特定类型的身份验证的可插拔接口。 LoginContext 通过读取配置(javax.security.auth.login.Configuration) 指定登录程序中的登录模块(S)。
一个登录配置包括以下信息 :
Name { ModuleClass Flag ModuleOptions; ModuleClass Flag ModuleOptions; ModuleClass Flag ModuleOptions; };
一个登录配置中可能包括不只一个的登录模块。
ModuleClass 是登录模块的完整相称类名。Flag 值 ( Required, Requisite, Sufficient, Optional ) 则控制身份验证的行为。
ModuleOptions则直接将值传递给底层登录模块,它的格式是一个用空格分割的列表。
将下列 Tomcat JAAS Realm 配置添加到 Tomcat server.xml 文件中:
<realm classname="org.apache.catalina.realm.JAASRealm" appname="jasslogin" userclassnames="com.test.secure.TestUserPrincipal" roleclassnames="com.test.secure.TestRolePrincipal"> </realm>
在 tomcat/conf 文件夹中创建 jass.config 文件:
jasslogin{ com.test.secure.TestLoginModule required; };
在 tomcat/bin 文件夹中创建 setenv.bat 文件,并添加下列配置:
set JAVA_OPTS=-Djava.security.auth.login.config==C:/tomcat/conf/jaas.config
当 logincontext 读取配置时,登录模块将初始化,包括 Subject ( javax.security.auth.Subject),回叫处理( javax.security.auth.callback.CallBackHandler),共享登录模块以及 LoginModule-specific 选项。
boolean login() throws LoginException;
第一个被 LoginContext 调用来实际处理身份验证的方法是 Login 方法,它将返回 true 或 false 。如果验证成功, commit 方法将被调用。
package com.test.secure; import java.io.IOException; import java.security.Principal; 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; public class TestLoginModule implements LoginModule { Logger logger = Logger.getLogger(TestLoginModule.class); public static String USER_QUERY = "select user_name from users where user_name=? and user_pass=?"; public static String ROLE_QUERY = "select role_name from user_roles where user_name=?"; private Subject subject; private CallbackHandler callbackHandler; private Map sharedState; private Map options; // configurable option private boolean debug = false; // the authentication status private boolean succeeded = false; private boolean commitSucceeded = false; // user credentials private String username = null; private char[] password = null; // principals private TestUserPrincipal testUserPrincipal; private TestRolePrincipal testRolePrincipal; private TestPasswordPrincipal testPasswordPrincipal; @Override public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) { this.subject = subject; this.callbackHandler = callbackHandler; this.sharedState = sharedState; this.options = options; } @Override public boolean login() throws LoginException { if (callbackHandler == null) { throw new LoginException("call back handler is null"); } 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 (username == null || password == null) { throw new LoginException( "Callback handler does not return login data properly"); } logger.info(" username" + username); logger.info("password" + password); // authenticate if (isValidUser()) { succeeded = true; return true; } } catch (IOException e) { e.printStackTrace(); } catch (UnsupportedCallbackException e) { e.printStackTrace(); } return false; } @Override public boolean commit() throws LoginException { logger.info("committing..."); if (succeeded == false) { return false; } else { testUserPrincipal = new TestUserPrincipal(username); if (!subject.getPrincipals().contains(testUserPrincipal)) { subject.getPrincipals().add(testUserPrincipal); } /* testPasswordPrincipal = new TestPasswordPrincipal(new String( password)); if (!subject.getPrincipals().contains(testPasswordPrincipal)) { subject.getPrincipals().add(testPasswordPrincipal); } */ // populate subject with roles. // strings List roles = getRoles(testUserPrincipal); for (String role : roles) { testRolePrincipal = new TestRolePrincipal(role); if (!subject.getPrincipals().contains(testRolePrincipal)) { subject.getPrincipals().add(testRolePrincipal); } } commitSucceeded = true; logger.info("Login subject were successfully populated with principals and roles"); logger.info("--------------principals"); logger.info(subject.getPrincipals()); for(Principal p: subject.getPrincipals()){ if(p instanceof TestRolePrincipal){ logger.info(" ROLE: "+p.getName()); } } return true; } } @Override public boolean abort() throws LoginException { if (succeeded == false) { return false; } else if (succeeded == true && commitSucceeded == false) { succeeded = false; username = null; if (password != null) { password = null; } testUserPrincipal = null; } else { logout(); } return true; } @Override public boolean logout() throws LoginException { subject.getPrincipals().remove(testUserPrincipal); succeeded = false; succeeded = commitSucceeded; username = null; if (password != null) { for (int i = 0; i < password.length; i++) { password[i] = ' '; password = null; } } testUserPrincipal = null; return true; } private boolean isValidUser() throws LoginException { Connection connection = null; ResultSet rs = null; PreparedStatement stmt = null; try { connection = getConnection(); stmt = connection.prepareStatement(USER_QUERY); 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 { connection.close(); } catch (SQLException e) { logger.error("Error when closing connection." + e); } } return false; } private List getRoles(TestUserPrincipal user) { Connection connection = null; ResultSet rs = null; PreparedStatement stmt = null; List roleList = new ArrayList(); try { connection = getConnection(); stmt = connection.prepareStatement(ROLE_QUERY); stmt.setString(1, username); rs = stmt.executeQuery(); while (rs.next()) { roleList.add(rs.getString("role_name")); user.addRole(new TestRolePrincipal((rs.getString("role_name")))); } } 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 { connection.close(); } catch (SQLException e) { logger.error("Error when closing connection." + e); } } return roleList; } private Connection getConnection() { try { Class.forName("com.mysql.jdbc.Driver"); return DriverManager.getConnection( "jdbc:mysql://localhost:3306/test", "root", "password"); } catch (Exception e) { e.printStackTrace(); } return null; } }
package com.test.secure; import java.security.Principal; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.apache.catalina.Group; import org.apache.catalina.Role; import org.apache.catalina.User; import org.apache.catalina.UserDatabase; public class TestUserPrincipal implements User { private String username; private Set roles = new HashSet(); public TestUserPrincipal(String u){ this.username=u; } @Override public String getName() { // TODO Auto-generated method stub return username; } @Override public void addGroup(Group arg0) { // TODO Auto-generated method stub } @Override public void addRole(Role role) { roles.add(role); } @Override public String getFullName() { // TODO Auto-generated method stub return username; } @Override public Iterator getGroups() { return null; } @Override public String getPassword() { // TODO Auto-generated method stub return null; } @Override public Iterator getRoles() { return roles.iterator(); } @Override public UserDatabase getUserDatabase() { // TODO Auto-generated method stub return null; } @Override public String getUsername() { // TODO Auto-generated method stub return username; } @Override public boolean isInGroup(Group arg0) { // TODO Auto-generated method stub return false; } @Override public boolean isInRole(Role role) { if(1==1){ return true; } if(!roles.isEmpty()){ Iterator it =roles.iterator(); while(it.hasNext()){ Role rol =(Role)it.next(); if(rol.getName()!=null && rol.getName().equals(role.getName())){ return true; } } } return false; } @Override public void removeGroup(Group arg0) { // TODO Auto-generated method stub } @Override public void removeGroups() { // TODO Auto-generated method stub } @Override public void removeRole(Role arg0) { // TODO Auto-generated method stub } @Override public void removeRoles() { roles.clear(); } @Override public void setFullName(String arg0) { setUsername(username); } @Override public void setPassword(String arg0) { // TODO Auto-generated method stub } @Override public void setUsername(String arg0) { this.username=arg0; } } package com.test.secure; import java.io.Serializable; import java.security.Principal; import org.apache.catalina.Role; import org.apache.catalina.UserDatabase; public class TestRolePrincipal implements Role, Serializable { private String roleName; public TestRolePrincipal(String name){ this.roleName=name; } @Override public String getName() { // TODO Auto-generated method stub return getRolename(); } @Override public String getDescription() { // TODO Auto-generated method stub return " some role"; } @Override public String getRolename() { // TODO Auto-generated method stub return roleName; } @Override public UserDatabase getUserDatabase() { // TODO Auto-generated method stub return null; } @Override public void setDescription(String arg0) { } @Override public void setRolename(String arg0) { roleName =arg0; }
你需要将 tomcat/lib 中的三个类都封装到 jar 中,一边在启动时加载。
在这个例子中我们使用的是 Spring MVC,但作为测试,你可使用任何其他的请求处理器,或者只是一个 Servlet:
package com.test.secure; import java.security.Principal; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.catalina.Session; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.AbstractController; public class SecureController extends AbstractController { @Override protected ModelAndView handleRequestInternal(HttpServletRequest req, HttpServletResponse res) throws Exception { ModelAndView m = new ModelAndView("SecureView"); return m; } }
在 web.xml 中增加一个安全约束 ( org.apache.catalina.deploy. SecurityConstraint) 和角色授权的资源访问:
< security-constraint > < web-resource-collection > < web-resource-name>interdit< /web-resource-name > < url-pattern >/go/*< /url-pattern > < /web-resource-collection > < auth-constraint > < description>tomcat< /description > < role-name>tomcat< /role-name > < /auth-constraint> < /security-constraint>
然后添加登录和登录错误页
FORMjasslogin/join.do/joinerror.do
CREATE TABLE `users` ( `user_name` varchar(15) NOT NULL, `user_pass` varchar(15) NOT NULL, PRIMARY KEY (`user_name`); INSERT INTO `users` VALUES ('admin','root'),('role1','root'),('tomcat','tomcat'); CREATE TABLE `user_roles` ( `user_name` varchar(15) NOT NULL, `role_name` varchar(15) NOT NULL, PRIMARY KEY (`user_name`,`role_name`); INSERT INTO `user_roles` VALUES ('tomcat','manager-gui'),('tomcat','manager-jmx'),('tomcat','manager-script'),('tomcat','manager-status'),('tomcat','tomcat');
<form action="<%= response.encodeURL(" j_security_check")="" %="">" method="post"> <fieldset> <legend>Login </legend> <p><label for="name">Username</label> <input name="j_username" type="text"></p> <p><label for="e-mail">Password</label> <input name="j_password" type="password"><br></p> <p class="submit"><input value="Submit" type="submit"></p> </fieldset> </form>