Tomcat 7 JSP 页面:在 Tomcat 中使用 Shibboleth 身份验证会话进行身份验证时避免 500 错误
Tomcat 7 JSP page: Avoid 500 error when authenticating in Tomcat using Shibboleth authentication session
Tomcat 7 异常:
Apache Tomcat/7.0.63 - HTTP Status 500 - An exception occurred
processing JSP page
参考:
我将此代码与我们的 i2b2 应用程序集成在一起,以便我们可以针对我们的 Shibboleth IdP 进行身份验证。
https://github.com/HSSC/i2b2-web-integration/blob/master/doc/INSTALL.md
申请(i2b2):
testshib:
(我们按照此构建了 Shibboleth 服务提供商)
Shibboleth IdP:
问题:
页面在 JSP 页面上失败,状态代码为 500,在设置 "id" 的行上,因为它上面的行有一个 "auth" 变量(类型List<string>
in Java) 为空。请注意,此页面包含 JSP 标签和 Javascript.
login.jsp 页面上的代码片段失败:
function initI2B2()
{
// alert('initI2B2');
<%
shibboleth.ConnectDatabase auth = (shibboleth.ConnectDatabase) session.getAttribute("connectDatabase");
String id = org.apache.commons.lang.StringEscapeUtils.escapeHtml(auth.getUser().identifier); // fails here because the List<string> object is null
String sessionId = org.apache.commons.lang.StringEscapeUtils.escapeHtml(auth.getUser().session);
Calendar cal = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateTime = sdf.format(cal.getTime());
String token = DigestUtils.sha512Hex(dateTime + sessionId);
%>
var passedInUserKey = "<%= org.apache.commons.lang.StringEscapeUtils.escapeHtml(auth.getUser().identifier)%>";
var passedInPassKey = "<%= org.apache.commons.lang.StringEscapeUtils.escapeHtml(auth.getUser().session)%>";
var passedInDomainK = "demo";
var passedInInstitutionid = "1";
var tokenV = "<%= token%>";
导致此问题的工作流程?
如果我们有一个好的会话,代码通常会很好地工作。我们现在的解决方法是让会话在 24 小时内超时。
这是导致错误的工作流程...
点击 i2b2 页面 > 自动重定向到 Shibboleth IdP > 使用 IdP 凭据登录 > 重定向回 i2b2 页面 > 此代码将用户和会话 ID(密码)从 Shibboleth 会话中取出并使用它自动授权进入 i2b2 应用程序。
login.jsp 页面被黑客入侵以从 Shibboleth 会话中获取数据,因此用户无需输入凭据。
问题是当 shibboleth session 超时,或者当 Tomcat session 过期或者浏览器中的缓存被清除时,就会出现这个错误,因为整个页面都需要来自 session 的那两个变量(ID 和密码)。
清除缓存是出现此错误的最快方法。另一种方法是将 Tomcat 默认会话超时从 30 分钟更改为 1 分钟。
<session-config>
<session-timeout>1</session-timeout>
</session-config>
可能给出此问题可能答案的想法:
在ASP.NET中,如果会话不存在,您可以告诉它自动重定向到另一个页面(使用配置)。 Tomcat 是否通过给它一些条件(使用代码)来提供该功能?
我们如何强制终止 Tomcat 会话(不处理此页面),并像我们有一个全新的请求一样重定向到 i2b2?我知道在 ASP.NET 中,如果您收到应用程序错误,可以调用代码。 Tomcat 有什么优惠吗?
如果我们更想hack这个页面,如何简单的用一个条件来避免错误,但仍然有页面处理,然后重定向?
还有其他想法吗?
package edu.tmc.uth.i2b2shibboleth;
import java.security.*;
import java.sql.*;
import java.util.*;
import javax.faces.bean.*;
import javax.faces.context.*;
import javax.servlet.ServletContext;
import javax.servlet.http.*;
/**Manages the retrieval of Shibboleth attributes and the connection to the
* i2b2 hive database.
*
* @author JRussell
*/
@ManagedBean(name = "connectDatabase")
@SessionScoped
public class ConnectDatabase implements HttpSessionBindingListener {
private User user;
private Connection connection;
private List<String> test = new ArrayList<String>();
String propertyPath = null;
private ResourceBundle properties;
public User getUser() {
return user;
}
/*Main function for retrieving Shibboleth information and updating
* database records. This is called from the Facelets page (index.xhtml).
*/
public List<String> getUserInfo() {
try {
connection = connectToDatabase();
setShibbolethAttributes();
updateI2b2UserDatabase();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
return test;
}
/*Returns a connection to the i2b2 hive database.
* Uses the properties in the database.properties file.
*/
public Connection connectToDatabase() {
try {
properties = ResourceBundle.getBundle("edu/tmc/uth/i2b2shibboleth/database");
String url = properties.getString("I2B2_PM.connectionURL");
String userName = properties.getString("I2B2_PM.userName");
String password = properties.getString("I2B2_PM.password");
Class.forName(properties.getString("I2B2_PM.databaseClass"));
connection = DriverManager.getConnection(url, userName, password);
if (connection != null) {
System.out.println("connected to DB");
test.add("Connected to i2b2 hive database.");
}
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return connection;
}
/*Populates the Shibboleth attributes into the User object.
* The request header values are based on the attribute-map.xml configuration
* file on the Shibboleth Service Provider. The attribute identifiers and
* the attributes released are configured on an institutional basis.
*/
public void setShibbolethAttributes() {
HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
String firstName = request.getHeader("Shib-InetOrgPerson-givenName");
String lastName = request.getHeader("Shib-Person-surname");
String email = request.getHeader("Shib-InetOrgPerson-mail");
String identifier = request.getHeader("Shib-iamPerson-subjectUniqueId");
String session = request.getHeader("Shib-Session-ID");
user = new User(firstName, lastName, email, identifier, session);
test.add("Basic User attributes");
test.add(user.toString());
test.add("All Shibboleth headers");
//Outputs all of the headers from the Shibboleth request
Enumeration enumer = request.getHeaderNames();
while(enumer.hasMoreElements()){
String headerName = enumer.nextElement().toString();
if(headerName.startsWith("Shib")){
test.add(headerName+" - "+request.getHeader(headerName));
}
}
}
/*Add a new user to the i2b2 hive database if they don't have an existing record.
* Update the password and enable the user if the i2b2 user already exists.
*/
private void updateI2b2UserDatabase() throws SQLException {
//Try to find current user in database
String stmt = "SELECT user_id FROM pm_user_data WHERE user_id=?";
PreparedStatement pst = connection.prepareStatement(stmt);
pst.setString(1, user.identifier);
System.out.println(stmt+" "+user.identifier);
ResultSet result = pst.executeQuery();
//user record found in database so update password
if (result.next()) {
System.out.println("user record found");
test.add("Subject identifier already in database - " + result.getString("user_id"));
stmt = "UPDATE pm_user_data SET password=?, status_cd=? WHERE user_id=?";
pst = connection.prepareStatement(stmt);
pst.setString(1, encryptMD5(user.session));
pst.setString(2, "A");
pst.setString(3, user.identifier);
pst.executeUpdate();
connection.commit();
}
else { //new user so add record to database
test.add("New user - " + user.identifier + " " + user.first + " " + user.last);
stmt = "INSERT INTO pm_user_data (user_id, full_name, password, email, status_cd) \n"
+ "VALUES (?,?,?,?,?)";
pst = connection.prepareStatement(stmt);
pst.setString(1, user.identifier);
pst.setString(2, user.first + " " + user.last);
pst.setString(3, encryptMD5(user.session));
pst.setString(4, user.email);
pst.setString(5, "A");
pst.executeUpdate();
//assign user roles to i2b2 project
String project = properties.getString("I2B2_PM.projectName");
pst = connection.prepareStatement("INSERT INTO pm_project_user_roles (project_id, user_id, user_role_cd, status_cd) \n"
+ "VALUES (?,?,?,?)");
pst.setString(1, project);
pst.setString(2, user.identifier);
pst.setString(3, "DATA_OBFSC");
pst.setString(4, "A");
pst.executeUpdate();
pst.setString(3, "DATA_AGG");
pst.executeUpdate();
pst.setString(3, "USER");
pst.executeUpdate();
connection.commit();
}
}
/* The i2b2 applications expect passwords to be encrypted.
*This function encrypts passwords with MD5 before inserting them
*into the database.
*/
private String encryptMD5(String text) {
String encrypted = "";
byte[] defaultBytes = text.getBytes();
try {
MessageDigest algorithm = MessageDigest.getInstance("MD5");
algorithm.reset();
algorithm.update(defaultBytes);
byte messageDigest[] = algorithm.digest();
StringBuilder hexString = new StringBuilder();
for (int i = 0; i < messageDigest.length; i++) {
hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
}
encrypted = hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return encrypted;
}
/*Function needed to implement HttpSessionBindingListener.
* Don't need to modify the function itself.
*/
public void valueBound(HttpSessionBindingEvent event) {
//do nothing
}
//This function deactivates a user in the i2b2 hive database when
//the user session times out. Session timeout is set in web.xml.
public void valueUnbound(HttpSessionBindingEvent event) {
connection = connectToDatabase();
try {
//Try to find current user in database
String stmt = "SELECT user_id FROM pm_user_data WHERE user_id=?";
PreparedStatement pst = connection.prepareStatement(stmt);
pst.setString(1, user.identifier);
ResultSet result = pst.executeQuery();
//user record found in database so deactivate them since they have logged off
if (result.next()) {
System.out.println("in results.next");
stmt = "UPDATE pm_user_data SET status_cd=? WHERE user_id=?";
pst = connection.prepareStatement(stmt);
pst.setString(1, "D");
pst.setString(2, user.identifier);
pst.executeUpdate();
connection.commit();
}
connection.close();
ServletContext context = event.getSession().getServletContext();
FacesContext.getCurrentInstance().getExternalContext().redirect("logout.xhtml");
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
Tomcat 实际上有一种方法可以捕获未处理的应用程序异常。它有点等同于 ASP.NET Application_Error 事件。这可能不是一个优雅的解决方案,但它现在有效。每当我们遇到任何 JSP 异常时,我们应该被重定向到这个 error.jsp 页面,然后通过在 error.jsp 页面代码中强制重定向到 Shibboleth IdP 登录页面。
在部署 web.xml 文件中进行设置。这是相对于 i2b2 部署文件夹的。
<error-page>
<exception-type>java.lang.Throwable</exception-type>
<location>/error/error.jsp</location>
</error-page>
然后我把这个放到error.jsp页面:
<%@ page isErrorPage="true" import="java.io.*" contentType="text/plain"%>
Message:
<%=exception.getMessage()%>
StackTrace:
<%
// redirect to Shibboleth IdP login page
String redirectURL = "https://redirecturl/";
response.sendRedirect(redirectURL);
%>
然后重新启动 Linux 服务。必须按此顺序完成,因为存在与 shibd 和 tomcat 服务的依赖关系。
service httpd stop
service tomcat stop
service jboss stop
service shibd stop
service shibd start
service jboss start
service tomcat start
service httpd start
Tomcat 7 异常:
Apache Tomcat/7.0.63 - HTTP Status 500 - An exception occurred processing JSP page
参考:
我将此代码与我们的 i2b2 应用程序集成在一起,以便我们可以针对我们的 Shibboleth IdP 进行身份验证。
https://github.com/HSSC/i2b2-web-integration/blob/master/doc/INSTALL.md
申请(i2b2):
testshib:
(我们按照此构建了 Shibboleth 服务提供商)
Shibboleth IdP:
问题:
页面在 JSP 页面上失败,状态代码为 500,在设置 "id" 的行上,因为它上面的行有一个 "auth" 变量(类型List<string>
in Java) 为空。请注意,此页面包含 JSP 标签和 Javascript.
login.jsp 页面上的代码片段失败:
function initI2B2()
{
// alert('initI2B2');
<%
shibboleth.ConnectDatabase auth = (shibboleth.ConnectDatabase) session.getAttribute("connectDatabase");
String id = org.apache.commons.lang.StringEscapeUtils.escapeHtml(auth.getUser().identifier); // fails here because the List<string> object is null
String sessionId = org.apache.commons.lang.StringEscapeUtils.escapeHtml(auth.getUser().session);
Calendar cal = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateTime = sdf.format(cal.getTime());
String token = DigestUtils.sha512Hex(dateTime + sessionId);
%>
var passedInUserKey = "<%= org.apache.commons.lang.StringEscapeUtils.escapeHtml(auth.getUser().identifier)%>";
var passedInPassKey = "<%= org.apache.commons.lang.StringEscapeUtils.escapeHtml(auth.getUser().session)%>";
var passedInDomainK = "demo";
var passedInInstitutionid = "1";
var tokenV = "<%= token%>";
导致此问题的工作流程?
如果我们有一个好的会话,代码通常会很好地工作。我们现在的解决方法是让会话在 24 小时内超时。
这是导致错误的工作流程...
点击 i2b2 页面 > 自动重定向到 Shibboleth IdP > 使用 IdP 凭据登录 > 重定向回 i2b2 页面 > 此代码将用户和会话 ID(密码)从 Shibboleth 会话中取出并使用它自动授权进入 i2b2 应用程序。
login.jsp 页面被黑客入侵以从 Shibboleth 会话中获取数据,因此用户无需输入凭据。
问题是当 shibboleth session 超时,或者当 Tomcat session 过期或者浏览器中的缓存被清除时,就会出现这个错误,因为整个页面都需要来自 session 的那两个变量(ID 和密码)。
清除缓存是出现此错误的最快方法。另一种方法是将 Tomcat 默认会话超时从 30 分钟更改为 1 分钟。
<session-config>
<session-timeout>1</session-timeout>
</session-config>
可能给出此问题可能答案的想法:
在ASP.NET中,如果会话不存在,您可以告诉它自动重定向到另一个页面(使用配置)。 Tomcat 是否通过给它一些条件(使用代码)来提供该功能?
我们如何强制终止 Tomcat 会话(不处理此页面),并像我们有一个全新的请求一样重定向到 i2b2?我知道在 ASP.NET 中,如果您收到应用程序错误,可以调用代码。 Tomcat 有什么优惠吗?
如果我们更想hack这个页面,如何简单的用一个条件来避免错误,但仍然有页面处理,然后重定向?
还有其他想法吗?
package edu.tmc.uth.i2b2shibboleth;
import java.security.*;
import java.sql.*;
import java.util.*;
import javax.faces.bean.*;
import javax.faces.context.*;
import javax.servlet.ServletContext;
import javax.servlet.http.*;
/**Manages the retrieval of Shibboleth attributes and the connection to the
* i2b2 hive database.
*
* @author JRussell
*/
@ManagedBean(name = "connectDatabase")
@SessionScoped
public class ConnectDatabase implements HttpSessionBindingListener {
private User user;
private Connection connection;
private List<String> test = new ArrayList<String>();
String propertyPath = null;
private ResourceBundle properties;
public User getUser() {
return user;
}
/*Main function for retrieving Shibboleth information and updating
* database records. This is called from the Facelets page (index.xhtml).
*/
public List<String> getUserInfo() {
try {
connection = connectToDatabase();
setShibbolethAttributes();
updateI2b2UserDatabase();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
return test;
}
/*Returns a connection to the i2b2 hive database.
* Uses the properties in the database.properties file.
*/
public Connection connectToDatabase() {
try {
properties = ResourceBundle.getBundle("edu/tmc/uth/i2b2shibboleth/database");
String url = properties.getString("I2B2_PM.connectionURL");
String userName = properties.getString("I2B2_PM.userName");
String password = properties.getString("I2B2_PM.password");
Class.forName(properties.getString("I2B2_PM.databaseClass"));
connection = DriverManager.getConnection(url, userName, password);
if (connection != null) {
System.out.println("connected to DB");
test.add("Connected to i2b2 hive database.");
}
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return connection;
}
/*Populates the Shibboleth attributes into the User object.
* The request header values are based on the attribute-map.xml configuration
* file on the Shibboleth Service Provider. The attribute identifiers and
* the attributes released are configured on an institutional basis.
*/
public void setShibbolethAttributes() {
HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
String firstName = request.getHeader("Shib-InetOrgPerson-givenName");
String lastName = request.getHeader("Shib-Person-surname");
String email = request.getHeader("Shib-InetOrgPerson-mail");
String identifier = request.getHeader("Shib-iamPerson-subjectUniqueId");
String session = request.getHeader("Shib-Session-ID");
user = new User(firstName, lastName, email, identifier, session);
test.add("Basic User attributes");
test.add(user.toString());
test.add("All Shibboleth headers");
//Outputs all of the headers from the Shibboleth request
Enumeration enumer = request.getHeaderNames();
while(enumer.hasMoreElements()){
String headerName = enumer.nextElement().toString();
if(headerName.startsWith("Shib")){
test.add(headerName+" - "+request.getHeader(headerName));
}
}
}
/*Add a new user to the i2b2 hive database if they don't have an existing record.
* Update the password and enable the user if the i2b2 user already exists.
*/
private void updateI2b2UserDatabase() throws SQLException {
//Try to find current user in database
String stmt = "SELECT user_id FROM pm_user_data WHERE user_id=?";
PreparedStatement pst = connection.prepareStatement(stmt);
pst.setString(1, user.identifier);
System.out.println(stmt+" "+user.identifier);
ResultSet result = pst.executeQuery();
//user record found in database so update password
if (result.next()) {
System.out.println("user record found");
test.add("Subject identifier already in database - " + result.getString("user_id"));
stmt = "UPDATE pm_user_data SET password=?, status_cd=? WHERE user_id=?";
pst = connection.prepareStatement(stmt);
pst.setString(1, encryptMD5(user.session));
pst.setString(2, "A");
pst.setString(3, user.identifier);
pst.executeUpdate();
connection.commit();
}
else { //new user so add record to database
test.add("New user - " + user.identifier + " " + user.first + " " + user.last);
stmt = "INSERT INTO pm_user_data (user_id, full_name, password, email, status_cd) \n"
+ "VALUES (?,?,?,?,?)";
pst = connection.prepareStatement(stmt);
pst.setString(1, user.identifier);
pst.setString(2, user.first + " " + user.last);
pst.setString(3, encryptMD5(user.session));
pst.setString(4, user.email);
pst.setString(5, "A");
pst.executeUpdate();
//assign user roles to i2b2 project
String project = properties.getString("I2B2_PM.projectName");
pst = connection.prepareStatement("INSERT INTO pm_project_user_roles (project_id, user_id, user_role_cd, status_cd) \n"
+ "VALUES (?,?,?,?)");
pst.setString(1, project);
pst.setString(2, user.identifier);
pst.setString(3, "DATA_OBFSC");
pst.setString(4, "A");
pst.executeUpdate();
pst.setString(3, "DATA_AGG");
pst.executeUpdate();
pst.setString(3, "USER");
pst.executeUpdate();
connection.commit();
}
}
/* The i2b2 applications expect passwords to be encrypted.
*This function encrypts passwords with MD5 before inserting them
*into the database.
*/
private String encryptMD5(String text) {
String encrypted = "";
byte[] defaultBytes = text.getBytes();
try {
MessageDigest algorithm = MessageDigest.getInstance("MD5");
algorithm.reset();
algorithm.update(defaultBytes);
byte messageDigest[] = algorithm.digest();
StringBuilder hexString = new StringBuilder();
for (int i = 0; i < messageDigest.length; i++) {
hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
}
encrypted = hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return encrypted;
}
/*Function needed to implement HttpSessionBindingListener.
* Don't need to modify the function itself.
*/
public void valueBound(HttpSessionBindingEvent event) {
//do nothing
}
//This function deactivates a user in the i2b2 hive database when
//the user session times out. Session timeout is set in web.xml.
public void valueUnbound(HttpSessionBindingEvent event) {
connection = connectToDatabase();
try {
//Try to find current user in database
String stmt = "SELECT user_id FROM pm_user_data WHERE user_id=?";
PreparedStatement pst = connection.prepareStatement(stmt);
pst.setString(1, user.identifier);
ResultSet result = pst.executeQuery();
//user record found in database so deactivate them since they have logged off
if (result.next()) {
System.out.println("in results.next");
stmt = "UPDATE pm_user_data SET status_cd=? WHERE user_id=?";
pst = connection.prepareStatement(stmt);
pst.setString(1, "D");
pst.setString(2, user.identifier);
pst.executeUpdate();
connection.commit();
}
connection.close();
ServletContext context = event.getSession().getServletContext();
FacesContext.getCurrentInstance().getExternalContext().redirect("logout.xhtml");
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
Tomcat 实际上有一种方法可以捕获未处理的应用程序异常。它有点等同于 ASP.NET Application_Error 事件。这可能不是一个优雅的解决方案,但它现在有效。每当我们遇到任何 JSP 异常时,我们应该被重定向到这个 error.jsp 页面,然后通过在 error.jsp 页面代码中强制重定向到 Shibboleth IdP 登录页面。
在部署 web.xml 文件中进行设置。这是相对于 i2b2 部署文件夹的。
<error-page>
<exception-type>java.lang.Throwable</exception-type>
<location>/error/error.jsp</location>
</error-page>
然后我把这个放到error.jsp页面:
<%@ page isErrorPage="true" import="java.io.*" contentType="text/plain"%>
Message:
<%=exception.getMessage()%>
StackTrace:
<%
// redirect to Shibboleth IdP login page
String redirectURL = "https://redirecturl/";
response.sendRedirect(redirectURL);
%>
然后重新启动 Linux 服务。必须按此顺序完成,因为存在与 shibd 和 tomcat 服务的依赖关系。
service httpd stop
service tomcat stop
service jboss stop
service shibd stop
service shibd start
service jboss start
service tomcat start
service httpd start