Remember-Me Authentication With Spring Security on Google App Engine
Spring Security has built-in “remember-me” authentication capability to remember the identity of a user (principle) between session.
However, default implementations for “persistent token” only support in-memory (for testing) and JDBC. I wanted to implement this with App Engine using Objectify for persistence.
All it takes is to implement a custom PersistentTokenRepository and configure Spring Security to use it. This post assumes that Spring Security has been configured to work correctly with App Engine.
RememberMeToken Entity
Define an Objectify entity class that will be used to store token data.
Converting Between PersistentRememberMeToken And RememberMeToken
Since different objects are used during runtime and persistence, I use Spring’s generic conversion service to help with type conversion.
12345678910111213141516171819202122232425
publicclassRememberMeConverterimplementsGenericConverter{@OverridepublicSet<ConvertiblePair>getConvertibleTypes(){returnof(newConvertiblePair(RememberMeToken.class,PersistentRememberMeToken.class),newConvertiblePair(PersistentRememberMeToken.class,RememberMeToken.class));}@OverridepublicObjectconvert(Objectsource,TypeDescriptorsourceType,TypeDescriptortargetType){if(sourceType.getType().equals(RememberMeToken.class)){// RememberMeToken to PersistentRememberMeTokenRememberMeTokenfrom=(RememberMeToken)source;returnnewPersistentRememberMeToken(from.getUsername(),from.getSeries(),from.getTokenValue(),from.getDate());}else{// PersistentRememberMeToken to RememberMeTokenPersistentRememberMeTokenfrom=(PersistentRememberMeToken)source;returnnewRememberMeToken(from.getUsername(),from.getSeries(),from.getTokenValue(),from.getDate());}}}
Create ObjectifyPersistentTokenRepository as a PersistentTokenRepository
This PersistentTokenRepository implementation uses a conversion service that has a RememberMeConverter registered and Objectify for persistence.
publicclassObjectifyPersistentTokenRepositoryimplementsPersistentTokenRepository{privateConversionServiceconversionService;publicObjectifyPersistentTokenRepository(ConversionServiceconversionService){this.conversionService=conversionService;}@OverridepublicvoidcreateNewToken(PersistentRememberMeTokentoken){RememberMeTokencurrent=ofy().load().type(RememberMeToken.class).id(token.getSeries()).now();if(current!=null){thrownewDataIntegrityViolationException("Series Id '"+token.getSeries()+"' already exists!");}RememberMeTokenofyToken=conversionService.convert(token,RememberMeToken.class);ofy().save().entity(ofyToken);}@OverridepublicvoidupdateToken(Stringseries,StringtokenValue,DatelastUsed){PersistentRememberMeTokentoken=getTokenForSeries(series);RememberMeTokenofyToken=newRememberMeToken(token.getUsername(),series,tokenValue,newDate());ofy().save().entity(ofyToken);}@OverridepublicPersistentRememberMeTokengetTokenForSeries(StringseriesId){RememberMeTokenrememberMeToken=ofy().load().type(RememberMeToken.class).id(seriesId).now();if(rememberMeToken!=null){returnconversionService.convert(rememberMeToken,PersistentRememberMeToken.class);}else{returnnull;}}@OverridepublicvoidremoveUserTokens(Stringusername){ofy().delete().keys(ofy().load().type(RememberMeToken.class).filter("username",username).keys().list());}}
Spring Security Configuration
The final bit is to hook things up when configuring Spring Security. This can be done in the configure(HttpSecurity http) method if you are using Java configuration.
123456789101112131415161718192021
@Configuration@EnableWebMvcSecuritypublicclassSecurityConfigextendsWebSecurityConfigurerAdapter{@AutowiredprivateConversionServiceconversionService;@Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{http// common configuration.authorizeRequests().antMatchers("/").permitAll().anyRequest().authenticated().and().formLogin().loginPage("/login").permitAll().and().logout().logoutRequestMatcher(newAntPathRequestMatcher("/logout")).logoutSuccessUrl("/").permitAll()// use the custom persistent token repository.and().rememberMe().tokenRepository(newObjectifyPersistentTokenRepository(conversionService));}}
JSP Login Page
Just in case you are wondering how the HTML form fields look:
1234567891011121314151617181920212223242526
<formaction="${loginUrl}"method="POST"><c:iftest="${param.error != null}"><p> Invalid username and password.
</p></c:if><c:iftest="${param.logout != null}"><p> You have been logged out.
</p></c:if><p><labelfor="username">Username</label><inputtype="text"id="username"name="username"/></p><p><labelfor="password">Password</label><inputtype="password"id="password"name="password"/></p><p><inputtype="checkbox"name="remember-me"value="true"> Remember Me
</p><inputtype="hidden"name="${_csrf.parameterName}"value="${_csrf.token}"/><buttontype="submit"class="btn">Log in</button></form>