瀏覽代碼

SEC-1490: Code for GAE Sample webapp

Luke Taylor 15 年之前
父節點
當前提交
5d35919ca3
共有 39 個文件被更改,包括 1184 次插入4 次删除
  1. 2 0
      .gitignore
  2. 3 0
      build.gradle
  3. 7 0
      buildSrc/build.gradle
  4. 26 0
      buildSrc/src/main/groovy/gae/GaePlugin.groovy
  5. 1 0
      buildSrc/src/main/resources/META-INF/gradle-plugins/gae.properties
  6. 40 0
      samples/gae/gae.gradle
  7. 33 0
      samples/gae/src/main/java/samples/gae/security/AppRole.java
  8. 88 0
      samples/gae/src/main/java/samples/gae/security/GaeAuthenticationFilter.java
  9. 62 0
      samples/gae/src/main/java/samples/gae/security/GaeUserAuthentication.java
  10. 23 0
      samples/gae/src/main/java/samples/gae/security/GoogleAccountsAuthenticationEntryPoint.java
  11. 64 0
      samples/gae/src/main/java/samples/gae/security/GoogleAccountsAuthenticationProvider.java
  12. 93 0
      samples/gae/src/main/java/samples/gae/users/GaeDataStoreUserRegistry.java
  13. 92 0
      samples/gae/src/main/java/samples/gae/users/GaeUser.java
  14. 34 0
      samples/gae/src/main/java/samples/gae/users/InMemoryUserRegistry.java
  15. 18 0
      samples/gae/src/main/java/samples/gae/users/UserRegistry.java
  16. 25 0
      samples/gae/src/main/java/samples/gae/validation/Forename.java
  17. 18 0
      samples/gae/src/main/java/samples/gae/validation/ForenameValidator.java
  18. 25 0
      samples/gae/src/main/java/samples/gae/validation/Surname.java
  19. 18 0
      samples/gae/src/main/java/samples/gae/validation/SurnameValidator.java
  20. 48 0
      samples/gae/src/main/java/samples/gae/web/GaeAppController.java
  21. 61 0
      samples/gae/src/main/java/samples/gae/web/RegistrationController.java
  22. 31 0
      samples/gae/src/main/java/samples/gae/web/RegistrationForm.java
  23. 10 0
      samples/gae/src/main/webapp/WEB-INF/appengine-web.xml
  24. 48 0
      samples/gae/src/main/webapp/WEB-INF/applicationContext-security.xml
  25. 2 0
      samples/gae/src/main/webapp/WEB-INF/classes/ValidationMessages.properties
  26. 25 0
      samples/gae/src/main/webapp/WEB-INF/gae-servlet.xml
  27. 16 0
      samples/gae/src/main/webapp/WEB-INF/jsp/disabled.jsp
  28. 26 0
      samples/gae/src/main/webapp/WEB-INF/jsp/home.jsp
  29. 33 0
      samples/gae/src/main/webapp/WEB-INF/jsp/landing.jsp
  30. 17 0
      samples/gae/src/main/webapp/WEB-INF/jsp/loggedout.jsp
  31. 40 0
      samples/gae/src/main/webapp/WEB-INF/jsp/register.jsp
  32. 4 0
      samples/gae/src/main/webapp/WEB-INF/logging.properties
  33. 46 0
      samples/gae/src/main/webapp/WEB-INF/web.xml
  34. 二進制
      samples/gae/src/main/webapp/favicon.ico
  35. 26 0
      samples/gae/src/main/webapp/static/css/gae.css
  36. 23 0
      samples/gae/src/test/java/samples/gae/security/AppRoleTests.java
  37. 53 0
      samples/gae/src/test/java/samples/gae/users/GaeDataStoreUserRegistryTests.java
  38. 2 1
      settings.gradle
  39. 1 3
      web/src/main/java/org/springframework/security/web/authentication/preauth/AbstractPreAuthenticatedProcessingFilter.java

+ 2 - 0
.gitignore

@@ -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

+ 3 - 0
build.gradle

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

+ 7 - 0
buildSrc/build.gradle

@@ -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'

+ 26 - 0
buildSrc/src/main/groovy/gae/GaePlugin.groovy

@@ -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)
+        }
+    }
+}

+ 1 - 0
buildSrc/src/main/resources/META-INF/gradle-plugins/gae.properties

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

+ 40 - 0
samples/gae/gae.gradle

@@ -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"
+
+}

+ 33 - 0
samples/gae/src/main/java/samples/gae/security/AppRole.java

@@ -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();
+    }
+}

+ 88 - 0
samples/gae/src/main/java/samples/gae/security/GaeAuthenticationFilter.java

@@ -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;
+    }
+}

+ 62 - 0
samples/gae/src/main/java/samples/gae/security/GaeUserAuthentication.java

@@ -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 +
+                '}';
+    }
+}

+ 23 - 0
samples/gae/src/main/java/samples/gae/security/GoogleAccountsAuthenticationEntryPoint.java

@@ -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()));
+    }
+}

+ 64 - 0
samples/gae/src/main/java/samples/gae/security/GoogleAccountsAuthenticationProvider.java

@@ -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);
+    }
+}

+ 93 - 0
samples/gae/src/main/java/samples/gae/users/GaeDataStoreUserRegistry.java

@@ -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);
+    }
+}

+ 92 - 0
samples/gae/src/main/java/samples/gae/users/GaeUser.java

@@ -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 +
+                '}';
+    }
+}

+ 34 - 0
samples/gae/src/main/java/samples/gae/users/InMemoryUserRegistry.java

@@ -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);
+    }
+}

+ 18 - 0
samples/gae/src/main/java/samples/gae/users/UserRegistry.java

@@ -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);
+}

+ 25 - 0
samples/gae/src/main/java/samples/gae/validation/Forename.java

@@ -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 {};
+}

+ 18 - 0
samples/gae/src/main/java/samples/gae/validation/ForenameValidator.java

@@ -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();
+    }
+}

+ 25 - 0
samples/gae/src/main/java/samples/gae/validation/Surname.java

@@ -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 {};
+}

+ 18 - 0
samples/gae/src/main/java/samples/gae/validation/SurnameValidator.java

@@ -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();
+    }
+}

+ 48 - 0
samples/gae/src/main/java/samples/gae/web/GaeAppController.java

@@ -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";
+    }
+}

+ 61 - 0
samples/gae/src/main/java/samples/gae/web/RegistrationController.java

@@ -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";
+	}
+}

+ 31 - 0
samples/gae/src/main/java/samples/gae/web/RegistrationForm.java

@@ -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;
+    }
+}

+ 10 - 0
samples/gae/src/main/webapp/WEB-INF/appengine-web.xml

@@ -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>

+ 48 - 0
samples/gae/src/main/webapp/WEB-INF/applicationContext-security.xml

@@ -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>

+ 2 - 0
samples/gae/src/main/webapp/WEB-INF/classes/ValidationMessages.properties

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

+ 25 - 0
samples/gae/src/main/webapp/WEB-INF/gae-servlet.xml

@@ -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>

+ 16 - 0
samples/gae/src/main/webapp/WEB-INF/jsp/disabled.jsp

@@ -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>

+ 26 - 0
samples/gae/src/main/webapp/WEB-INF/jsp/home.jsp

@@ -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>

+ 33 - 0
samples/gae/src/main/webapp/WEB-INF/jsp/landing.jsp

@@ -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>

+ 17 - 0
samples/gae/src/main/webapp/WEB-INF/jsp/loggedout.jsp

@@ -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>

+ 40 - 0
samples/gae/src/main/webapp/WEB-INF/jsp/register.jsp

@@ -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>

+ 4 - 0
samples/gae/src/main/webapp/WEB-INF/logging.properties

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

+ 46 - 0
samples/gae/src/main/webapp/WEB-INF/web.xml

@@ -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>

二進制
samples/gae/src/main/webapp/favicon.ico


+ 26 - 0
samples/gae/src/main/webapp/static/css/gae.css

@@ -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;
+}

+ 23 - 0
samples/gae/src/test/java/samples/gae/security/AppRoleTests.java

@@ -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());
+    }
+
+
+
+}

+ 53 - 0
samples/gae/src/test/java/samples/gae/users/GaeDataStoreUserRegistryTests.java

@@ -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());
+    }
+}

+ 2 - 1
settings.gradle

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

+ 1 - 3
web/src/main/java/org/springframework/security/web/authentication/preauth/AbstractPreAuthenticatedProcessingFilter.java

@@ -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();