39 changed files with 1184 additions and 4 deletions
@ -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) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1 @@ |
|||||||
|
implementation-class=gae.GaePlugin |
||||||
@ -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" |
||||||
|
|
||||||
|
} |
||||||
@ -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(); |
||||||
|
} |
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
@ -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 + |
||||||
|
'}'; |
||||||
|
} |
||||||
|
} |
||||||
@ -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())); |
||||||
|
} |
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
@ -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 + |
||||||
|
'}'; |
||||||
|
} |
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
@ -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 {}; |
||||||
|
} |
||||||
@ -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(); |
||||||
|
} |
||||||
|
} |
||||||
@ -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 {}; |
||||||
|
} |
||||||
@ -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(); |
||||||
|
} |
||||||
|
} |
||||||
@ -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"; |
||||||
|
} |
||||||
|
} |
||||||
@ -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"; |
||||||
|
} |
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
@ -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> |
||||||
@ -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> |
||||||
@ -0,0 +1,2 @@ |
|||||||
|
samples.gae.forename=Must be a valid forename |
||||||
|
samples.gae.surname=Must be a valid surname |
||||||
@ -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> |
||||||
@ -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> |
||||||
@ -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> |
||||||
@ -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–demand" authentication when a user accesses a secured resource.</li> |
||||||
|
<li>Supplement the information from Google Accounts with application–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> |
||||||
@ -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> |
||||||
@ -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> |
||||||
@ -0,0 +1,4 @@ |
|||||||
|
.level = INFO |
||||||
|
|
||||||
|
org.springframework.level = FINE |
||||||
|
org.springframework.security.level = FINER |
||||||
@ -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> |
||||||
|
After Width: | Height: | Size: 1.1 KiB |
@ -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; |
||||||
|
} |
||||||
@ -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()); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
@ -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()); |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue