Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

We’re showing branches in this repository, but you can also compare across forks.

base fork: splix/grails-spring-security-facebook
base: 5b677a766e
...
head fork: splix/grails-spring-security-facebook
compare: f973154c98
  • 6 commits
  • 10 files changed
  • 0 commit comments
  • 1 contributor
3  SpringSecurityFacebookGrailsPlugin.groovy
View
@@ -23,7 +23,7 @@ import com.the6hours.grails.springsecurity.facebook.DefaultFacebookAuthDao
class SpringSecurityFacebookGrailsPlugin {
- String version = '0.7.4'
+ String version = '0.8'
String grailsVersion = '2.0.0 > *'
Map dependsOn = ['springSecurityCore': '1.2.7.2 > *']
@@ -95,6 +95,7 @@ class SpringSecurityFacebookGrailsPlugin {
authenticationManager = ref('authenticationManager')
facebookAuthUtils = ref('facebookAuthUtils')
logoutUrl = conf.logout.filterProcessesUrl
+ forceLoginParameter = conf.facebook.filter.forceLoginParameter
}
facebookAuthCookieLogout(FacebookAuthCookieLogoutHandler) {
facebookAuthUtils = ref('facebookAuthUtils')
42 docs/index.txt
View
@@ -5,7 +5,7 @@ h1. Information
Sources: [https://github.com/splix/grails-spring-security-facebook]
h2. Requirements:
- * grails 1.3.7
+ * grails 2.0.0
* spring-security-core plugin 1.1+
h2. How to install:
@@ -176,4 +176,44 @@ and then you can use Facebook API. For example you can fetch user's email on use
Facebook facebook = new FacebookTemplate(token.accessToken)
FacebookProfile fbProfile = facebook.userOperations().userProfile
String email = fbProfile.getEmail()
+{code}
+
+h1. Common issues
+
+h2. Please enable logging
+
+If you have troubles with plugin, please enabled logging, so you can see what's happening:
+{code}
+log4j = {
+ debug 'com.the6hours'
+}
+{code}
+
+h2. Can't find a cookie / user not authenticated by plugin
+
+Make sure that you're using some kind of domain, not @localhost@, because Facebook can't setup
+cookie for localhost. Avoid @.local@ domains as well.
+
+You can use domain like @myapp.dev@, that you should setup at file @/etc/hosts@ (or
+@C:/Windows/system32/etc/hosts@ on Windows), by adding following line:
+{code}
+127.0.0.1 myapp.dev
+{code}
+
+Notice: if you already have line starting with @127.0.0.1@, then put your @myapp.dev@ domain in this line too (it can
+have multiply associated domains)
+
+After that, you should configure your Grails app to use this domain, by adding following line into @Config.groovy@:
+{code}
+grails.serverURL = "http://myapp.dev:8080/${appName}"
+{code}
+
+Of course, you need to use this domain only for development, so put this configuration into @development@
+environment config:
+{code}
+environments {
+ development {
+ grails.serverURL = "http://myapp.dev:8080/${appName}"
+ }
+}
{code}
1  grails-app/conf/DefaultFacebookSecurityConfig.groovy
View
@@ -35,6 +35,7 @@ security {
filter {
processUrl = "/j_spring_facebook_security_check"
position = 720 //see SecurityFilterPosition
+ forceLoginParameter = 'j_spring_facebook_force'
}
beans {
118 src/groovy/com/the6hours/grails/springsecurity/facebook/DefaultFacebookAuthDao.groovy
View
@@ -10,6 +10,7 @@ import org.codehaus.groovy.grails.commons.GrailsApplication
import org.codehaus.groovy.grails.plugins.support.aware.GrailsApplicationAware
import org.apache.log4j.Logger
import org.springframework.security.core.userdetails.UserDetails
+import java.util.concurrent.TimeUnit
/**
* TODO
@@ -34,6 +35,17 @@ class DefaultFacebookAuthDao implements FacebookAuthDao<Object>, InitializingBea
def facebookAuthService
DomainsRelation domainsRelation = DomainsRelation.JoinedUser
+ Object getFacebookUser(Object user) {
+ if (domainsRelation == DomainsRelation.JoinedUser) {
+ return user?.getAt(connectionPropertyName)// load the User object to memory prevent LazyInitializationException
+ }
+ if (domainsRelation == DomainsRelation.SameObject) {
+ return user
+ }
+ log.error("Invalid domainsRelation value: $domainsRelation")
+ return user
+ }
+
Object findUser(long uid) {
if (facebookAuthService && facebookAuthService.respondsTo('findUser', Long)) {
return facebookAuthService.findUser(uid)
@@ -46,9 +58,7 @@ class DefaultFacebookAuthDao implements FacebookAuthDao<Object>, InitializingBea
def user = null
User.withTransaction { status ->
user = User.findWhere(uid: uid)
- if (domainsRelation == DomainsRelation.JoinedUser) {
- user?.getAt(connectionPropertyName)// load the User object to memory prevent LazyInitializationException
- }
+ getFacebookUser(user) // load the User object to memory prevent LazyInitializationException
}
return user
}
@@ -69,7 +79,10 @@ class DefaultFacebookAuthDao implements FacebookAuthDao<Object>, InitializingBea
def user = grailsApplication.getDomainClass(domainClassName).newInstance()
user.uid = token.uid
if (user.properties.containsKey('accessToken')) {
- user.accessToken = token.accessToken
+ user.accessToken = token.accessToken.accessToken
+ }
+ if (user.properties.containsKey('accessTokenExpires')) {
+ user.accessTokenExpires = token.accessToken.expireAt
}
def appUser
@@ -87,7 +100,7 @@ class DefaultFacebookAuthDao implements FacebookAuthDao<Object>, InitializingBea
facebookAuthService.prepopulateAppUser(appUser, token)
} else {
appUser[securityConf.userLookup.usernamePropertyName] = "facebook_$token.uid"
- appUser[securityConf.userLookup.passwordPropertyName] = token.accessToken
+ appUser[securityConf.userLookup.passwordPropertyName] = token.accessToken.accessToken
appUser[securityConf.userLookup.enabledPropertyName] = true
appUser[securityConf.userLookup.accountExpiredPropertyName] = false
appUser[securityConf.userLookup.accountLockedPropertyName] = false
@@ -150,18 +163,21 @@ class DefaultFacebookAuthDao implements FacebookAuthDao<Object>, InitializingBea
}
if (UserDetails.isAssignableFrom(user.class)) {
- return ((UserDetails)user).getAuthorities()
+ return ((UserDetails)user).getAuthorities()
}
def conf = SpringSecurityUtils.securityConfig
Class<?> PersonRole = grailsApplication.getDomainClass(conf.userLookup.authorityJoinClassName)?.clazz
if (!PersonRole) {
- log.error("Can't load roles for user $user. Reason: can't find ${conf.userLookup.authorityJoinClassName} class")
- return []
+ log.error("Can't load roles for user $user. Reason: can't find ${conf.userLookup.authorityJoinClassName} class")
+ return []
}
Collection roles = []
PersonRole.withTransaction { status ->
- roles = getPrincipal(user)?.getAt(rolesPropertyName)
+ roles = user?.getAt(rolesPropertyName)
+ }
+ if (!roles) {
+ roles = []
}
if (roles.empty) {
return roles
@@ -175,6 +191,64 @@ class DefaultFacebookAuthDao implements FacebookAuthDao<Object>, InitializingBea
}
}
+ Boolean hasValidToken(Object user) {
+ if (facebookAuthService && facebookAuthService.respondsTo('hasValidToken', user.class)) {
+ return facebookAuthService.hasValidToken(user)
+ }
+ def facebookUser = getFacebookUser(user)
+ if (facebookUser.properties.containsKey('accessToken')) {
+ if (facebookUser.accessToken == null) {
+ return false
+ }
+ }
+ if (facebookUser.properties.containsKey('accessTokenExpires')) {
+ if (facebookUser.accessTokenExpires == null) {
+ return false
+ }
+ Date goodExpiration = new Date(System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(4))
+ if (goodExpiration.after(facebookUser.accessTokenExpires)) {
+ return false
+ }
+ } else {
+ log.warn("Domain ${facebookUser.class} don't have 'acccessTokenExpires' field, can't check accessToken expiration")
+ }
+ return true //not supported currently
+ }
+
+ void updateToken(Object user, FacebookAuthToken token) {
+ if (facebookAuthService && facebookAuthService.respondsTo('updateToken', user.class, token.class)) {
+ facebookAuthService.updateToken(user, token)
+ return
+ }
+ log.debug("Update access token to $token")
+ def facebookUser = getFacebookUser(user)
+ if (facebookUser.properties.containsKey('accessToken')) {
+ facebookUser.accessToken = token.accessToken.accessToken
+ }
+ if (facebookUser.properties.containsKey('accessTokenExpires')) {
+ facebookUser.accessTokenExpires = token.accessToken.expireAt
+ }
+ Class<?> UserClass = grailsApplication.getDomainClass(domainClassName)?.clazz
+ if (!UserClass) {
+ log.error("Can't find domain: $domainClassName")
+ return
+ }
+ UserClass.withTransaction {
+ facebookUser.save()
+ }
+ }
+
+ String getAccessToken(Object user) {
+ if (facebookAuthService && facebookAuthService.respondsTo('getAccessToken', user.class)) {
+ return facebookAuthService.getAccessToken(user)
+ }
+ def facebookUser = getFacebookUser(user)
+ if (facebookUser.properties.containsKey('accessToken')) {
+ return facebookUser.accessToken
+ }
+ return null
+ }
+
void afterPropertiesSet() {
if (!facebookAuthService) {
if (applicationContext.containsBean('facebookAuthService')) {
@@ -186,30 +260,30 @@ class DefaultFacebookAuthDao implements FacebookAuthDao<Object>, InitializingBea
List serviceMethods = []
if (facebookAuthService) {
- facebookAuthService.metaClass.methods.each {
- serviceMethods<< it.name
- }
+ facebookAuthService.metaClass.methods.each {
+ serviceMethods<< it.name
+ }
}
def conf = SpringSecurityUtils.securityConfig
if (!serviceMethods.contains('getRoles')) {
Class<?> UserDomainClass = grailsApplication.getDomainClass(userDomainClassName)?.clazz
if (UserDomainClass == null || !UserDetails.isAssignableFrom(UserDomainClass)) {
- if (!conf.userLookup.authorityJoinClassName) {
- log.error("Don't have authority join class configuration. Please configure 'grails.plugins.springsecurity.userLookup.authorityJoinClassName' value")
- } else if (!grailsApplication.getDomainClass(conf.userLookup.authorityJoinClassName)) {
- log.error("Can't find authority join class (${conf.userLookup.authorityJoinClassName}). Please configure 'grails.plugins.springsecurity.userLookup.authorityJoinClassName' value, or create your own 'facebookAuthService.getRoles()'")
- }
+ if (!conf.userLookup.authorityJoinClassName) {
+ log.error("Don't have authority join class configuration. Please configure 'grails.plugins.springsecurity.userLookup.authorityJoinClassName' value")
+ } else if (!grailsApplication.getDomainClass(conf.userLookup.authorityJoinClassName)) {
+ log.error("Can't find authority join class (${conf.userLookup.authorityJoinClassName}). Please configure 'grails.plugins.springsecurity.userLookup.authorityJoinClassName' value, or create your own 'facebookAuthService.getRoles()'")
+ }
}
}
if (!serviceMethods.contains('findUser')) {
if (!domainClassName) {
- log.error("Don't have facebook user class configuration. Please configure 'grails.plugins.springsecurity.facebook.domain.classname' value")
+ log.error("Don't have facebook user class configuration. Please configure 'grails.plugins.springsecurity.facebook.domain.classname' value")
} else {
- Class<?> User = grailsApplication.getDomainClass(domainClassName)?.clazz
- if (!User) {
- log.error("Can't find facebook user class ($domainClassName). Please configure 'grails.plugins.springsecurity.facebook.domain.classname' value, or create your own 'facebookAuthService.findUser()'")
- }
+ Class<?> User = grailsApplication.getDomainClass(domainClassName)?.clazz
+ if (!User) {
+ log.error("Can't find facebook user class ($domainClassName). Please configure 'grails.plugins.springsecurity.facebook.domain.classname' value, or create your own 'facebookAuthService.findUser()'")
+ }
}
}
}
22 src/groovy/com/the6hours/grails/springsecurity/facebook/FacebookAccessToken.groovy
View
@@ -0,0 +1,22 @@
+package com.the6hours.grails.springsecurity.facebook
+
+import java.util.regex.Pattern
+
+/**
+ *
+ * @author Igor Artamonov (http://igorartamonov.com)
+ * @since 22.05.12
+ */
+class FacebookAccessToken {
+
+ String accessToken
+ Date expireAt
+
+ String toString() {
+ StringBuilder buf = new StringBuilder()
+ buf.append('Access token: ').append(accessToken)
+ buf.append(', expires at ').append(expireAt)
+ return buf.toString()
+ }
+
+}
6 src/groovy/com/the6hours/grails/springsecurity/facebook/FacebookAuthCookieFilter.groovy
View
@@ -25,13 +25,17 @@ class FacebookAuthCookieFilter extends GenericFilterBean implements ApplicationE
FacebookAuthUtils facebookAuthUtils
AuthenticationManager authenticationManager
String logoutUrl = '/j_spring_security_logout'
+ String forceLoginParameter = null
void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, javax.servlet.FilterChain chain) {
HttpServletRequest request = servletRequest
HttpServletResponse response = servletResponse
String url = request.requestURI.substring(request.contextPath.length())
logger.debug("Processing url: $url")
- if (url != logoutUrl && SecurityContextHolder.context.authentication == null) {
+ if (url != logoutUrl
+ && (SecurityContextHolder.context.authentication == null
+ || (forceLoginParameter
+ && servletRequest.getParameter(forceLoginParameter) == 'true'))) {
logger.debug("Applying facebook auth filter")
assert facebookAuthUtils != null
Cookie cookie = facebookAuthUtils.getAuthCookie(request)
21 src/groovy/com/the6hours/grails/springsecurity/facebook/FacebookAuthDao.groovy
View
@@ -38,4 +38,25 @@ public interface FacebookAuthDao<F> {
*/
Collection<GrantedAuthority> getRoles(F user)
+ /**
+ *
+ * @param user target user
+ * @return false when user have invalid token, or don't have token
+ */
+ Boolean hasValidToken(F user)
+
+ /**
+ * Setup new Facebook Access Token for specified user
+ *
+ * @param user target user
+ * @param token valid access token
+ */
+ void updateToken(F user, FacebookAuthToken token)
+
+ /**
+ *
+ * @param user target user
+ * @return current access_token, or null if not exists
+ */
+ String getAccessToken(F user)
}
26 src/groovy/com/the6hours/grails/springsecurity/facebook/FacebookAuthProvider.groovy
View
@@ -21,6 +21,7 @@ public class FacebookAuthProvider implements AuthenticationProvider {
FacebookAuthToken token = authentication
def user = facebookAuthDao.findUser(token.uid as Long)
+ boolean justCreated = false
if (user == null) {
//log.debug "New person $token.uid"
@@ -28,11 +29,36 @@ public class FacebookAuthProvider implements AuthenticationProvider {
log.info "Create new facebook user with uid $token.uid"
token.accessToken = facebookAuthUtils.getAccessToken(token.code)
user = facebookAuthDao.create(token)
+ justCreated = true
} else {
log.error "User $token.uid not exists - not authenticated"
}
}
if (user != null) {
+ if (!justCreated && !facebookAuthDao.hasValidToken(user)) {
+ String currentAccessToken = facebookAuthDao.getAccessToken(user)
+ FacebookAccessToken freshToken = null
+ if (currentAccessToken) {
+ freshToken = facebookAuthUtils.refreshAccessToken(currentAccessToken)
+ if (!freshToken) {
+ log.warn("Can't refresh access token")
+ }
+ }
+
+ if (!freshToken) {
+ freshToken = facebookAuthUtils.getAccessToken(token.code)
+ }
+
+ if (freshToken) {
+ if (freshToken.accessToken != currentAccessToken) {
+ token.accessToken = freshToken
+ facebookAuthDao.updateToken(user, token)
+ } else {
+ log.debug("User already have same access token")
+ }
+ }
+ }
+
UserDetails userDetails = createUserDetails(user, token.code)
token.details = userDetails
5 src/groovy/com/the6hours/grails/springsecurity/facebook/FacebookAuthToken.groovy
View
@@ -6,9 +6,10 @@ import org.springframework.security.authentication.AbstractAuthenticationToken
public class FacebookAuthToken extends AbstractAuthenticationToken implements Authentication {
- long uid
- String accessToken
+ long uid
+ FacebookAccessToken accessToken
String code
+
Object principal
Collection<GrantedAuthority> authorities
45 src/groovy/com/the6hours/grails/springsecurity/facebook/FacebookAuthUtils.groovy
View
@@ -8,6 +8,7 @@ import javax.crypto.Mac
import org.apache.commons.codec.binary.Base64
import org.springframework.security.authentication.BadCredentialsException
import grails.converters.JSON
+import java.util.concurrent.TimeUnit
/**
* TODO
@@ -65,11 +66,49 @@ class FacebookAuthUtils {
}
}
- String getAccessToken(String code) {
+
+
+ FacebookAccessToken refreshAccessToken(String existingAccessToken) {
+ String authUrl = "https://graph.facebook.com/oauth/access_token?client_id=$applicationId&client_secret=$secret&grant_type=fb_exchange_token&fb_exchange_token=$existingAccessToken"
+ return requestAccessToken(authUrl)
+ }
+
+ FacebookAccessToken getAccessToken(String code) {
+ String authUrl = "https://graph.facebook.com/oauth/access_token?client_id=$applicationId&redirect_uri=&client_secret=$secret&code=$code"
+ return requestAccessToken(authUrl)
+ }
+
+ FacebookAccessToken requestAccessToken(String authUrl) {
try {
- String authUrl = "https://graph.facebook.com/oauth/access_token?client_id=$applicationId&redirect_uri=&client_secret=$secret&code=$code"
URL url = new URL(authUrl)
- return url.readLines().first().split('&').first().split('=')[1]
+ String response = url.readLines().first()
+ //println "AccessToken response: $response"
+ Map data = [:]
+ response.split('&').each {
+ String[] kv = it.split('=')
+ if (kv.length != 2) {
+ log.warn("Invalid response part: $it")
+ } else {
+ data[kv[0]] = kv[1]
+ }
+ }
+ FacebookAccessToken token = new FacebookAccessToken()
+ if (data.access_token) {
+ token.accessToken = data.access_token
+ } else {
+ log.error("No access_token in response: $response")
+ }
+ if (data.expires) {
+ if (data.expires =~ /^\d+$/) {
+ token.expireAt = new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(Long.parseLong(data.expires)))
+ } else {
+ log.warn("Invalid 'expires' value: $data.expires")
+ }
+ } else {
+ log.error("No expires in response: $response")
+ }
+ log.debug("Got AccessToken: $token")
+ return token
} catch (IOException e) {
log.error("Can't read data from Facebook", e)
return null

No commit comments for this range

Something went wrong with that request. Please try again.