Dear Wiki user,

You have subscribed to a wiki page or wiki category on "Tapestry Wiki" for 
change notification.

The following page has been changed by UlrichStaerk:
http://wiki.apache.org/tapestry/Tapestry5HowToSpringSecurityAndOpenId

The comment on the change is:
intermittent save, have to go to lunch ;-)

New page:
[[TableOfContents]]
= Motivation =
OpenID is a service providing single-sign-on features. At our institute we are 
operating an OpenID provider that delegates authentication to our 
ActiveDirectory where all our students are registered. For a course planning 
system I was assessing Tapestrys integration possibilities with this OpenID 
provider and found out, that the tapestry-spring-security project already has 
much of the stuff needed for integration with our provider on board. This is a 
description of what you have to do in order to have OpenID authentication in 
your Tapestry apps.
= Background =
Spring Security already comes with everything needed for authenticating users 
against an OpenID provider (although this is somewhat incomplete regarding 
additional attributes that can be provided by the OpenID provider). But the 
tapestry-spring-security lacks the configuration needed to use this out of the 
box.
In particular it lacks the filter configuration for the 
OpenIDAuthenticationProcessingFilter that processes incoming OpenID 
authentications. In a "normal" setup this is just another Filter that you 
configure in your web.xml. In a Tapestry setup though, Tapestry receives all 
incoming requests and processes them in the HttpServletRequestHandler pipeline. 
Fortunately this pipeline can be extended by contributing 
HttpServletRequestFilters. So all we need to do is wrap the 
OpenIDAuthenticationProcessingFilter and contribute it to the 
HttpServletRequestHandler pipeline. Such a wrapper is provided by the 
tapestry-spring-security project. With that in place we can process incoming 
OpenID authentication requests.
Next, we have to look up the authenticated user in our local database where we 
store the roles assigned to a user. For this we have to implement a 
UserDetailsService which retrieves a user and his roles from some data store. 
The user object that is returned has to implement the UserDetails interface and 
the roles he holds have to implement the GrantedAuthority interface. If you 
don't want to grant access to some OpenID authenticated user, you have to do 
that in the UserDetailsService. The implementation I will show you grants 
accesss to EVERY OpenID authenticated user so don't use this in a "real" 
environment.

= Requirements =

tapestry-hibernate and tapestry-spring-security have to be in your pom.xml (or 
their jars and all of their dependencies have to be in your classpath). 
Configuring tapestry-hibernate is outside the scope of this article, please 
consult it's own documentation.

= Implementation =

Let's begin with the User and Role objects that implement the UserDetails and 
GrantedAuthority interfaces, respectively. These are used to store users and 
their roles in a local database using Hibernate. The User is a minimalistic 
implementation that only stores an id and a username.

{{{
package org.mygroup.myapp.entities;

import java.util.HashSet;
import java.util.Set;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Transient;

import org.springframework.security.GrantedAuthority;
import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.userdetails.UserDetails;

/**
 * A minimalistic UserDetails implementation providing a username only. Storing
 * a password is not necessary since the OpenID provider will do the 
authentication.
 * 
 * @author Ulrich Stärk
 */
@Entity
public class User implements UserDetails
{
    private static final long serialVersionUID = 4068206679084877888L;

    private int id;

    private String username;

    private Set<Role> roles;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    public int getId()
    {
        return id;
    }

    public void setId(int id)
    {
        this.id = id;
    }

    @ManyToMany
    public Set<Role> getRoles()
    {
        return roles;
    }

    public void setRoles(Set<Role> roles)
    {
        this.roles = roles;
    }

    @Transient
    public GrantedAuthority[] getAuthorities()
    {
        Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();

        for (Role role : getRoles())
        {
            authorities.add(new GrantedAuthorityImpl(role.getAuthority()));
        }

        return authorities.toArray(new GrantedAuthority[authorities.size()]);
    }

    @Transient
    public String getPassword()
    {
        return "notused";
    }

    public String getUsername()
    {
        return username;
    }

    public void setUsername(String username)
    {
        this.username = username;
    }

    @Transient
    public boolean isAccountNonExpired()
    {
        return true;
    }

    @Transient
    public boolean isAccountNonLocked()
    {
        return true;
    }

    @Transient
    public boolean isCredentialsNonExpired()
    {
        return true;
    }

    @Transient
    public boolean isEnabled()
    {
        return true;
    }
}
}}}

{{{
package org.mygroup.myapp.entities;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import org.springframework.security.GrantedAuthority;

/**
 * A GrantedAuthority (aka Role) implementation. hashCode, equals and compareTo 
are
 * a bit messy (auto-generated), you might want to change this.
 * 
 * @author Ulrich St&auml;rk
 */
@Entity
public class Role implements GrantedAuthority
{
    private static final long serialVersionUID = -117212611936641518L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    private String authority;

    public int getId()
    {
        return id;
    }

    public void setId(int id)
    {
        this.id = id;
    }

    public String getAuthority()
    {
        return authority;
    }

    public void setAuthority(String authority)
    {
        this.authority = authority;
    }

    @Override
    public int hashCode()
    {
        return authority.hashCode();
    }

    @Override
    public boolean equals(Object obj)
    {
        if (this == obj) return true;
        if (obj == null) return false;
        if (obj.getClass() == String.class) return obj.equals(authority);
        if (getClass() != obj.getClass()) return false;
        final Role other = (Role) obj;
        if (authority == null)
        {
            if (other.authority != null) return false;
        }
        else if (!authority.equals(other.authority)) return false;
        return true;
    }

    public int compareTo(Object o)
    {
        if (this == o) return 0;
        if (o == null) return -1;
        if (o.getClass() == String.class) return authority.compareTo((String) 
o);
        if (getClass() != o.getClass()) return -1;
        final Role other = (Role) o;
        if (authority == null)
        {
            if (other.authority != null) return 1;
        }
        else
            return authority.compareTo(other.authority);
        return -1;
    }
}
}}}

Next, we need a UserDetailsService implementation, that finds users and their 
roles in our local database:

{{{
package org.mygroup.myapp.services.impl;

import java.util.HashSet;

import org.apache.tapestry5.hibernate.HibernateSessionManager;
import org.hibernate.Session;
import org.hibernate.criterion.Restrictions;
import org.slf4j.Logger;
import org.springframework.dao.DataAccessException;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.UserDetailsService;
import org.springframework.security.userdetails.UsernameNotFoundException;

import de.spielviel.mailadmin.entities.Role;
import de.spielviel.mailadmin.entities.User;

/**
 * @author Ulrich St&auml;rk
 */
public class UserDetailsServiceImpl implements UserDetailsService
{
    private HibernateSessionManager sessionManager;

    private Logger logger;

    public UserDetailsServiceImpl(HibernateSessionManager sessionManager, 
Logger logger)
    {
        this.sessionManager = sessionManager;
        this.logger = logger;
    }

    /**
     * Try to find the given user in the local database. Since we are using 
OpenID that user
     * might not exist in our database yet. If it doesn't, create a new user 
and store it.
     * 
     * WARNING: This implementation will permit EVERY OpenID authenticated user 
to log in. In
     * a real environment you want to restrict this to trusted OpenID providers 
or you have
     * to restrict those users to non-sensible information (by means of roles).
     */
    public UserDetails loadUserByUsername(String username) throws 
UsernameNotFoundException,
            DataAccessException
    {
        logger.debug("trying to find user " + username);

        Session session = sessionManager.getSession();

        User u = (User) session.createCriteria(User.class).add(
                Restrictions.eq("username", username)).uniqueResult();

        if (u == null)
        {
            logger.debug("user not found, creating");

            Role r = (Role) session.createCriteria(Role.class).add(
                    Restrictions.eq("authority", "ROLE_USER")).uniqueResult();

            if (r == null)
            {
                logger.debug("role not found, creating");

                r = new Role();

                r.setAuthority("ROLE_USER");

                session.saveOrUpdate(r);
            }

            u = new User();

            u.setUsername(username);

            u.setRoles(new HashSet<Role>());

            u.getRoles().add(r);

            session.saveOrUpdate(u);
        }

        logger.debug("returning user " + u.getUsername());

        sessionManager.commit();

        return u;
    }

}
}}}

That's it. The rest is setting this all up in your AppModule.

= Tying it together =

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to