среда, 7 апреля 2010 г.
Идеология
Идеология в наше время это не система идей, а скорее всего система предубеждений и надуманных запретов.
среда, 17 марта 2010 г.
End to end identity propagation with jboss 4, hibernate and oracle. Part 3
Usual way to get the database connection - use data source. I saw a toons of applications, that use the DataSource#getConnection() method to get the data base connection via configured in application server data source. And usual situation, data source configured with priveledged user (root for mysql, schema owner in oracle case, sa for mssql, etc.) name and his password. LOL data source tell you - here the access key, and there the place where you can get everything and even more. To prevent unauthorized access need to use DataSource#getConnection(String userName, String userPassord) instead of DataSource#getConnection(). At this point need to solve following issues:
In web application configuration jboss-web.xml you have to configure security domain
And last question is created connection for user willl be pooled ? Simple answer - yes it is. If you not sure - dig the jboss source code :)
Hmmm... 3 articles, guess nedd to provide overview how it works from end to end.
- how to configure hibernate to get the JDBC connection with user name and password ?
- how to configure jboss, application and data source ?
- passing the password ?
- is created connection will be in connection pool ?
Here the answers:
If you are look to hibernate configuration documentation you can find an amazing configuration parameter - hibernate.connection.provider_class - That has classname of a custom org.hibernate.connection.ConnectionProvider which provides JDBC connections to Hibernate WOW ! This in definitely that need to use !
The source code see below.
/**
* User: iazarny
* Date: 18.08.2008
* Time: 17:44:21
*
* This custom connection provider will work via JBoss datasource with
* application-managment-security configuration
* @see@ hibernate.connection.provider_class configation parameter for Hibernate JPA provider
*/
public class OracleConnectionProvider implements ConnectionProvider {
private static Logger logger = Logger.getLogger(OracleConnectionProvider.class);
private static InitialContext ctx;
private static DataSource ds;
static {
try {
ctx = new InitialContext();
ds = (DataSource) ctx.lookup("java:/CrTimeDS");
} catch (NamingException e) {
logger.fatal("Error ",e);
}
}
private Properties properties;
public void configure(Properties properties) throws HibernateException {
this.properties = properties;
}
public Connection getConnection() throws SQLException {
String userName = null;
String password = null;
if(SecurityAssociation.getPrincipal()!=null) {
userName = SecurityAssociation.getPrincipal().getName();
password = SecurityAssociation.getCredential().toString();
logger.info("SecurityAssociation.getPrincipal = " + userName);
logger.info("SecurityAssociation.getCredential() = " + password!=null );
}
if (password==null) {
userName = properties.getProperty(Environment.USER);
password = properties.getProperty(Environment.PASS);
logger.info("Environment.USER = " + userName);
logger.info("Environment.PASS = " + password!=null );
}
return ds.getConnection(userName,password);
}
public void closeConnection(Connection connection) throws SQLException {
logger.info("OracleConnectionProvider connection return to pool " + connection );
connection.close();
}
public void close() throws HibernateException {
logger.info("OracleConnectionProvider detach from DataSouce");
ds = null;
}
public boolean supportsAggressiveRelease() {
return false;
}
}
Guess you remember from End to end identity propagation with jboss 4, hibernate and oracle. Part 2 how to configure tamcat/jboss-web valves to get the current user password from SecurityAssociation.
As you can see from connection provider example need to configure CrTimeDS datasource.
<?xml version="1.0" encoding="UTF-8"?>
<datasources>
<local-tx-datasource>
<jndi-name>CrTimeDS</jndi-name>
<connection-url>jdbc:oracle:thin:@127.0.0.1:1521:az3</connection-url>
<driver-class>oracle.jdbc.driver.OracleDriver</driver-class>
<!-- username and password not necessary, because of application-managed-security -->
<user-name></user-name>
<password></password>
<exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.OracleExceptionSorter
</exception-sorter-class-name>
<min-pool-size>10</min-pool-size>
<max-pool-size>200</max-pool-size>
<blocking-timeout-millis>1000</blocking-timeout-millis>
<idle-timeout-minutes>1</idle-timeout-minutes>
<application-managed-security/>
<metadata>
<type-mapping>Oracle9i</type-mapping>
</metadata>
</local-tx-datasource>
</datasources>
In the persistence.xml connection provider wired with hibernate. In case if hibernate will try to check data base schema against domain model on application start and you find a lot of stack traces you need to add copule of lines.
<property name="hibernate.connection.username" value="readSchemaOnlyUser"/>
<property name="hibernate.connection.password" value="readSchemaOnlyPassword"/>
The readSchemaOnlyUser user in oracle, that can read schema meta data, but not actuall data. It easy with oracle
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">
<persistence-unit name="crtimedbUnit" transaction-type="JTA">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>java:/CrTimeDS</jta-data-source>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.Oracle9Dialect"/>
<property name="hibernate.connection.charset" value="UTF-8"/>
<property name="hibernate.cache.use_second_level_cache" value="false"/>
<property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
<property name="hibernate.archive.autodetection" value="class"/>
<property name="hibernate.default_schema" value="CRTIME"/>
<property name="hibernate.connection.provider_class" value="com.crtime.connection.provider.OracleConnectionProvider"/>
</properties>
</persistence-unit>
</persistence>
In web application configuration jboss-web.xml you have to configure security domain
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss-web PUBLIC "-//JBoss//DTD Web Application 2.3V2//EN"
"http://www.jboss.org/j2ee/dtd/jboss-web_3_2.dtd">
<jboss-web>
<security-domain>java:/jaas/crtime-webapp</security-domain>
</jboss-web>
And last question is created connection for user willl be pooled ? Simple answer - yes it is. If you not sure - dig the jboss source code :)
Hmmm... 3 articles, guess nedd to provide overview how it works from end to end.
When not authorized user hit the page he will be promted by login to input his name and password. Provided login name and password will be checked via CrTimeAuthDS datasource by standard java login module (see part 1) in case if result is successful, user become authorized. Database connection will be obtained configured CrTimeDS and hibernate connection provider by using DataSource#getConnection(user, password) method. So user not impersonalized at data base level as well as web and middleware levels.
Ярлыки:
hibernate,
identity,
JAAS,
jboss,
legacy,
login module,
oracle,
propagation
пятница, 5 марта 2010 г.
Second level cache in hibernate
Do you use hibernate ? I guess - yes, if you are here.
Do you use second level cache and query cache with hibernate? Are you happy with performance? I dont know... probably. Actually I am not happy with hibernate performance with query cache and 2nd level cache. Lets look inside ;)
First of all you have to understand, that 2nd level cache used when you perform lookup entity by id. Sound good, but by default entity will be cache without collections. The first solution - enable cache for collection in configurations, it will works for bags, lists, collections, sets, maps.
Second - query cache. It cache only the ids and must be used in conjunction second level cache.
Everything not bad at surface, but when you create hibernate and ehcache configuration and run you solution you will be surprised. Hibernate still hit you db :( Root cause - collection cache will not works for map, that has a map-key, and you db will be hitted a lot of times per single entity.
How to solve this ? Throw away the hibernate cache and use cache aspect in your application, it save a lot of you times, IO operations and CPU. It more predictable way that hibernate cache and allow to cache that you really need :)
четверг, 4 марта 2010 г.
пятница, 26 февраля 2010 г.
End to end identity propagation with jboss 4, hibernate and oracle. Part 2
Complete web application to test oracle login module.
First of all we have to understand where username/password stored after successful authentication to reuse it for our future needs.
By default jboss and tomcat not allow you to get password via SecurityAssociation class, so need additional configuration in context.xml.
Tomcat (and derived jboss web server) has a very useful valve elements for configuration
and additional request/response processing
see http://tomcat.apache.org/tomcat-5.5-doc/config/valve.html for more details.
By default jboss and tomcat not allow you to get password via SecurityAssociation class, so need additional configuration in context.xml.
Tomcat (and derived jboss web server) has a very useful valve elements for configuration
and additional request/response processing
see http://tomcat.apache.org/tomcat-5.5-doc/config/valve.html for more details.
Following changes in context.xml allow to get user password via SecurityAssociationValve class in jboss and tomcat
<Context cookies="true" crossContext="true">
<Valve className="org.jboss.web.tomcat.security.ExtendedFormAuthenticator"
includePassword="true" />
</Context>
As far as you remember from part 1 im try to minimize administrative overhead, but unfortunately i have to duplicate roles in web and db.
In any case lets create a simple web app with 3 pages and login form. Each role has access to one page only. See security-constraint nodes for page names and roles. web.xml shall looks like this:
In any case lets create a simple web app with 3 pages and login form. Each role has access to one page only. See security-constraint nodes for page names and roles. web.xml shall looks like this:
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<servlet>
<servlet-name>logon</servlet-name>
<servlet-class>com.crtime.web.Logon</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>logon</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<security-role>
<role-name>CRTIME_ADMIN</role-name>
</security-role>
<security-role>
<role-name>CRTIME_MANAGER</role-name>
</security-role>
<security-role>
<role-name>anonymous</role-name>
</security-role>
<security-role>
<role-name>CRTIME_SOMEOTHERROLE</role-name>
</security-role>
<security-constraint>
<display-name>CRTIME_ADMIN</display-name>
<web-resource-collection>
<web-resource-name>Protected Area</web-resource-name>
<url-pattern>/crtime_admin.jsp</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>CRTIME_ADMIN</role-name>
</auth-constraint>
<user-data-constraint>
<description>SSL required</description>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<security-constraint>
<display-name>CRTIME_MANAGER</display-name>
<web-resource-collection>
<web-resource-name>Protected Area</web-resource-name>
<url-pattern>/crtime_manager.jsp</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>CRTIME_MANAGER</role-name>
</auth-constraint>
<user-data-constraint>
<description>SSL required</description>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<security-constraint>
<display-name>CRTIME_SOMEOTHERROLE</display-name>
<web-resource-collection>
<web-resource-name>Protected Area</web-resource-name>
<url-pattern>/crtime_someotherrole.jsp</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>CRTIME_SOMEOTHERROLE</role-name>
</auth-constraint>
<user-data-constraint>
<description>SSL required</description>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<login-config>
<auth-method>FORM</auth-method>
<realm-name>CrTime</realm-name>
<form-login-config>
<form-login-page>/login.jsp</form-login-page>
<form-error-page>/loginError.jsp</form-error-page>
</form-login-config>
</login-config>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<error-page>
<error-code>403</error-code>
<location>/login.jsp</location>
</error-page>
</web-app>
Login form:
<form name="logonForm" action="j_security_check" method=post>
<input type="text" name="j_username" maxlength=20>
<input type="password" name="j_password" maxlength=20>
<input type="submit" value="Login">
</form>
Login servlet
In case of successful authentication servlet will redirect to referred page.
package com.crtime.web;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.ServletException;
import java.io.IOException;
import org.jboss.web.tomcat.security.login.WebAuthentication;
import org.jboss.web.tomcat.security.SecurityAssociationValve;
import org.apache.catalina.connector.Request;
import org.apache.catalina.Session;
import org.apache.catalina.authenticator.Constants;
/**
* User: iazarny
* Date: 10.06.2008
* Time: 11:21:38
*
* see following link for more details
* http://roneiv.wordpress.com/2008/03/
* http://forum.java.sun.com/thread.jspa?threadID=5293266&tstart=0
* http://www.javaworld.com/javaforums/printthread.php?Board=JavaSecurity&main=2500&type=post
*
*
*/
public class Logon extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
try {
WebAuthentication webAuthentication = new WebAuthentication();
boolean stat = webAuthentication.login(request.getParameter("j_username"), request.getParameter("j_password"));
if (stat) {
Request activeRequest = (Request) SecurityAssociationValve.activeRequest.get();
Session session = activeRequest.getSessionInternal(false);
String userNameFromTomCatsession = (String)session.getNote(Constants.SESS_USERNAME_NOTE);
String userPasswordFromTomCatsession = (String)session.getNote(Constants.SESS_PASSWORD_NOTE);
System.out.println("\nuserNameFromTomCatsession =" + userNameFromTomCatsession);
System.out.println("\nuserPasswordFromTomCatsession =" + userPasswordFromTomCatsession);
String referer = request.getHeader("Referer");
System.out.println("\nreferer = " + referer);
response.sendRedirect(referer);
} else {
response.sendRedirect(request.getContextPath() + "/loginError.jsp");
}
} catch (Exception e) {
e.printStackTrace();
response.sendRedirect("loginError.jsp");
}
}
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
To be continued ....
Ярлыки:
hibernate,
identity propagation,
JAAS,
jboss,
login module,
oracle,
security
четверг, 25 февраля 2010 г.
End to end identity propagation with jboss 4, hibernate and oracle. Part 1
Several times i was asked by customers for create high security solutions with following requirements:
- End to end identity propagation across all application levels - web/middle tier and database.
- No password must be stored in jboss data source configuration.
- Secure solution must work on public enviroment.
- etc.
- It means, that Egor Lupan, for example, with account elupan can not have anonymous/impersonalized resourses or connections.
- Also it means, that elupan must have accounts on web app/middle tier/db.
- It means, that administrative overhead for manage accounts not allowed.
To minimaze administrative overhead oracle will be used as authorization, authentication and role provider for JAAS login module without any additional user/roles tables, just reuse the oracle all_users dictionary.
OracleDatabaseServerLoginModule source code:
For advanced programmers thats enough to catch the main idea. Only just small hint - we will use two datasources one for login module, second for application. In next part - Jboss/Tomcat context configuration, second datasource configuration, hibernate triks.
OracleDatabaseServerLoginModule source code:
package org.jboss.security.auth.spi;
import org.jboss.tm.TransactionDemarcationSupport;
import org.jboss.security.SimpleGroup;
import javax.security.auth.login.LoginException;
import javax.security.auth.login.FailedLoginException;
import javax.naming.NamingException;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import javax.transaction.Transaction;
import java.sql.SQLException;
import java.sql.ResultSet;
import java.sql.PreparedStatement;
import java.sql.Connection;
import java.security.acl.Group;
import java.security.Principal;
import java.util.HashMap;
import java.security.Principal;
import java.security.acl.Group;
import javax.resource.spi.security.PasswordCredential;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException;
import org.jboss.security.SimplePrincipal;
/**
* User: iazarny
* Date: 04.06.2008
* Time: 17:15:08
*/
public class OracleDatabaseServerLoginModule extends DatabaseServerLoginModule {
/**
* Oracle already validate the user
*
* @param inputPassword
* @param expectedPassword
* @return true
*/
protected boolean validatePassword(String inputPassword, String expectedPassword) {
return expectedPassword != null;
}
/**
* Get the expected password for the current username available via
* the getUsername() method. This is called from within the login()
* method after the CallbackHandler has returned the username and
* candidate password.
*
* @return the valid password String
*/
protected String getUsersPassword() throws LoginException {
String[] info = getUsernameAndPassword();
String usernameInput = info[0];
String passwordInput = info[1];
String username = getUsername();
String password = null;
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
Transaction tx = null;
if (suspendResume) {
tx = TransactionDemarcationSupport.suspendAnyTransaction();
}
try {
InitialContext ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup(dsJndiName);
// This is main point if connection obtained succesfuly - than pwd is valid
conn = ds.getConnection(username, passwordInput);
// Get the password
// log.trace("Excuting query: "+principalsQuery+", with username: "+username);
ps = conn.prepareStatement(principalsQuery);
ps.setString(1, username.toUpperCase());
rs = ps.executeQuery();
if (rs.next() == false) {
throw new FailedLoginException("No matching username found in Principals");
}
password = rs.getString(1);
password = convertRawPassword(password);
}
catch (NamingException ex) {
LoginException le = new LoginException("Error looking up DataSource from: " + dsJndiName);
le.initCause(ex);
throw le;
}
catch (SQLException ex) {
LoginException le = new LoginException("Query failed");
le.initCause(ex);
throw le;
}
finally {
if (rs != null) {
try {
rs.close();
}
catch (SQLException e) {
}
}
if (ps != null) {
try {
ps.close();
}
catch (SQLException e) {
}
}
if (conn != null) {
try {
conn.close();
}
catch (SQLException ex) {
}
}
if (suspendResume) {
TransactionDemarcationSupport.resumeAnyTransaction(tx);
}
}
return password;
}
/**
* Execute the rolesQuery against the dsJndiName to obtain the roles for
* the authenticated user.
*
* @return Group[] containing the sets of roles
*/
protected Group[] getRoleSets() throws LoginException {
String username = getUsername();
Group[] roleSets = getRoleSets(username, dsJndiName, rolesQuery, this,
suspendResume);
return roleSets;
}
/**
* Execute the rolesQuery against the dsJndiName to obtain the roles for
* the authenticated user.
*
* @return Group[] containing the sets of roles
*/
private Group[] getRoleSets(String username, String dsJndiName,
String rolesQuery, AbstractServerLoginModule aslm, boolean suspendResume)
throws LoginException {
Connection conn = null;
HashMap setsMap = new HashMap();
PreparedStatement ps = null;
ResultSet rs = null;
Transaction tx = null;
if (suspendResume) {
tx = TransactionDemarcationSupport.suspendAnyTransaction();
}
try {
String[] info = getUsernameAndPassword();
String usernameInput = info[0];
String passwordInput = info[1];
InitialContext ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup(dsJndiName);
conn = ds.getConnection(usernameInput,passwordInput);
// Get the user role names
ps = conn.prepareStatement(rolesQuery);
try {
ps.setString(1, username);
}
catch (ArrayIndexOutOfBoundsException ignore) {
// The query may not have any parameters so just try it
}
rs = ps.executeQuery();
if (rs.next() == false) {
if (aslm.getUnauthenticatedIdentity() == null)
throw new FailedLoginException("No matching username found in Roles");
/* We are running with an unauthenticatedIdentity so create an
empty Roles set and return.
*/
Group[] roleSets = {new SimpleGroup("Roles")};
return roleSets;
}
do {
String name = rs.getString(1);
String groupName = rs.getString(2);
if (groupName == null || groupName.length() == 0)
groupName = "Roles";
Group group = (Group) setsMap.get(groupName);
if (group == null) {
group = new SimpleGroup(groupName);
setsMap.put(groupName, group);
}
try {
Principal p = aslm.createIdentity(name);
group.addMember(p);
}
catch (Exception e) {
//log.debug("Failed to create principal: " + name, e);
}
} while (rs.next());
}
catch (NamingException ex) {
LoginException le = new LoginException("Error looking up DataSource from: " + dsJndiName);
le.initCause(ex);
throw le;
}
catch (SQLException ex) {
LoginException le = new LoginException("Query failed");
le.initCause(ex);
throw le;
}
finally {
if (rs != null) {
try {
rs.close();
}
catch (SQLException e) {
}
}
if (ps != null) {
try {
ps.close();
}
catch (SQLException e) {
}
}
if (conn != null) {
try {
conn.close();
}
catch (Exception ex) {
}
}
if (suspendResume) {
TransactionDemarcationSupport.resumeAnyTransaction(tx);
//if (trace)
// log.trace("resumeAnyTransaction");
}
}
Group[] roleSets = new Group[setsMap.size()];
setsMap.values().toArray(roleSets);
return roleSets;
}
}
How it works ?
Login module get the provided login user and password and try to create the real connection to oracle via data source, that configured as application managed security, for this kind of data sources not need to provide login and password in data source configuration. In getUsersPassword() method need to verify provided username/password in oracle via getting connection. In case if connection succesful login module get configured roles for username.
Data source configuration for login module will be following:
<?xml version="1.0" encoding="UTF-8"?>
<datasources>
<local-tx-datasource>
<jndi-name>CrTimeAuthDS</jndi-name>
<connection-url>jdbc:oracle:thin:@127.0.0.1:1521:az3</connection-url>
<driver-class>oracle.jdbc.driver.OracleDriver</driver-class>
<!-- username and password not necessary, because of application-managed-security -->
<user-name></user-name>
<password></password>
<exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.OracleExceptionSorter
</exception-sorter-class-name>
<min-pool-size>10</min-pool-size>
<max-pool-size>20</max-pool-size>
<blocking-timeout-millis>1000</blocking-timeout-millis>
<idle-timeout-minutes>1</idle-timeout-minutes>
<application-managed-security/>
<metadata>
<type-mapping>Oracle9i</type-mapping>
</metadata>
</local-tx-datasource>
</datasources>
JBoss login configuration:
<!-- part of login-config.xml -->
<application-policy name="crtime-webapp">
<authentication>
<login-module code="org.jboss.security.auth.spi.OracleDatabaseServerLoginModule" flag="required">
<module-option name="unauthenticatedIdentity">anonymous</module-option>
<module-option name = "dsJndiName">java:/CrTimeAuthDS</module-option>
<module-option name = "principalsQuery">SELECT USER_ID FROM all_users WHERE username=?</module-option>
<module-option name = "rolesQuery">SELECT granted_role, 'Roles' FROM user_role_privs WHERE upper(username)=upper(?)</module-option>
</login-module>
</authentication>
</application-policy>
For advanced programmers thats enough to catch the main idea. Only just small hint - we will use two datasources one for login module, second for application. In next part - Jboss/Tomcat context configuration, second datasource configuration, hibernate triks.
Ярлыки:
hibernate,
identity propagation,
JAAS,
jboss,
login module,
oracle,
security
четверг, 26 ноября 2009 г.
DbUnit. Nil in flat xml file.
Sometimes DbUnit insert 32768 value into database instead of specified 0 in flat xml file. So <TABLE_NAME id="0"/> become INSERT INTO TABLE_NAME(ID) VALUES (32768); I dont have a time to "wash inside out" it. And do not forget about column sensing with DbUnit, it can save a lot of time for you.
Подписаться на:
Сообщения (Atom)