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 >
