8
votes

I'm implementing an app using spring security 3.0.2 with OpenId login and registration. I can login succesfully, but if the user isn't registered i want to do:

1) Get some OpenId attributes like email and name.
2) Show to the user a registration form with just these two fields and the OpenId URI filled.

I've been searching a lot but i didn't find an "ellegant" way of doing this. I wonder if some of u can come out with a solution to implement this strategy in my app.

Thanks in advance.

2

2 Answers

5
votes

You can't show the email and name before the user has registered/login himself, since he has to allow the app to access his profile first. You can show him this page with his openid, mail etc after he logged in.

Define which attributes you want to use:

<openid-login login-page="/openidlogin.jsp" authentication-failure-url="/openidlogin.jsp?login_error=true">
  <attribute-exchange>
    <openid-attribute name="email" type="http://schema.openid.net/contact/email" required="true" count="2"/>
    <openid-attribute name="name" type="http://schema.openid.net/namePerson/friendly" />
  </attribute-exchange>
</openid-login>

And then access the attributes, after the user has logged in:

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

Have a look at the example from the spring repository, and the OpenId Support Documentation.

0
votes

That is not implemented in Spring security < 3.1

However you can use a workaround with apectJ. Define the following aspect:

package org.acoveo.spring.utils;
@Aspect
public class OpenIDSpringAuthenticationHackAspect {
    static ThreadLocal<Authentication> authHolder = new ThreadLocal<Authentication>();
    @Around(value="execution(* org.springframework.security.openid.OpenIDAuthenticationProvider.authenticate(..))")
    public Object around(ProceedingJoinPoint jp) throws Throwable {
        try {
            Authentication auth = (Authentication) jp.getArgs()[0];
            authHolder.set(auth);
            Object returnVal = jp.proceed();
            authHolder.set(null);
            return returnVal;
        }catch(Throwable e) {
            System.out.println("Exception while running OpenIDSpringAuthenticationHackAspect");
            e.printStackTrace();
            return null;
        }
    }
    public static Authentication getTransientAuthentication() {
        return authHolder.get();
    }
}

and register it in your aop.xml:

<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">  
<aspectj>
    <weaver options="-showWeaveInfo -verbose" />
    <weaver>
        <include within="org.springframework.security.openid..*" />
        <!-- This is required to make the spring instrument javaagent work with hibernate CGLIB
         -->
        <exclude within="*..*CGLIB*" />
    </weaver>
    <aspects>
        <aspect name="org.acoveo.spring.utils.OpenIDSpringAuthenticationHackAspect" />
    </aspects>
</aspectj>

Then in your UserDetailsService, you can access the OpenID attributes as follows:

public UserDetails loadUserByUsername(String username, boolean includeTemporary) throws UsernameNotFoundException, DataAccessException {
    Authentication auth = OpenIDSpringAuthenticationHackAspect.getTransientAuthentication();
    if(auth != null && auth instanceof OpenIDAuthenticationToken) {
        // First try to find the user by their openid email address
        OpenIDAuthenticationToken openIdToken = (OpenIDAuthenticationToken)auth;
        String email = null;
        for(OpenIDAttribute attr : openIdToken.getAttributes()) {
            if("email".equals(attr.getName()) && attr.getValues() != null && !attr.getValues().isEmpty()) {
                email = attr.getValues().get(0);
                break;
            }
        }
        // TODO retrieve and return user