Spring Integration + Apache Shiro + Views

Hello all,

this topic raised out of the following thread
https://vaadin.com/forum/#!/thread/1811101/1811100
.

What I need is basically absolutely the same as in this sample application, just that I want to use a View for the login page instead of a freemarker template:
https://github.com/xpoft/vaadin-samples/tree/master/apache-shiro
.


That was the short problem description :slight_smile:


Now the long description:

So the core requirement is a simple authentication & authorization for a Vaadin application. For that purpose I want to use Apache Shiro, as it provides a simple way using annotations to satisfy the authorization needs (e.g. roles) and I can provide (custom) realms for e.g. authentication through an active directory.

Furthermore it would be very nice if the authentication would not happen through a plain jsp or http site using form authentication, but by using a view.

After I did some investigation I saw that the Spring Integration Addon (
https://vaadin.com/directory#addon/springvaadinintegration
) provides a simple way of using Apache Shiro (besides the great benefits of using spring), as, besides the useful autodiscovering of the views, a class called ShiroSecurityNavigator simply handles the authorization needs.

So I looked at the samples and tried to modify it in a way that I can use a view instead of a freemarker template.


Problem:

Using Shiros filter chain, I can not provide a login page from the views, as the url looks like this: /#!login and due to the fact that everything after the fragment identifier is not transferred to the server, Shiro does not work an produces an infinite redirect loop.


Long story short:

Has anyone a hint or at best a working example on how to use the ShiroSecurityNavigator benefits (autodiscovering of views, authorization via annotations) of the Spring Integration Addon together with a Login-View?

Your questions cover a lot of ground but I’ll describe my setup. I haven’t used the ShiroSecurityNavigator because my application’s security requirements were a little more complex than simple roles (the requirements folks love fine grained permissions).

First, the basic Spring configuration for Shiro:

[code]


[/code]This sets up an application style Shiro security manager, not a web security manager. I don’t have any security related stuff in my web.xml (or equivilent servlet configuration). I found this to be a better fit for a Vaadin application because, as you pointed out, you don’t have specific URLs that you’re navigating to because all navigation is done through URI fragments. The VaadinSessionManager and VaadinSecurityContext are custom classes I found were needed to work with push in 7.1+ because Shiro’s default SecurityUtils (and related classes) depend on thread locals which may not work because different threads may be used when suspending or resuming requests in a push configuration. Refer to my
blog post
for more details.

To handle the login, I have a normal view with a button that executes some pretty standard Shiro stuff. There isn’t much Vaadin or Spring related here.

public void onLoginClick() {

try {
      Subject subject = securityContext.getSubject();
     
      if (subject.isAuthenticated()) {
        subject.logout();
      }

    UsernamePasswordToken token = new UsernamePasswordToken(userTxt.getValue(), passwordTxt.getValue().toCharArray());
      subject.login(token);

   // Navigate to the user's home page here.
}
catch (AuthenticationException ex) {
      log.info("Login attempt failed for principal [{}]
: {}.", token.
          getPrincipal(), ex.getMessage());

      // Show the user some kind of nice rejection message here.
    }
}

As for enforcing view access restrictions, I implemented a GuardedView interface that my protected views implement. In my UI, I register as a view change listener and in the beforeViewChange method I check if the view is a GuardedView and then ask it if it can display given the current URL parameters, authc, and authz information. The implementation of these checks is usually just normal business rules and permission checking using Subject.hasPermission. In theory if you can boil all view access down to just roles, the ShiroSecurityNavigator might work for you but I’ve never had a simple enough set of view rules to try it.

Different applications use the same basic configuration and I just swap out the realm implementation appropriately. Some of my applications use a custom Oracle DB for authentication while others use ActiveDirectory. At this point you’re completely in the Shiro world where you’ll want to look at Realm and its subclasses like JdbcRealm and ActiveDirectoryRealm. For my cases I ended up implementing my own realms (it is pretty easy) because my DB structure didn’t map into the JDBC realm and we use Atlassian Crowd for AD access so I couldn’t use the standard AD realm.

If you want to or need to implement your own AD authentication, I recommend looking at
Atlassian Crowd
(especially if you are already using Atlassian products like Jira) which can hide some of the AD details behind a nice REST interface or Spring LDAP which I describe
here
.

Hope that gives you something to start with.

-mike

First of all, thank you a lot for your help!

What you say about the single page style & the security manager (application style vs. web style) make quite a lot of sense.
I read through your blog post and tried to implement a simple login using the given information.

I saw that the VaadinSecurityContext extends a custom SecurityContext. Could you maybe provide some Details about this class?

The guarded view approach makes sense, I will try this once I get the login working. Also it follows the suggested Vaadin approach and one benefit would be that by doing the security check “live” before a view change one has always the latest roles & rights of the subject (I saw that the ShiroSecurityNavigator does this only one during adding the Views to the navigator while getting initialized).

After a simple login works, I will try to add the AD authentication. But this shouldnt be to hard as - like you mentioned - this is plain/standard Shiro-stuff.

And again, thanks for the help!

SecurityContext is just an interface to support easy DI and mock testing of my code. I’ll include it below but nothing special:

/**
 * Simple wrapper on the {@link SecurityUtils} that supports dependency
 * injection to make unit testing simpler and to hide the singleton nature of
 * the {@link Subject}.
 * 
 * @author mpilone
 */
public interface SecurityContext {

  /**
   * Returns the security manager for the application (never null).
   * 
   * @return the security manager for the application
   * @see SecurityUtils#getSecurityManager()
   */
  public SecurityManager getSecurityManager();

  /**
   * Returns the subject for the application and thread which represents the
   * current user. The subject is always available; however it may represent an
   * anonymous user.
   * 
   * @return the subject for the current application and thread
   * @see SecurityUtils#getSubject()
   */
  public Subject getSubject();

  /**
   * Returns the principal or null if there is no principal. This
   * is simply a shortcut for {@link SecurityContext#getSubject()} followed by
   * {@link Subject#getPrincipal()}.
   * 
   * @return the primary principal or null
   * @see Subject#getPrincipal()
   * @see ContentDepotRealm2
   */
  public Object getPrincipal();

  /**
   * Returns the first discovered principal assignable from the specified type,
   * or null if there are none of the specified type. This is a shortcut for
   * {@link SecurityContext#getSubject()} followed by
   * {@link Subject#getPrincipals()} followed by
   * {@link PrincipalCollection#oneByType(Class)}.
   * 
   * @param type
   *          the type of the principal to get
   * @return the principal or null
   */
  public <T> T getPrincipalByType(Class<T> type);

}

Hi again,

thank you very much for your help, I had finally the time to try it out and to get it working in a simple test implementation.

To use the authorization-annotations for the views I simply extended the DiscoveryNavigator provided by the Spring Integration Addon and intercept the navigateTo() method to first check the authorization.

For the authorization check I just quickly copied some of the code from the ShiroSecurityNavigator (again from the Spring Integration Addon).

The classes look like follows (just if someone is dealing with the same problem):


SecureNavigator

@Configurable(preConstruction = true)
public class SecureNavigator extends DiscoveryNavigator {

    @Inject
    private ViewSecurityUtils viewSecurityUtils;

    @Inject
    private VaadinSecurityContext securityContext;

    private String login;
    private String unauthorized;

    public SecureNavigator(UI ui, ComponentContainer container) {
        super(ui, container);
    }

    public SecureNavigator(UI ui, SingleComponentContainer container) {
        super(ui, container);
    }

    public SecureNavigator(UI ui, ViewDisplay display) {
        super(ui, display);
    }

    public SecureNavigator(UI ui, NavigationStateManager stateManager,
                           ViewDisplay display) {
        super(ui, stateManager, display);
    }

    @Override
    protected void navigateTo(View view, String viewName, String parameters) {
        if (viewSecurityUtils.accessPermitted(view)) {
            super.navigateTo(view, viewName, parameters);
        } else {
            handleAccessDenied();
        }
    }

    private void handleAccessDenied() {
        if (securityContext.getSubject().isAuthenticated()) {
            super.navigateTo(unauthorized);
        } else {
            super.navigateTo(login);
        }
    }

    public void setLogin(String navigationState) {
        this.login = navigationState;
    }

    public void setUnauthorized(String navigationState) {
        this.unauthorized = navigationState;
    }
}


ViewSecurityUtils

@Component
@Configurable(preConstruction = true)
public class ViewSecurityUtils {

    @Inject VaadinSecurityContext securityContext;

    public boolean accessPermitted(View view) {
        boolean isAllow = true;

        Class<?> clazz = view.getClass();

        Subject subject = securityContext.getSubject();

        if (clazz.isAnnotationPresent(RequiresRoles.class)) {
            isAllow = hasRoles(subject, clazz.getAnnotation(RequiresRoles.class));
        }

        if (isAllow && clazz.isAnnotationPresent(RequiresPermissions.class)) {
            isAllow = hasPermissions(subject, clazz.getAnnotation(RequiresPermissions.class));
        }

        if (isAllow && clazz.isAnnotationPresent(RequiresAuthentication.class)) {
            isAllow = subject.isAuthenticated();
        }

        if (isAllow && clazz.isAnnotationPresent(RequiresGuest.class)) {
            isAllow = subject.getPrincipals() == null;
        }

        if (isAllow && clazz.isAnnotationPresent(RequiresUser.class)) {
            isAllow = subject.getPrincipals() != null && !subject.getPrincipals().isEmpty();
        }

        return isAllow;
    }

    private boolean hasRoles(Subject subject, RequiresRoles requiredRoles) {

        boolean hasRoles = false;

        String[] roles = requiredRoles.value();
        Logical logical = requiredRoles.logical();
        if (roles.length > 0) {
            if (!subject.isAuthenticated()) {
                return false;
            }

            if (logical == Logical.AND && subject.hasAllRoles(Arrays.asList(roles))) {
                hasRoles = true;
            }

            if (logical == Logical.OR) {
                for (boolean hasRole : subject.hasRoles(Arrays.asList(roles))) {
                    if (hasRole) {
                        hasRoles = true;
                        break;
                    }
                }
            }
        }

        return hasRoles;
    }

    private boolean hasPermissions(Subject subject, RequiresPermissions requiredPermissions) {

        boolean hasPermissions = false;

        String[] permissions = requiredPermissions.value();
        Logical logical = requiredPermissions.logical();

        if (permissions.length > 0) {
            if (!subject.isAuthenticated()) {
                return false;
            }

            if (logical == Logical.AND && subject.isPermittedAll(permissions)) {
                hasPermissions = true;
            }

            if (logical == Logical.OR && subject.isPermittedAll(permissions)) {
                for (boolean isPermitted : subject.isPermitted(permissions)) {
                    if (isPermitted) {
                        hasPermissions = true;
                        break;
                    }
                }
            }
        }

        return hasPermissions;
    }

}

Thank you again for your help!

Glad it is all working for you. Nice work and thanks for sharing your final code.

-mike

Hi,

I think I have antoher question that you might be able to answer:
Is it somehow possible to use Shiros “Remember me” functionality in this setup? And if yes, how would you do it?

Thank you!