Java 认证和授权服务(JAAS)是一种用于验证用户身份以确定安全等级的 Tomcat Realm ( org.apache.catalina.Realm) 的实现。
需求
Tomcat 7.0, MVC (推荐 Spring MVC)和数据库(推荐 Mysql)
1. 配置
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
2. 登录模块
当 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 中,一边在启动时加载。
3.登录处理器
在这个例子中我们使用的是 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;
}
}
4. 安全约束
在 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
5. 用户密码和角色
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');
6.登录表单
<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>