Category Archives: jboss / wildfly

Throwing complexity over the wall, easy in-container testing, Shrinkwrap and Arquillian, Andrew Rubinger

JBossWorld Boston, Andrew Lee Rubinger, Throwing complexity over the wall

What is important to employer
– bottom line
– features
– time-to-delivery
– implicit assumption that everything is of high quality
– not much tolerance for refactoring, stress-testing,…

Software can be in three silos
– core concerns : business logic, domain specific, no one else can do this ; time spent here is a good investment
– cross cutting concerns : independent of business logic, targeted, composable, “aspects”/”advice”, orthogonal/perpendicular
– plumbing : get data from A to B, transformation, does (should) not affect state, enables loose coupling, generic, usually implemented by frameworks ; typically not the best use of an application developers time

What should you do
– business logic, core concerns
leave the rest to servers and frameworks
– less you write, less you need to maintain

Components model as solution
– run in a “container”
– container manages the environment, wires up at runtime
– user code executes as deployable components
– components follow a standard form, or model

And what is Jave EE
– collection of specifications which allow writing business logic as components
— recipe to write less
— increase signal to noise ration
– platform defining how containers should handle this

What’s missing?
– a cohesive way to develop and test our applications
– applying the same component model to out tests

Importance of testing – the often ignored
– forces developers to be users
– key in proving API design makes sense
– self-documenting
– gives a sustainable path forward
– slims the release process, no big-bang runs at the end

Excuses, excuses
– testing is not enjoyable, it should be!
– when under pressure to deliver, test code does not deliver bottom-line

Unit testing
– finely grained
– speed is important

Integration testing
– coarsely grained
– component interactions
– agile “user stories”

Traditional approach to integration testing
– start container
– do test using some kind of remoting
– clean up the house

In-JVM embedded integration testing
Pro
– shared memory
– pass-in-reference
– no remotable views
– manage concurrency
Cons
– lack of isolation
– JVM start-up parameters may differ

Hybrid approach
– container in its own process
– test is deployed as archive, bundled alongside test execution framework
– test runs inside container
– TestRunner obtains the results remotely

Test reliance upon the build
– adds extra step to the development/test cycle
– packaging : defines a unit, regulates class loading

Fake the environment for unit tests
– use mock objects, stub out API which may not be available, gets you running in POJO environment

Introducing ShrinkWrap
– simple mechanism to assemble archives like JAR, WAR, EAR in Java
– deploy “virtual” archive in container, integrates with JBoss EmbeddedAS, OpenEJB, Jetty, GlassFish v3

Micro deployments
– Using ShrinkWrap to deploy components in isolation
– test one thing at a time

Arquillian
– provide simple test harness to produce braod set of integration tests for (enterprise) Java applications
– abstract our server lifecycle

How
– use “@RunWith(Arquillian.class)”, define the deployment (annotated with “@Deployment”)
– rest are just your tests and some annotations to wire everything up
– container is not defined in the test class, can be defined separately

getting the logged in user in JBoss

When developing EJB beans, there are ways to inject the current user to assure the logged in user is known. The same goes when developing a web application where the information is available through the HTTPServletRequest.

However when outside these, like when developing a JMX bean or when writing a webservice which requires authentication, there are no standard ways to get this information.

In JBoss however, this can be solved by accessing the SecurityAssociation class.

SecurityAssociation is the JBoss recommended make login and credentials (password) available when doing remote EJB access. Looking though the JBoss source it appears that this is used to store the credentials server side as well. So, you can always get the login details using
SecurityAssociation.getPrincipal().

Also interesting (but completely untested), is the pushRunAsIdentity() method which should allow (server-side only) switching run-as role without the need to call a EJB.

authentication in tapestry5 using chenillekit-access and application server login

Most web applications need authentication and authorization support. For tapestry5, there are a couple of libraries which promise to help in providing this. The two most advanced are probably tapestry-spring-security and chenillekit-access. For the application I am working on, my choice was to use chenillekit. Both of them are rather thin in documentation, but chenillekit is reasonable easy to grasp from the source.

One of the principal extra requirements I have is to be able to link with the application server to assure the web application is also logged in using the current user to allow the JEE beans to work in the right security context.

Plain chenillekit-access to protect your pages

Base configuration for chenillekit-acces is to configure the class used to store the “WebSessionUser” information and service which checks whether the credentials are correct.

These need to be wired in the application module.

    public static void contributeAuthenticationService( OrderedConfiguration<AuthenticationService> configuration )
    {
        configuration.add( "MINE", new MyAuthService() );
    }

    public static void contributeAccessValidator( MappedConfiguration<String, Class> configurations )
    {
        configurations.add( ChenilleKitAccessConstants.WEB_SESSION_USER_KEY, MyWebSessionUser.class );
    }

The WebSessionUser basically gives access to the role information to check base authorizations (which can be configured on page level). The actual authentication is provided in the AuthService, which could look like the following.

public class MyAuthService
    implements AuthenticationService
{
    private static final Logger log = Logger.getLogger( MyAuthService.class );

    public MyWebSessionUser doAuthenticate( String userName, String password )
    {
        if ( log.isDebugEnabled() ) log.debug( "try to authenticate " + userName );
        if ( null == userName ) return null;
        if ( null == password ) password = "";
        try
        {
            LoginCache loginCache = LoginCache.getLoginCache();
            LoginInfo loginInfo = loginCache.getWithAuth( userName );
            if ( loginInfo != null && !loginInfo.checkPassword( password) ) return null;
            if ( log.isDebugEnabled() ) log.debug( "authentication succeeded for " + userName );
            return new MyWebSessionUser( loginInfo );
        }
        catch ( Exception ex )
        {
            log.error( "problem while logging in user", ex );
            return null;
        }
    }
}

When the user tries to access a page which needs login, the user should be redirected to the login page, supply credentials and then continue on the requested page.
Chenillekit provides a login component which can be embedded in your pages, but if you want a separate login page, a bit more effort is required. The return is not fully implemented in 1.0 (probably because you need tapestry 5.1 to get this fully working with all parameters), so we need some tricks to get that to work.

First wire the login page in your application module.

    public static void contributeApplicationDefaults( MappedConfiguration<String, String> configuration )
    {
        configuration.add( ChenilleKitAccessConstants.LOGIN_PAGE, "login" );
    }

Then the login page code. This explicitly only allows you three attempts to login, and forces you to create a new session (most likely by closing the browser) before you can try again.

public class Login
{
    private static final int MAX_LOGIN_ATTEMPTS = 3;

    @Inject
    private Logger logger;

    @Persist
    @Property
    private String userName;

    @Property
    private String password;

    @ApplicationState
    private EquandaWebSessionUser webSessionUser;

    @Inject
    private Messages messages;

    @Inject
    @Local
    private AuthenticationService authenticationService;

    @Inject
    private ComponentResources resources;

    @Persist
    private int loginAttempts;

    private EquandaWebSessionUser tmpUser;

    @Inject
    private Cookies cookies;

    @Inject
    private ContextValueEncoder valueEncoder;

    @Inject
    private LinkFactory linkFactory;

    final public boolean isLoginAllowed()
    {
        return loginAttempts < MAX_LOGIN_ATTEMPTS;
    }

    void onValidateForm()
    {
        loginAttempts++;
        tmpUser = (EquandaWebSessionUser)authenticationService.doAuthenticate( userName, password );
    }

    Object onSuccess()
    {
        if ( null != tmpUser ) resources.discardPersistentFieldChanges();
        webSessionUser = tmpUser;
        if ( null != tmpUser )
        {
            //asoManager.set(  );
            String prevPage = cookies.readCookieValue( ChenilleKitAccessConstants.REQUESTED_PAGENAME_COOKIE );
            String prevContext = cookies.readCookieValue( ChenilleKitAccessConstants.REQUESTED_EVENTCONTEXT_COOKIE );
            if ( prevPage != null )
            {
                cookies.removeCookieValue( ChenilleKitAccessConstants.REQUESTED_EVENTCONTEXT_COOKIE );
                cookies.removeCookieValue( ChenilleKitAccessConstants.REQUESTED_PAGENAME_COOKIE );

                return prevPage;
            }
        }
        return null;
    }
}

The template looks like this (using a but more chenillekit to make it look “better”).

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
<head>
    <title>${equanda-message:title.Login}</title>
</head>

<body>

<div t:type="ck/RoundCornerContainer"
    fgcolor="#749572"
    bgcolor="#FFFFFF"
    style="padding: 40px;" >

    <div style="padding: 40px; background-color:#749572;" >
        
        <h1 style="text-align:center;">${equanda-message:title.Login}</h1>

        <t:form action="#">
            <t:errors/>
            <t:if test="isLoginAllowed()">
                <t:parameter name="else">
                    <div class="loginError" style="text-align:center;">
                        ${equanda-message:error.TooManyLoginAttempts}
                    </div>
                </t:parameter>

                <table align="center">
                    <tr>
                        <td>
                            <label>${equanda-message:login.UserName}</label>
                        </td>
                        <td>
                            <input t:type="TextField" t:value="userName" t:id="inputUserName" type="text" t:validate="required" />
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <label>${equanda-message:login.Password}</label>
                        </td>
                        <td>
                            <input t:type="PasswordField" t:value="password" t:id="inputPassword" type="password" t:validate="required" />
                        </td>
                    </tr>
                    <tr>
                        <td colspan="2" style="text-align:center;">
                            <input type="submit" value="${equanda-message:login.Submit}"/>
                        </td>
                    </tr>
                </table>
            </t:if>
        </t:form>

    </div>

</div>
</body>
</html>

Application server login integration

The application server integration can be handled by using the filter chaining which is done in tapestry. Both page render and component event filters start with the “AccessControl” filter from chenillekit-access. You can add your own filters after that and use this to pickup the WebSessionUser object to check whether a user a logged in (on the web side) to propagate the authentication to the entire application server.

In your application module, contribute to the filters.

    /* Contributes AppServerLoginFilter which handles appserver login */
    public static void contributePageRenderRequestHandler( OrderedConfiguration<PageRenderRequestFilter> configuration, final AppServerLoginFilter accessFilter )
    {
        configuration.add( "AppServerLogin", accessFilter, "after:AccessControl" );
    }

    /* Contributes AppServerLoginFilter which handles appserver login */
    public static void contributeComponentEventRequestHandler( OrderedConfiguration<ComponentEventRequestFilter> configuration, final AppServerLoginFilter accessFilter )
    {
        configuration.add( "AppServerLogin", accessFilter, "after:AccessControl" );
    }

We explicitly ordered these to be after the “AccessControl” filter though this is not really required as these will be in front anyway (they are added with “before:*”).

Your AppServerLoginFilter can now pickup the WebSessionUser object if it exists and forward the login to the application server login service.

public class AppServerLoginFilter
    implements ComponentEventRequestFilter, PageRenderRequestFilter
{
    private final ApplicationStateManager asoManager;
    private final AppServerLoginService appServerLoginService;

    public AppServerLoginFilter( ApplicationStateManager asoManager,
                                 AppServerLoginService appServerLoginService )
    {
        this.asoManager = asoManager;
        this.appServerLoginService = appServerLoginService;
    }

    public void handle( ComponentEventRequestParameters componentEventRequestParameters,
                        ComponentEventRequestHandler componentEventRequestHandler )
        throws IOException
    {
        WebSessionUser wsu = asoManager.getIfExists( EquandaWebSessionUser.class );
        if ( null != wsu ) appServerLoginService.appServerLogin( wsu );
        componentEventRequestHandler.handle( componentEventRequestParameters );
    }

    public void handle( PageRenderRequestParameters pageRenderRequestParameters,
                        PageRenderRequestHandler pageRenderRequestHandler )
        throws IOException
    {
        WebSessionUser wsu = asoManager.getIfExists( EquandaWebSessionUser.class );
        if ( null != wsu ) appServerLoginService.appServerLogin( wsu );
        pageRenderRequestHandler.handle( pageRenderRequestParameters );
    }
}