Tom, Thanks for the link. That is basically a Runtime equivalent of what I produced. The code relies on spring-framework to actually parse through the classes at runtime, which one of my goals was to not depend on any jars other than wicket jars and the Java core so I may just create a separate wicketstuff.
Thanks again for sharing that project with me. John On Tue, Mar 4, 2014 at 1:28 PM, Burton, Tom F (DOR) <[email protected]>wrote: > Hello John, > > Not to further muddy the water or insult the good work that's been done. > https://github.com/wicketstuff/core/wiki/Annotation > > I don't know how widely this project is used, but it sounds like you've > duplicated and possibly extended the project. Maybe they could be merged? > > > Tom Burton > > -----Original Message----- > From: Martin Grigorov [mailto:[email protected]] > Sent: Tuesday, March 04, 2014 1:16 AM > To: [email protected] > Subject: Re: AutoMounter Annotation Processor > > Hi John, > > > On Wed, Feb 26, 2014 at 7:53 PM, John Sarman <[email protected]> wrote: > > > 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. > > > > I think there is not enough interest in this functionality at the moment. > There were no requests from Wicket users for supporting this neither in > Jira nor in the mailing lists. > I believe this is so because Wicket's authentication/authorization is much > simpler than the ones in Servlet specification. > > Additionally your code will require a new compilation step - the > annotation processing. So far Wicket has tried to avoid any code generation. > > Anyway, I'd love to redirect any potential user to the place where you > decide to share your implementation - WicketStuff or your own repository. > If there is more demand later then we can reconsider your offer to donate > this code to Wicket itself. > > Thank you! > > > > > > > > Thanks, > > John Sarman > > >
