Hi John,

On Wed, Feb 26, 2014 at 7:53 PM, John Sarman <johnsar...@gmail.com> 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
>

Reply via email to