Spring Security with Openid and Database Integration

openidspringspring-security

I am very new to Spring and Spring Security, and hoping someone can help me to solve the following problem.

What I want to achieve is to extract user's username and email address after this user is successfully authenticated by OpenID provider(gmail), then check with the database in order to load the usermodel for this user.

In my spring-security.xml, i have

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:security="http://www.springframework.org/schema/security"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context-3.0.xsd
                           http://www.springframework.org/schema/security 
                           http://www.springframework.org/schema/security/spring-security-3.0.xsd">

    <security:authentication-manager alias="openIDAuthenticationManager" />

    <bean id="authenticationSuccessHandler" class="org.school.openid.service.YouEatAuthenticationSuccessHandler">
        <property name="defaultTargetUrl" value="/krams/main/common" />
        <property name="attributes2UserDetails" ref="openIDAttributes2UserDetails" />
    </bean>

    <security:http >
        <security:anonymous enabled="false" />
        <security:logout />
        <security:openid-login user-service-ref="userDetailsServiceOpenIDImpl" authentication-success-handler-ref="authenticationSuccessHandler"
            login-page="/krams/auth/login" authentication-failure-url="/krams/auth/login?error=true">
            <security:attribute-exchange>
                <security:openid-attribute name="email" type="http://schema.openid.net/contact/email" required="true" />
                <security:openid-attribute name="firstName" type="http://axschema.org/namePerson/first" required="true" />
                <security:openid-attribute name="lastName" type="http://axschema.org/namePerson/last" required="true" />
            </security:attribute-exchange>
        </security:openid-login>
    </security:http>

    <bean id="openIDAttributes2UserDetails" class="org.school.openid.service.OpenIDAttributes2UserDetailsImpl" />

    <bean id="userDetailsServiceOpenIDImpl" class="org.school.openid.service.UserDetailsServiceOpenIDImpl" />

</beans>

my problem is at UserDetailsServiceOpenIDImpl.java is

public class UserDetailsServiceOpenIDImpl implements UserDetailsService {

    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException, DataAccessException {
        System.out.println(username);
        //extract username and email address, HOW?
    }
}

The print statement prints out something like

https://www.google.com/accounts/o8/id?id=AItOawlq2C3EdFAuqp-ski_xkgB8jsEKbe-mZE

My questions are

(1) How could I extract the username and the email address from the returned url (also, I am not even sure if the username and email address returned correctly)?

(2) By running the debug on Eclipse, the YouEatAuthenticationSuccessHandler seems not called when the url (https://www.google.com/accounts/o8/id?id=AItOawlq2C3EdFAuqp-ski_xkgB8jsEKbe-mZE) returned.

Thanks.

Edit:
thanks for the link http://static.springsource.org/spring-security/site/docs/3.0.x/reference/ns-config.html#ns-openid.

It says that "The attribute values are returned as part of the authentication process and can be accessed afterwards using the following code:…"

I have added

OpenIDAuthenticationToken token = (OpenIDAuthenticationToken)SecurityContextHolder.getContext().getAuthentication();
List attributes = token.getAttributes();

into loadUserByUsername method. But the "token" object is null.

Edit 2
By following page https://fisheye.springsource.org/browse/spring-security/samples/openid/src/main/webapp/WEB-INF/applicationContext-security.xml?hb=true, i am able to extra the name and email address for the user.
My spring-security.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:security="http://www.springframework.org/schema/security"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context-3.0.xsd
                           http://www.springframework.org/schema/security 
                           http://www.springframework.org/schema/security/spring-security-3.1.xsd">
    <security:authentication-manager alias="openIDAuthenticationManager" />
    <security:http pattern="/krams/auth/login" security="none"/>
    <security:http auto-config="true" access-denied-page="/krams/auth/denied">
        <security:intercept-url pattern="/krams/main/*" access="ROLE_USER" />
        <security:anonymous enabled="false" />
        <security:logout 
            invalidate-session="true"
            logout-success-url="/krams/auth/login"
            logout-url="/krams/auth/logout"/>
        <security:openid-login 
            user-service-ref="registeringUserService"
            login-page="/krams/auth/login" 
            authentication-failure-url="/krams/auth/login?error=true" 
            default-target-url="/krams/main/common">
            <security:attribute-exchange identifier-match="https://www.google.com/.*">
                <security:openid-attribute name="email" type="http://schema.openid.net/contact/email" required="true" />
                <security:openid-attribute name="firstName" type="http://axschema.org/namePerson/first" required="true" />
                <security:openid-attribute name="lastName" type="http://axschema.org/namePerson/last" required="true" />
            </security:attribute-exchange>
            <security:attribute-exchange identifier-match=".*yahoo.com.*">
                <security:openid-attribute name="email" type="http://axschema.org/contact/email" required="true"/>
                <security:openid-attribute name="fullname" type="http://axschema.org/namePerson" required="true" />
            </security:attribute-exchange>
        </security:openid-login>
        <!-- if remember is needed
         <security:remember-me token-repository-ref="tokenRepo"/>
         -->       
    </security:http>
    <bean id="tokenRepo" class="org.springframework.security.web.authentication.rememberme.InMemoryTokenRepositoryImpl" />
<!--
    A custom UserDetailsService which will allow any user to authenticate and "register" their IDs in an internal map
    for use if they return to the site. This is the most common usage pattern for sites which use OpenID.
 -->
   <bean id="registeringUserService" class="org.school.openid.service.CustomUserDetailsService" />
</beans>

My CustomUserDetailsService.java

public class CustomUserDetailsService implements AuthenticationUserDetailsService {

    /*
    private final Map registeredUsers = new HashMap();
    */
       private static final List DEFAULT_AUTHORITIES = AuthorityUtils.createAuthorityList("ROLE_USER");
    protected static Logger logger = Logger.getLogger("service");    
    /**
     * Implementation of {@code AuthenticationUserDetailsService} which allows full access to the submitted
     * {@code Authentication} object. Used by the OpenIDAuthenticationProvider.
     */
    public UserDetails loadUserDetails(OpenIDAuthenticationToken token) {
        String id = token.getIdentityUrl();
        String email = null;
        String firstName = null;
        String lastName = null;
        String fullName = null;
        List attributes = token.getAttributes();
        for (OpenIDAttribute attribute : attributes) {
            if (attribute.getName().equals("email")) {
                email = attribute.getValues().get(0);
            }
            if (attribute.getName().equals("firstName")) {
                firstName = attribute.getValues().get(0);
            }
            if (attribute.getName().equals("lastName")) {
                lastName = attribute.getValues().get(0);
            }
            if (attribute.getName().equals("fullname")) {
                fullName = attribute.getValues().get(0);
            }
        }
        if (fullName == null) {
            StringBuilder fullNameBldr = new StringBuilder();
            if (firstName != null) {
                fullNameBldr.append(firstName);
            }
            if (lastName != null) {
                fullNameBldr.append(" ").append(lastName);
            }
            fullName = fullNameBldr.toString();
        }
        CustomUserDetails user = new CustomUserDetails(id,fullName,email, DEFAULT_AUTHORITIES);        
        logger.debug("Set username " + fullName + " email " + email);
        return user;
    }
}

My CustomUserDetails.java

public class CustomUserDetails extends User {
    private static final long serialVersionUID = 1L;
    private String email;
    private String name;
    public CustomUserDetails(String id,String name, String email,Collection authorities) {
        super(name, "unused", true,true,true,true,authorities);
        this.email = email;
        this.name = name;
    }
    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

And

...
<repository>
    <id>org.springframework.maven.milestone</id>
    <name>Spring Maven Milestone Repository</name>
    <url>http://maven.springframework.org/milestone</url>
</repository>
...
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
    <version>3.1.0.RC1</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>3.1.0.RC1</version>
    <type>jar</type>
        <scope>compile</scope>
</dependency>
<dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-config</artifactId>
      <version>3.1.0.RC1</version>
    </dependency>
        <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-openid</artifactId>
        <version>3.1.0.RC1</version>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>3.0.5.RELEASE</version>
</dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-openid</artifactId>
        <version>3.1.0.RC1</version>
        <type>pom</type>
        <scope>compile</scope>
    </dependency>

Hope can save you some time.

Best Answer

As I see that question text itself contains the answer. I am just pulling it out and posting it as the answer for the sake of clarity to other developers with the same problem. It took me a while to figure out that question has the answer!

To access user email & name, you need to add below configuration in the security xml file.

<security:attribute-exchange identifier-match="https://www.google.com/.*">
    <security:openid-attribute name="email" type="http://schema.openid.net/contact/email" required="true" />
    <security:openid-attribute name="firstName" type="http://axschema.org/namePerson/first" required="true" />
    <security:openid-attribute name="lastName" type="http://axschema.org/namePerson/last" required="true" />
</security:attribute-exchange>
<security:attribute-exchange identifier-match=".*yahoo.com.*">
    <security:openid-attribute name="email" type="http://axschema.org/contact/email" required="true"/>
    <security:openid-attribute name="fullname" type="http://axschema.org/namePerson" required="true" />
</security:attribute-exchange>

Thereafter, it will be accessible in the AuthenticationUserDetailsService class, as shown below.

public UserDetails loadUserDetails(OpenIDAuthenticationToken token) {
    String id = token.getIdentityUrl();
        :
        :
    List attributes = token.getAttributes();
    for (OpenIDAttribute attribute : attributes) {
        if (attribute.getName().equals("email")) {
            email = attribute.getValues().get(0);
        }
        if (attribute.getName().equals("firstName")) {
            firstName = attribute.getValues().get(0);
        }
        if (attribute.getName().equals("lastName")) {
            lastName = attribute.getValues().get(0);
        }
        if (attribute.getName().equals("fullname")) {
            fullName = attribute.getValues().get(0);
        }
    }
        :
        :
    // form and return user object
}

Considering that we use more of Java-based configuration/beans now, translating these XML configuration to Java-based configuration shouldn't be a problem.

Hope it helps.

Related Topic