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