Monday, April 11, 2011

myopenid + google federated login + with spring roo. dead simply.

##gonna clean up the formatting on this article soon.
## this step by step guide is based on the following excellent article:
http://bsgdev.wordpress.com/2011/01/18/exploring-google-and-openid-login-with-spring-security-and-spring-roo/

# Project
project --topLevelPackage za.co.bsg.rnd.trf.openidlogin

# Persistence
persistence setup --provider HIBERNATE --database HYPERSONIC_IN_MEMORY --databaseName openidlogin --hostName localhost

# Dependencies
dependency add --groupId org.springframework.security --artifactId spring-security-openid --version 3.0.5.RELEASE
dependency add --groupId org.openid4java --artifactId openid4java --version 0.9.5

# Domain model
enum type --class ~.domain.UserRole
enum constant --name ROLE_ADMIN
enum constant --name ROLE_USER

enum type --class ~.domain.EmployeeStatus
enum constant --name ACTIVE
enum constant --name DORMANT
enum constant --name RESIGNED
enum constant --name TERMINATED

entity --class ~.domain.Employee --table employee --identifierColumn employee_id
field string --fieldName username --column username --notNull
field string --fieldName password --column password --notNull
field enum --type ~.domain.UserRole --fieldName userRole --notNull --enumType STRING
field string --fieldName openIdIdentifier --column openid_identifier
field string --fieldName firstName --column first_name
field string --fieldName lastName --column last_name
field string --fieldName emailAddress --column email_address
field enum --type ~.domain.EmployeeStatus --fieldName status --notNull --enumType STRING

# we will also need a finder
finder add --finderName findEmployeesByOpenIdIdentifier --class ~.domain.Employee

# Scaffold the web frontend
controller all --package ~.web

# Spring Security
security setup

# Other classes
class --class ~.InsertTestData
class --class ~.OpenIdUserDetailsService



Now implement these two java classes InsertTestData.java simply populates an initial db with data.
OpenIdUserDetailsService is our implementation of spring UserDetailsService that uses our finder to query the db using openid and return the right employee which we've augmented to be a fully fledged UserDetails Object by implementing that interface.



package za.co.bsg.rnd.trf.openidlogin;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.apache.commons.codec.binary.Hex;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import za.co.bsg.rnd.trf.openidlogin.domain.Employee;
import za.co.bsg.rnd.trf.openidlogin.domain.EmployeeStatus;
import za.co.bsg.rnd.trf.openidlogin.domain.UserRole;

@Component
@Configurable
public class InsertTestData implements
ApplicationListener<contextrefreshedevent>
{

public static final String PASSWORD = "password";

@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
init();
}

private void init() {
if (!Employee.findAllEmployees().isEmpty()) {
return;
}
Employee employeeAdminActive = new Employee();
employeeAdminActive.setUsername("user1");
employeeAdminActive.setPassword(hexSha256(PASSWORD));
employeeAdminActive.setUserRole(UserRole.ROLE_ADMIN);
employeeAdminActive.setStatus(EmployeeStatus.ACTIVE);
employeeAdminActive.setOpenIdIdentifier("https://you.myopenid.com/");
employeeAdminActive.persist();
Employee employee2 = new Employee();
employee2.setUsername("user2");
employee2.setPassword(hexSha256(PASSWORD));
employee2.setUserRole(UserRole.ROLE_USER);
employee2.setFirstName("Peter");
employee2.setLastName("Jones");
employee2.setStatus(EmployeeStatus.ACTIVE);
employee2.setOpenIdIdentifier("https://www.google.com/accounts/o8/id?id=your_id_goes_here");
employee2.persist();
Employee employee3 = new Employee();
employee3.setUsername("user3");
employee3.setPassword(hexSha256(PASSWORD));
employee3.setUserRole(UserRole.ROLE_USER);
employee3.setFirstName("Christina");
employee3.setLastName("Applegate");
employee3.setStatus(EmployeeStatus.RESIGNED);
employee3.persist();
}



private String hexSha256(String password2) {
MessageDigest md;
try {
md = MessageDigest.getInstance("SHA-256");
md.update(password2.getBytes());
byte byteData[] = md.digest();
return new String(Hex.encodeHex(byteData));

} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return password2;
}
}
package za.co.bsg.rnd.trf.openidlogin;

import org.springframework.security.authentication.DisabledException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import za.co.bsg.rnd.trf.openidlogin.domain.Employee;

import java.util.List;

public class OpenIdUserDetailsService implements UserDetailsService {

public UserDetails loadUserByUsername(String openIdIdentifier) {
List<employee> employeeList =
Employee.findEmployeesByOpenIdIdentifier(openIdIdentifier).getResultList();
Employee employee = employeeList.size() == 0 ? null : employeeList.get(0);

if (employee == null) {
throw new UsernameNotFoundException("User not found for OpenID: " + openIdIdentifier);
} else {
if (!employee.isEnabled()) {
throw new DisabledException("User is disabled");
}
return employee;
}
}

}


/*Now make sure Employee.java implements the UserDetails Interface.

public class Employee implements UserDetails


and add the following interface method implementations.
(you need to leave roo running to correct the aspect j getters for username and password.)
*/


@Override
public String getUsername() {
return this.username;
}

@Override
public String getPassword() {
return this.password;
}

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

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

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

@Override
public boolean isEnabled() {
return this.status == EmployeeStatus.ACTIVE;
}

@Override
public Collection getAuthorities() {
Collection grantedAuthorities = new HashSet();
grantedAuthorities.add(
new GrantedAuthorityImpl(this.userRole.name()));
return grantedAuthorities;
}



in applicationContext-securtity
change
<intercept-url pattern="/**" access="permitAll" />
to
<intercept-url pattern="/" access="permitAll" />
<intercept-url pattern="/login/**" access="permitAll" />
<intercept-url pattern="/**" access="isAuthenticated()" />
<openid-login authentication-failure-url="/login?login_error=t" user-service-ref="openIdUserService" />


and change
<user-service>
<user name="admin" password="8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918" authorities="ROLE_ADMIN"/>
<user name="user" password="04f8996da763b7a969b1028ee3007569eaf3a635486ddab211d512c85b9df8fb" authorities="ROLE_USER"/>
</user-service>

to

<jdbc-user-service
data-source-ref="dataSource"
users-by-username-query=
"SELECT
U.username,
U.password,
U.status = 'ACTIVE'
FROM
employee U
WHERE
U.username=?"

authorities-by-username-query=
"SELECT
U.username,
U.user_role as authority
FROM
employee U
WHERE
U.username=?"
/>



after </authentication-manager> add:
<beans:bean id="openIdUserService" class="za.co.bsg.rnd.trf.openidlogin.OpenIdUserDetailsService" />


add the google and open id form actions to the autogenerated login.jspx after </util:panel>

<spring:url value="/j_spring_openid_security_check" var="form_url_openid" />
<spring:url var="google" value="/resources/images/google-account-logo.png" />
<spring:url var="openid" value="/resources/images/openid-logo.png" />

<util:panel id="title_google" title="Google Login" >
<img src="${google}" style="float:right" />
<p style="height:0.25em"/>
<form action="${fn:escapeXml(form_url_openid)}" method="post">
<input name="openid_identifier" size="50"
maxlength="100" type="hidden"
value="http://www.google.com/accounts/o8/id"/>

<div class="submit">
<input id="proceed_google" type="submit" value="Sign in with Google" />
</div>
</form>
<br/>
<p/>
</util:panel>

<util:panel id="title_openid" title="OpenID Login" >
<a href="http://openid.net/get-an-openid/" target="_blank" title="Where do I get one?">
<img src="${openid}" style="float:right" />
</a>
<p/>
<form action="${fn:escapeXml(form_url_openid)}" method="post">
<div>
<label for="openid_identifier">OpenID</label>
<input id="openid_identifier" name="openid_identifier" type="text" style="width:150px" />
</div>
<br/>
<div class="submit">
<input id="proceed_openid" type="submit" value="${fn:escapeXml(submit_label)}" />
</div>
</form>
</util:panel>



## Just change you.myopenid.com to whatever provider you prefer for that user in the insert test data method and you are good to go.

No comments:

Post a Comment