SEC-1490: Code for GAE Sample webapp

This commit is contained in:
Luke Taylor 2010-07-06 23:52:11 +01:00
parent c1c8fd1874
commit 5d35919ca3
39 changed files with 1184 additions and 4 deletions

2
.gitignore vendored
View File

@ -4,6 +4,7 @@ target/
.project
.DS_Store
.settings/
out/
build/
*.log
*.iml
@ -11,3 +12,4 @@ build/
*.iws
.gradle/
gradle.properties
atlassian-ide-plugin.xml

View File

@ -37,6 +37,9 @@ allprojects {
if (!config) {
return
}
ideaModule {
gradleCacheVariable = 'GRADLE_CACHE'
}
}
ideaModule {

View File

@ -3,9 +3,11 @@ apply plugin: 'groovy'
repositories {
mavenRepo name:'localRepo', urls: "file://" + System.properties['user.home'] + "/.m2/repository"
mavenCentral()
mavenRepo name: 'GAE', urls:'http://maven-gae-plugin.googlecode.com/svn/repository'
mavenRepo name:'Shibboleth Repo', urls:'http://shibboleth.internet2.edu/downloads/maven2'
}
// Docbook Plugin
dependencies {
def fopDeps = [ 'org.apache.xmlgraphics:fop:0.95-1@jar',
'org.apache.xmlgraphics:xmlgraphics-commons:1.3',
@ -26,6 +28,11 @@ dependencies {
'net.sf.docbook:docbook-xsl:1.75.2:ns-resources@zip'
}
// GAE
dependencies {
compile 'com.google.appengine:appengine-tools-api:1.3.5'
}
task ide(type: Copy) {
from configurations.runtime
into 'ide'

View File

@ -0,0 +1,26 @@
package gae;
import com.google.appengine.tools.admin.AppCfg
import org.gradle.api.*;
class GaePlugin implements Plugin<Project> {
public void apply(Project project) {
if (!project.hasProperty('appEngineSdkRoot')) {
println "'appEngineSdkRoot' must be set in gradle.properties"
}
System.setProperty('appengine.sdk.root', project.property('appEngineSdkRoot'))
File explodedWar = new File(project.buildDir, "gae-exploded")
project.task('gaeDeploy') << {
AppCfg.main("update", explodedWar.toString())
}
project.gaeDeploy.dependsOn project.war
project.war.doLast {
ant.unzip(src: project.war.archivePath, dest: explodedWar)
}
}
}

View File

@ -0,0 +1 @@
implementation-class=gae.GaePlugin

40
samples/gae/gae.gradle Normal file
View File

@ -0,0 +1,40 @@
apply plugin: 'war'
apply plugin: 'jetty'
apply plugin: 'gae'
gaeVersion="1.3.5"
repositories {
// Hibernate Validator
mavenRepo name: 'JBoss', urls: 'https://repository.jboss.org/nexus/content/repositories/releases'
// GAE Jars
mavenRepo name: 'GAE', urls:'http://maven-gae-plugin.googlecode.com/svn/repository'
}
// Remove logback as it causes security issues with GAE.
configurations.runtime.exclude(group: 'ch.qos.logback')
dependencies {
providedCompile 'javax.servlet:servlet-api:2.5@jar',
"com.google.appengine:appengine-api-1.0-sdk:$gaeVersion"
compile project(':spring-security-core'),
project(':spring-security-web'),
"org.springframework:spring-beans:$springVersion",
"org.springframework:spring-web:$springVersion",
"org.springframework:spring-webmvc:$springVersion",
"org.springframework:spring-context:$springVersion",
"org.springframework:spring-context-support:$springVersion",
'javax.validation:validation-api:1.0.0.GA',
'org.hibernate:hibernate-validator:4.1.0.Final',
"org.slf4j:slf4j-api:$slf4jVersion"
runtime project(':spring-security-config'),
"org.slf4j:jcl-over-slf4j:$slf4jVersion",
"org.slf4j:slf4j-jdk14:$slf4jVersion"
testCompile "com.google.appengine:appengine-testing:$gaeVersion"
testRuntime "com.google.appengine:appengine-api-labs:$gaeVersion",
"com.google.appengine:appengine-api-stubs:$gaeVersion"
}

View File

@ -0,0 +1,33 @@
package samples.gae.security;
import org.springframework.security.core.GrantedAuthority;
/**
* @author Luke Taylor
*/
public enum AppRole implements GrantedAuthority {
ADMIN (0),
NEW_USER (1),
USER (2);
private int bit;
/**
* Creates an authority with a specific bit representation. It's important that this doesn't
* change as it will be used in the database. The enum ordinal is less reliable as the enum may be
* reordered or have new roles inserted which would change the ordinal values.
*
* @param bit the permission bit which will represent this authority in the datastore.
*/
AppRole(int bit) {
this.bit = bit;
}
public int getBit() {
return bit;
}
public String getAuthority() {
return toString();
}
}

View File

@ -0,0 +1,88 @@
package samples.gae.security;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserServiceFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.util.Assert;
import org.springframework.web.filter.GenericFilterBean;
/**
* @author Luke Taylor
*/
public class GaeAuthenticationFilter extends GenericFilterBean {
private static final String REGISTRATION_URL = "/register.htm";
private final Logger logger = LoggerFactory.getLogger(getClass());
private AuthenticationDetailsSource ads = new WebAuthenticationDetailsSource();
private AuthenticationManager authenticationManager;
private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
User googleUser = UserServiceFactory.getUserService().getCurrentUser();
if (googleUser != null) {
logger.debug("Currently logged on to GAE as user " + googleUser);
logger.debug("Authenticating to Spring Security");
// User has returned after authenticating via GAE. Need to authenticate through Spring Security.
PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(googleUser, null);
token.setDetails(ads.buildDetails(request));
try {
authentication = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (AuthenticationException e) {
failureHandler.onAuthenticationFailure((HttpServletRequest)request, (HttpServletResponse)response, e);
return;
}
}
}
// A new user has to register with the app before doing anything else
if (authentication != null && authentication.getAuthorities().contains(AppRole.NEW_USER)
&& !((HttpServletRequest)request).getRequestURI().endsWith(REGISTRATION_URL)) {
logger.debug("New user authenticated. Redirecting to registration page");
((HttpServletResponse) response).sendRedirect(REGISTRATION_URL);
return;
}
chain.doFilter(request, response);
}
@Override
public void afterPropertiesSet() throws ServletException {
Assert.notNull(authenticationManager, "AuthenticationManager must be set");
}
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
public void setFailureHandler(AuthenticationFailureHandler failureHandler) {
this.failureHandler = failureHandler;
}
}

View File

@ -0,0 +1,62 @@
package samples.gae.security;
import java.util.Collection;
import java.util.HashSet;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import samples.gae.users.GaeUser;
/**
* Authentication object representing a fully-authenticated user.
*
* @author Luke Taylor
*/
public class GaeUserAuthentication implements Authentication {
private final GaeUser principal;
private final Object details;
private boolean authenticated;
public GaeUserAuthentication(GaeUser principal, Object details) {
this.principal = principal;
this.details = details;
authenticated = true;
}
public Collection<GrantedAuthority> getAuthorities() {
return new HashSet<GrantedAuthority>(principal.getAuthorities());
}
public Object getCredentials() {
throw new UnsupportedOperationException();
}
public Object getDetails() {
return null;
}
public Object getPrincipal() {
return principal;
}
public boolean isAuthenticated() {
return authenticated;
}
public void setAuthenticated(boolean isAuthenticated) {
authenticated = isAuthenticated;
}
public String getName() {
return principal.getUserId();
}
@Override
public String toString() {
return "GaeUserAuthentication{" +
"principal=" + principal +
", details=" + details +
", authenticated=" + authenticated +
'}';
}
}

View File

@ -0,0 +1,23 @@
package samples.gae.security;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
public class GoogleAccountsAuthenticationEntryPoint implements AuthenticationEntryPoint {
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
UserService userService = UserServiceFactory.getUserService();
response.sendRedirect(userService.createLoginURL(request.getRequestURI()));
}
}

View File

@ -0,0 +1,64 @@
package samples.gae.security;
import com.google.appengine.api.users.User;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import samples.gae.users.GaeUser;
import samples.gae.users.UserRegistry;
/**
* A simple authentication provider which interacts with {@code User} returned by the GAE {@code UserService},
* and also the local persistent {@code UserRegistry} to build an application user principal.
* <p>
* If the user has been authenticated through google accounts, it will check if they are already registered
* and either load the existing user information or assign them a temporary identity with limited access until they
* have registered.
* <p>
* If the account has been disabled, a {@code DisabledException} will be raised.
*
* @author Luke Taylor
*/
public class GoogleAccountsAuthenticationProvider implements AuthenticationProvider, MessageSourceAware {
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private UserRegistry userRegistry;
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
User googleUser = (User) authentication.getPrincipal();
GaeUser user = userRegistry.findUser(googleUser.getUserId());
if (user == null) {
// User not in registry. Needs to register
user = new GaeUser(googleUser.getUserId(), googleUser.getNickname(), googleUser.getEmail());
}
if (!user.isEnabled()) {
throw new DisabledException("Account is disabled");
}
return new GaeUserAuthentication(user, authentication.getDetails());
}
/**
* Indicate that this provider only supports PreAuthenticatedAuthenticationToken (sub)classes.
*/
public final boolean supports(Class<?> authentication) {
return PreAuthenticatedAuthenticationToken.class.isAssignableFrom(authentication);
}
public void setUserRegistry(UserRegistry userRegistry) {
this.userRegistry = userRegistry;
}
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
}

View File

@ -0,0 +1,93 @@
package samples.gae.users;
import java.util.Collection;
import java.util.EnumSet;
import java.util.Set;
import com.google.appengine.api.datastore.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.GrantedAuthority;
import samples.gae.security.AppRole;
/**
* UserRegistry implementation which uses GAE's low-level Datastore APIs.
*
* @author Luke Taylor
*/
public class GaeDataStoreUserRegistry implements UserRegistry {
private final Logger logger = LoggerFactory.getLogger(getClass());
private static final String USER_TYPE = "GaeUser";
private static final String USER_FORENAME = "forename";
private static final String USER_SURNAME = "surname";
private static final String USER_NICKNAME = "nickname";
private static final String USER_EMAIL = "email";
private static final String USER_ENABLED = "enabled";
private static final String USER_AUTHORITIES = "authorities";
public GaeUser findUser(String userId) {
Key key = KeyFactory.createKey(USER_TYPE, userId);
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
try {
Entity user = datastore.get(key);
long binaryAuthorities = (Long)user.getProperty(USER_AUTHORITIES);
Set<AppRole> roles = EnumSet.noneOf(AppRole.class);
for (AppRole r : AppRole.values()) {
if ((binaryAuthorities & (1 << r.getBit())) != 0) {
roles.add(r);
}
}
GaeUser gaeUser = new GaeUser(
user.getKey().getName(),
(String)user.getProperty(USER_NICKNAME),
(String)user.getProperty(USER_EMAIL),
(String)user.getProperty(USER_FORENAME),
(String)user.getProperty(USER_SURNAME),
roles,
(Boolean)user.getProperty(USER_ENABLED));
return gaeUser;
} catch (EntityNotFoundException e) {
logger.debug(userId + " not found in datastore");
return null;
}
}
public void registerUser(GaeUser newUser) {
logger.debug("Attempting to create new user " + newUser);
Key key = KeyFactory.createKey(USER_TYPE, newUser.getUserId());
Entity user = new Entity(key);
user.setProperty(USER_EMAIL, newUser.getEmail());
user.setProperty(USER_NICKNAME, newUser.getNickname());
user.setProperty(USER_FORENAME, newUser.getForename());
user.setProperty(USER_SURNAME, newUser.getSurname());
user.setUnindexedProperty(USER_ENABLED, newUser.isEnabled());
Collection<? extends GrantedAuthority> roles = newUser.getAuthorities();
long binaryAuthorities = 0;
for (GrantedAuthority r : roles) {
binaryAuthorities |= 1 << ((AppRole)r).getBit();
}
user.setUnindexedProperty(USER_AUTHORITIES, binaryAuthorities);
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
datastore.put(user);
}
public void removeUser(String userId) {
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
Key key = KeyFactory.createKey(USER_TYPE, userId);
datastore.delete(key);
}
}

View File

@ -0,0 +1,92 @@
package samples.gae.users;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.Set;
import org.springframework.security.core.GrantedAuthority;
import samples.gae.security.AppRole;
/**
* Custom user object for the application.
*
* @author Luke Taylor
*/
public class GaeUser implements Serializable {
private final String userId;
private final String email;
private final String nickname;
private final String forename;
private final String surname;
private final Set<AppRole> authorities;
private final boolean enabled;
/**
* Pre-registration constructor.
*
* Assigns the user the "NEW_USER" role only.
*/
public GaeUser(String userId, String nickname, String email) {
this.userId = userId;
this.nickname = nickname;
this.authorities = EnumSet.of(AppRole.NEW_USER);
this.forename = null;
this.surname = null;
this.email = email;
this.enabled = true;
}
/**
* Post-registration constructor
*/
public GaeUser(String userId, String nickname, String email, String forename, String surname, Set<AppRole> authorities, boolean enabled) {
this.userId = userId;
this.nickname = nickname;
this.email = email;
this.authorities = authorities;
this.forename = forename;
this.surname = surname;
this.enabled= enabled;
}
public String getUserId() {
return userId;
}
public String getNickname() {
return nickname;
}
public String getEmail() {
return email;
}
public String getForename() {
return forename;
}
public String getSurname() {
return surname;
}
public boolean isEnabled() {
return enabled;
}
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String toString() {
return "GaeUser{" +
"userId='" + userId + '\'' +
", nickname='" + nickname + '\'' +
", forename='" + forename + '\'' +
", surname='" + surname + '\'' +
", authorities=" + authorities +
'}';
}
}

View File

@ -0,0 +1,34 @@
package samples.gae.users;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
/**
* @author Luke Taylor
*/
public class InMemoryUserRegistry implements UserRegistry {
private final Logger logger = LoggerFactory.getLogger(getClass());
private Map<String, GaeUser> users = Collections.synchronizedMap(new HashMap<String, GaeUser>());
public GaeUser findUser(String userId) {
return users.get(userId);
}
public void registerUser(GaeUser newUser) {
logger.debug("Attempting to create new user " + newUser);
Assert.state(!users.containsKey(newUser.getUserId()));
users.put(newUser.getUserId(), newUser);
}
public void removeUser(String userId) {
users.remove(userId);
}
}

View File

@ -0,0 +1,18 @@
package samples.gae.users;
import com.google.appengine.api.datastore.EntityNotFoundException;
/**
*
* Service used to maintain a list of users who are registered with the application.
*
* @author Luke Taylor
*/
public interface UserRegistry {
GaeUser findUser(String userId);
void registerUser(GaeUser newUser);
void removeUser(String userId);
}

View File

@ -0,0 +1,25 @@
package samples.gae.validation;
import static java.lang.annotation.ElementType.*;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import org.hibernate.validator.constraints.NotBlank;
/**
* @author Luke Taylor
*/
@Target( { METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ForenameValidator.class)
public @interface Forename {
String message() default "{samples.gae.forename}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,18 @@
package samples.gae.validation;
import java.util.regex.Pattern;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* @author Luke Taylor
*/
public class ForenameValidator implements ConstraintValidator<Forename, String> {
private static final Pattern VALID = Pattern.compile("[\\p{L}'\\-,.]+") ;
public void initialize(Forename constraintAnnotation) {}
public boolean isValid(String value, ConstraintValidatorContext context) {
return VALID.matcher(value).matches();
}
}

View File

@ -0,0 +1,25 @@
package samples.gae.validation;
import static java.lang.annotation.ElementType.*;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import org.hibernate.validator.constraints.NotBlank;
/**
* @author Luke Taylor
*/
@Target( { METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = SurnameValidator.class)
public @interface Surname {
String message() default "{samples.gae.surname}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,18 @@
package samples.gae.validation;
import java.util.regex.Pattern;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* @author Luke Taylor
*/
public class SurnameValidator implements ConstraintValidator<Surname, String> {
private static final Pattern VALID = Pattern.compile("[\\p{L}'\\-,.]+") ;
public void initialize(Surname constraintAnnotation) {}
public boolean isValid(String value, ConstraintValidatorContext context) {
return VALID.matcher(value).matches();
}
}

View File

@ -0,0 +1,48 @@
package samples.gae.web;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.appengine.api.users.UserServiceFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
*
* @author Luke Taylor
*
*/
@Controller
public class GaeAppController {
@RequestMapping(value = "/", method= RequestMethod.GET)
public String landing() {
return "landing";
}
@RequestMapping(value = "/home.htm", method= RequestMethod.GET)
public String home() {
return "home";
}
@RequestMapping(value = "/disabled.htm", method= RequestMethod.GET)
public String disabled() {
return "disabled";
}
@RequestMapping(value = "/logout.htm", method= RequestMethod.GET)
public void logout(HttpServletRequest request, HttpServletResponse response) throws IOException {
request.getSession().invalidate();
String logoutUrl = UserServiceFactory.getUserService().createLogoutURL("/");
response.sendRedirect(logoutUrl);
}
@RequestMapping(value = "/loggedout.htm", method= RequestMethod.GET)
public String loggedOut() {
return "loggedout";
}
}

View File

@ -0,0 +1,61 @@
package samples.gae.web;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import com.google.appengine.api.users.UserServiceFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import samples.gae.security.AppRole;
import samples.gae.security.GaeUserAuthentication;
import samples.gae.users.GaeUser;
import samples.gae.users.UserRegistry;
/**
* @author Luke Taylor
*/
@Controller
@RequestMapping(value="/register.htm")
public class RegistrationController {
@Autowired
private UserRegistry registry;
@RequestMapping(method= RequestMethod.GET)
public RegistrationForm registrationForm() {
return new RegistrationForm();
}
@RequestMapping(method = RequestMethod.POST)
public String register(@Valid RegistrationForm form, BindingResult result) {
if (result.hasErrors()) {
return null;
}
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
GaeUser currentUser = (GaeUser)authentication.getPrincipal();
Set<AppRole> roles = EnumSet.of(AppRole.USER);
if (UserServiceFactory.getUserService().isUserAdmin()) {
roles.add(AppRole.ADMIN);
}
GaeUser user = new GaeUser(currentUser.getUserId(), currentUser.getNickname(), currentUser.getEmail(),
form.getForename(), form.getSurname(), roles, true);
registry.registerUser(user);
// Update the context with the full authentication
SecurityContextHolder.getContext().setAuthentication(new GaeUserAuthentication(user, authentication.getDetails()));
return "redirect:/home.htm";
}
}

View File

@ -0,0 +1,31 @@
package samples.gae.web;
import org.hibernate.validator.constraints.NotBlank;
import samples.gae.validation.Forename;
import samples.gae.validation.Surname;
/**
* @author Luke Taylor
*/
public class RegistrationForm {
@Forename
private String forename;
@Surname
private String surname;
public String getForename() {
return forename;
}
public void setForename(String forename) {
this.forename = forename;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<application>gaespringsec</application>
<version>1</version>
<sessions-enabled>true</sessions-enabled>
<system-properties>
<property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
</system-properties>
</appengine-web-app>

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<b:beans xmlns="http://www.springframework.org/schema/security"
xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<http pattern="/static/**" security="none" />
<http pattern="/favicon.ico" security="none" />
<http pattern="/_ah/resources**" security="none" />
<http use-expressions="true" entry-point-ref="gaeEntryPoint">
<intercept-url pattern="/" access="permitAll" />
<intercept-url pattern="/_ah/login**" access="permitAll" />
<intercept-url pattern="/_ah/admin**" access="permitAll" />
<intercept-url pattern="/logout.htm" access="permitAll" />
<intercept-url pattern="/register.htm*" access="hasRole('NEW_USER')" />
<intercept-url pattern="/**" access="hasRole('USER')" />
<custom-filter position="PRE_AUTH_FILTER" ref="gaeFilter" />
</http>
<b:bean id="gaeEntryPoint" class="samples.gae.security.GoogleAccountsAuthenticationEntryPoint" />
<b:bean id="gaeFilter" class="samples.gae.security.GaeAuthenticationFilter">
<b:property name="authenticationManager" ref="authenticationManager"/>
<b:property name="failureHandler">
<b:bean class="org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler">
<b:property name="exceptionMappings">
<b:map>
<b:entry key="org.springframework.security.authentication.DisabledException" value="/disabled.htm" />
</b:map>
</b:property>
</b:bean>
</b:property>
</b:bean>
<authentication-manager alias="authenticationManager">
<authentication-provider ref="gaeAuthenticationProvider"/>
</authentication-manager>
<b:bean id="gaeAuthenticationProvider" class="samples.gae.security.GoogleAccountsAuthenticationProvider">
<b:property name="userRegistry" ref="userRegistry" />
</b:bean>
<b:bean id="userRegistry" class="samples.gae.users.GaeDataStoreUserRegistry" />
</b:beans>

View File

@ -0,0 +1,2 @@
samples.gae.forename=Must be a valid forename
samples.gae.surname=Must be a valid surname

View File

@ -0,0 +1,25 @@
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<!-- Enables JSR-303 -->
<mvc:annotation-driven/>
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
<context:component-scan base-package="samples.gae"/>
<context:annotation-config />
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="messages"/>
</bean>
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>

View File

@ -0,0 +1,16 @@
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<%@page session="false" %>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="/static/css/gae.css" type="text/css" />
<title>Disabled Account</title></head>
<body>
<div id="content">
<p>Sorry, it looks like your account has been disabled for some reason...</p>
</div>
</body>
</html>

View File

@ -0,0 +1,26 @@
<%@ page import="com.google.appengine.api.users.UserServiceFactory" %>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="/static/css/gae.css" type="text/css" />
<title>Home Page</title>
</head>
<body>
<div id="content">
<h3>The Home Page</h3>
<p>Welcome back <sec:authentication property="principal.nickname"/>.</p>
<p>
You can get to this page if you have authenticated and are a registered user.
You are registered as
<sec:authentication property="principal.forename"/> <sec:authentication property="principal.surname"/>.
</p>
<p>
<a href="/logout.htm">Logout</a>.
</p>
</div>
</body>
</html>

View File

@ -0,0 +1,33 @@
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@page session="false" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="/static/css/gae.css" type="text/css" />
<title>Spring Security GAE Sample</title>
</head>
<body>
<div id="content">
<h3>Spring Security GAE Application</h3>
<p>
This application demonstrates the integration of Spring Security
with the services provided by Google App Engine. It shows how to:
<ul>
<li>Authenticate using Google Accounts.</li>
<li>Implement "on&ndash;demand" authentication when a user accesses a secured resource.</li>
<li>Supplement the information from Google Accounts with application&ndash;specific roles.</li>
<li>Store user account data in an App Engine datastore using the native API.</li>
<li>Setup access-control restrictions based on the roles assigned to users.</li>
<li>Disable the accounts of specfic users to prevent access.</li>
</ul>
</p>
<p>
Go to the <a href="/home.htm">home page</a>.
</p>
</div>
</body>
</html>

View File

@ -0,0 +1,17 @@
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@page session="false" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="/static/css/gae.css" type="text/css" />
<title>Spring Security GAE Sample</title>
</head>
<body>
<div id="content">
<p>You've been logged out of the application. <a href="/home.htm">Log back in</a>.</p>
</div>
</body>
</html>

View File

@ -0,0 +1,40 @@
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="/static/css/gae.css" type="text/css" />
<title>Registration</title>
</head>
<body>
<div id="content">
<p>
Welcome to the Spring Security GAE sample application, <sec:authentication property="principal.nickname" />.
Please enter your registration details in order to use the application.
</p>
<p>
The data you enter here will be registered in the application's GAE data store, keyed under your unique
Google Accounts identifier. It doesn't have to be accurate. When you log in again, the information will be automatically
retrieved.
</p>
<form:form id="register" method="post" modelAttribute="registrationForm">
<fieldset>
<form:label path="forename">
Forename:
</form:label> <form:errors path="forename" cssClass="fieldError" /><br />
<form:input path="forename" /> <br />
<form:label path="surname">
Surname:
</form:label><form:errors path="surname" cssClass="fieldError" /> <br />
<form:input path="surname" /><br />
</fieldset>
<input type="submit" value="Register">
</form:form>
</body>
</div>
</html>

View File

@ -0,0 +1,4 @@
.level = INFO
org.springframework.level = FINE
org.springframework.security.level = FINER

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/applicationContext-security.xml
</param-value>
</context-param>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>gae</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/gae-servlet.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>gae</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>gae</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>
</web-app>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,26 @@
body {
font-family:"Palatino Linotype","Book Antiqua",Palatino,serif;
}
#content {
margin: 5em auto;
width: 40em;
}
form {
width: 25em;
margin: 0 2em;
}
form fieldset {
margin-bottom: 0.5em;
}
fieldset input {
margin: 0.6em 0;
}
.fieldError {
color: red;
}

View File

@ -0,0 +1,23 @@
package samples.gae.security;
import static org.junit.Assert.*;
import static samples.gae.security.AppRole.*;
import org.junit.Test;
import org.springframework.security.core.GrantedAuthority;
/**
* @author Luke Taylor
*/
public class AppRoleTests {
@Test
public void getAuthorityReturnsRoleName() {
GrantedAuthority admin = ADMIN;
assertEquals("ADMIN", admin.getAuthority());
}
}

View File

@ -0,0 +1,53 @@
package samples.gae.users;
import static org.junit.Assert.assertEquals;
import java.util.EnumSet;
import java.util.Set;
import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import samples.gae.security.AppRole;
/**
* @author Luke Taylor
*/
public class GaeDataStoreUserRegistryTests {
private final LocalServiceTestHelper helper =
new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig());
@Before
public void setUp() throws Exception {
helper.setUp();
}
@After
public void tearDown() throws Exception {
helper.tearDown();
}
@Test
public void correctDataIsRetrievedAfterInsert() {
GaeDataStoreUserRegistry registry = new GaeDataStoreUserRegistry();
Set<AppRole> roles = EnumSet.of(AppRole.ADMIN, AppRole.USER);
String userId = "someUserId";
GaeUser origUser = new GaeUser(userId, "nick", "nick@blah.com", "Forename", "Surname", roles, true);
registry.registerUser(origUser);
GaeUser loadedUser = registry.findUser(userId);
assertEquals(loadedUser.getUserId(), origUser.getUserId());
assertEquals(true, loadedUser.isEnabled());
assertEquals(roles, loadedUser.getAuthorities());
assertEquals("nick", loadedUser.getNickname());
assertEquals("nick@blah.com", loadedUser.getEmail());
assertEquals("Forename", loadedUser.getForename());
assertEquals("Surname", loadedUser.getSurname());
}
}

View File

@ -14,7 +14,8 @@ def String[] samples = [
'tutorial',
'contacts',
'openid',
'aspectj'
'aspectj',
'gae'
]
def String[] docs = [

View File

@ -1,7 +1,6 @@
package org.springframework.security.web.authentication.preauth;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
@ -10,7 +9,6 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.security.authentication.AuthenticationDetailsSource;
@ -55,7 +53,7 @@ import org.springframework.web.filter.GenericFilterBean;
* @since 2.0
*/
public abstract class AbstractPreAuthenticatedProcessingFilter extends GenericFilterBean implements
InitializingBean, ApplicationEventPublisherAware {
ApplicationEventPublisherAware {
private ApplicationEventPublisher eventPublisher = null;
private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource();