Devs,
   Recently, I wanted to integrate wicket-auth-roles with servlet 3.0
container security.  What I ultimately did was create a simple SigninPage
as so:

public class LoginInterceptorPage extends WebPage {

   public  LoginInterceptorPage() {
         continueToOriginalDestination();
   }

}

In my Application class that extends AuthenticatedWebApplication I
implement the getSignInPageClass() to return the LoginInterceptorPage.

The trick is to mount the LoginInterceptorPage to a url that the
servletContainer will pick up  as a security-contraint.
web.xml snippet
<security-constraint>
        <display-name>Constraint1</display-name>
        <web-resource-collection>
            <web-resource-name>secure</web-resource-name>
            <description/>
            <url-pattern>/sec/*</url-pattern>       <!-------------   If
this pattern is seen then redirect to login scheme if not logged in  -->
        </web-resource-collection>
        <auth-constraint>
            <description>user</description>
            <role-name>USER</role-name>
        </auth-constraint>
    </security-constraint>

and in Application.init()  I added
mountPage("sec/login.html",LoginInterceptorPage.class); // maps to a url
matching pattern set in web.xml

Also I added getSecuritySettings().setAuthorizationStrategy(new
AnnotationsRoleAuthorizationStrategy(this));
to use the annotation @AuthorizeInstantiation to mark pages or packages as
needing authorization.

For the WebSession I implement as so:
public class ServletContainerAuthenticatedWebSession extends
AbstractAuthenticatedWebSession {

    private static final long serialVersionUID = 1L;

    /**
     * @return Current authenticated web session
     */
    public static ServletContainerAuthenticatedWebSession get() {
        return (ServletContainerAuthenticatedWebSession) Session.get();
    }

    public ServletContainerAuthenticatedWebSession(Request request) {
        super(request);
    }

    @Override
    public Roles getRoles() {
        if (isSignedIn()) {
            return new UserPrincipalRoles();
        }
        return null;
    }

    @Override
    public boolean isSignedIn() {
        return getRequest().getUserPrincipal() != null;
    }

    public void signOut() {
        if (isSignedIn()) {
            try {
                getRequest().logout();
            } catch (ServletException ex) {
                throw new RuntimeException(ex);
            }
        }
    }

    private static HttpServletRequest getRequest() {
        return (HttpServletRequest)
RequestCycle.get().getRequest().getContainerRequest();
    }


}

Where UserPrincipalRoles is :
public class UserPrincipalRoles extends Roles{

    public UserPrincipalRoles() {

    }

    @Override
    public boolean hasAllRoles(Roles roles) {
        Iterator<String> allRoles = roles.iterator();
        boolean result = true;
        while(allRoles.hasNext() && result) {
            result = getRequest().isUserInRole(allRoles.next());
        }
        return result;
    }

    @Override
    public boolean hasAnyRole(Roles roles) {
       Iterator<String> allRoles = roles.iterator();
        boolean result = false;
        while(allRoles.hasNext() && !result) {
            result = getRequest().isUserInRole(allRoles.next());
        }
        return result;
    }

    @Override
    public boolean hasRole(String role) {
        return getRequest().isUserInRole(role);
    }

    private static HttpServletRequest getRequest() {
     return
(HttpServletRequest)RequestCycle.get().getRequest().getContainerRequest();
    }


With this I solely rely on the container to determine if the session is
logged in via  getRequest().getUserPrincipal() != null; and role matching
using the container.

What happens is when a page that is annotated with  @AuthorizedInstantion
and the user is not authenticated, the
AuthenticatedWebApplication.onUnauthorizedInstantiation is triggered which
then redirects to the LoginInterceptorPage.  Since the LoginInterceptorPage
is mounted as sec/Login.html and the security-constraint is looks for
anything in sec/*  this forces the  auth-method of the login-config in the
web.xml to occur.

<login-config>
        <auth-method>FORM</auth-method>
        <realm-name>file-realm</realm-name>
        <form-login-config>
            <form-login-page>/login.html</form-login-page>
            <form-error-page>/error.html</form-error-page>
        </form-login-config>
    </login-config>

In my case I setup the auth-method to be FORM and added a login.html to the
root of the war file.  The login.html:
<form action="j_security_check" method=post>
    <p><strong>Please Enter Your User Name: </strong>
    <input type="text" name="j_username" size="25">
    <p><p><strong>Please Enter Your Password: </strong>
    <input type="password" size="15" name="j_password">
    <p><p>
    <input type="submit" value="Submit">
    <input type="reset" value="Reset">
</form>

I set up the file-realm for testing, but obviously ldap, database, SPENAGO,
etc can replace this for production. Also html can be jazzed up :)

Once the user successfully authenticated, the servlet container redirects
back to sec/login.html which then calls continueToOriginalDestination() and
ultimately to the page annotated with  @AuthorizedInstantion, assuming the
user had the proper role the page would be displayed.


This works great.  So from there what I realized is I should mount all
pages that are annotated with  @AuthorizedInstantion to a url that is
caught in the security-constraint of web.xml.  This I call double checking,
that is first the security container automatically intercepts any call to a
bookmarkable page ie setResponse(SomeSecurePage.class)  and forces the
login page, or if a non bookmarkable call occurs the
onUnauthorizedInstantiation is triggered which redirects to the
LoginInterceptorPage which is mounted to a url that is mapped in the
security-contraint in the web.xml. Furthurmore if a bookmarkable secure
page is not mapped to a url matching the security-constraint the
 onUnauthorizedInstantiation is triggered which intercepts and forces the
login page of the container.

Nice, with no changes to wicket I can easily integrate wicket-auth-role to
use the servlet 3.0 security. (pre 3.0 the getUserPrincipal and such was
not in HttpServletRequest)  Also one could create a login page with wicket
and call request.authenticate(user,pass), but login-config did not like it
when I set the url to that page.


All that being said what I needed was a way to AutoMount any page or
package that had the  @AuthorizedInstantion annotation.  Based on that I
created an AnnotationProcessor that generated source which was a list of
url and classes.  Here is a sample of the generated source

public class MyAppMountInfo implements MountInfo
{
@Override
public List<Mount> getMountPoints() {
List<Mount> ret = new ArrayList<Mount>();
ret.add(new
Mount("sec/custom/Page3.shtml", com.example.ui.user2.Page3.class));
ret.add(new Mount("sec/yo/Yo.html", com.example.ui.user2.Page4.class));
ret.add(new Mount("sec/user.html", com.example.ui.user.Page2.class));
ret.add(new
Mount("sec/AdminPage.shtml", com.example.ui.admin.AdminPage.class));
return ret;
}
}



The class is generated as AppName + MountInfo and implements MountInfo so
that in the app code you can do something like so
public class AutoMounter {

    public static boolean mountAll(WebApplication app) {
        String mapInfoClassName = app.getClass().getCanonicalName() +
"MountInfo";
        try {

            MountInfo mountInfo = (MountInfo)
Class.forName(mapInfoClassName).newInstance();

            for (MountInfo.Mount mp : mountInfo.getMountPoints()) {
                app.mountPage(mp.path, (Class<Page>) mp.pageClass);
            }
            return true;
        } catch (Exception ex) {
            return false;
        }
    }
}

where MountInfo is:
public interface MountInfo {

    List<Mount> getMountPoints();

    public static class Mount {

        String path;
        Class<? extends Page> pageClass;

        public Mount(String path, Class<? extends Page> pageClass) {
            this.path = path;
            this.pageClass = pageClass;
        }

    }
}


In MyApp,init()  just call AutoMounter.mountAll(this);

For the processor to actually generate the code you also nee to add the
@AutoMount(secure=true)  to the AppClass, this is the annotation that the
processor looks for to process the code.

public @interface AutoMount {

    String defaultRoot() default "";
    String mimeExtension() default "";
    boolean secure() default false;
    String secureRoot() default "secure";
}

When implementing this I also realized that maybe users just wanted to
AutoMount pages even without doing the secureMount stuff, so I made it also
possible to Mount any page.  To set a Mount Point you add the @MountPoint
to a page or package-info if you would like to automount all pages in a
package.

examples:

@MountPoint(path="users/ImportantPage.html")
public class ImportantUserPage extends WebPage {...

ultimately creates  mountPage("users/ImportantPage.html",
com.example.ui.user.ImportantUserPage.class);

However assume AutoMount(defaultRoot="users", mimeExtension="php")  is set
on the App Class and

@MountPoint
public class ImportantUserPage extends WebPage {...

creates  mountPage("users/ImportantUserPage.php",
com.example.ui.user.ImportantUserPage.class);  // Really php it is just to
prove a point


Finally the reason I initially created this code
 AutoMount(secure=true, secureRoot="sec")
public class MyApp ....



@AuthorizedInstantiation({"USERS","ADMIN"})
public class SecureUserPage extends WebPage {


creates mountPage("sec/SecureUserPage",
com.example.ui.user.secure.SecureUserPage.class);


I already created code and wonder if I should create a pull request, or
make it a wicketstuff or just use it for myself.   I personally think it
would be nice in wicket itself but I wanted to ask first before going any
further.


Thanks,
John Sarman

Reply via email to