Java 如何使用Grails1.3.2和插件SpringSecurityCore1实现自定义FilterSecurityInterceptor?
我正在编写一个Grails1.3.2应用程序,并使用SpringSecurityCore1.0实现安全性。出于本问题范围之外的原因,除了开箱即用的拦截器之外,我还实现了一个自定义FilterSecurityInterceptor。我从一个关于这个主题的例子开始,并尝试将它调整为SpringSecurity3,但没有太大成功 松散地遵循博客(因为它基于较旧版本的Spring Security),我创建了以下类:Java 如何使用Grails1.3.2和插件SpringSecurityCore1实现自定义FilterSecurityInterceptor?,java,grails,filter,spring-security,grails-plugin,Java,Grails,Filter,Spring Security,Grails Plugin,我正在编写一个Grails1.3.2应用程序,并使用SpringSecurityCore1.0实现安全性。出于本问题范围之外的原因,除了开箱即用的拦截器之外,我还实现了一个自定义FilterSecurityInterceptor。我从一个关于这个主题的例子开始,并尝试将它调整为SpringSecurity3,但没有太大成功 松散地遵循博客(因为它基于较旧版本的Spring Security),我创建了以下类: 用于保存我的凭据的org.springframework.security.authe
class InterchangeAuthenticationToken extends AbstractAuthenticationToken {
String credentials
Integer name
Integer principal
String getCredentials() { //necessary or I get compilation error
return credentials
}
Integer getPrincipal() { //necessary or I get compilation error
return principal
}
}
class InterchangeAuthenticationProvider implements org.springframework.security.authentication.AuthenticationProvider {
Authentication authenticate(Authentication customAuth) {
def authUser = AuthUser.get(customAuth.principal)
if (authUser) {
customAuth.setAuthorities(authUser.getAuthorities())
customAuth.setAuthenticated(true)
return customAuth
} else {
return null
}
}
boolean supports(Class authentication) {
return InterchangeAuthenticationToken.class.isAssignableFrom(authentication)
}
}
我的AuthenticationProvider非常简单:
class InterchangeAuthenticationToken extends AbstractAuthenticationToken {
String credentials
Integer name
Integer principal
String getCredentials() { //necessary or I get compilation error
return credentials
}
Integer getPrincipal() { //necessary or I get compilation error
return principal
}
}
class InterchangeAuthenticationProvider implements org.springframework.security.authentication.AuthenticationProvider {
Authentication authenticate(Authentication customAuth) {
def authUser = AuthUser.get(customAuth.principal)
if (authUser) {
customAuth.setAuthorities(authUser.getAuthorities())
customAuth.setAuthenticated(true)
return customAuth
} else {
return null
}
}
boolean supports(Class authentication) {
return InterchangeAuthenticationToken.class.isAssignableFrom(authentication)
}
}
我实现了一个简单的FilterSecurity接口。最终,这将做一些有趣的事情:
class InterchangeFilterSecurityInterceptor extends FilterSecurityInterceptor implements InitializingBean {
def authenticationManager
def interchangeAuthenticationProvider
def securityMetadataSource
void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
if (SecurityContextHolder.getContext().getAuthentication() == null) {
def myAuth = new InterchangeAuthenticationToken()
myAuth.setName(1680892)
myAuth.setCredentials('SDYLWUYa:nobody::27858cff')
myAuth.setPrincipal(1680892)
myAuth = authenticationManager.authenticate(myAuth);
if (myAuth) {
println "Successfully Authenticated ${userId} in object ${myAuth}"
// Store to SecurityContextHolder
SecurityContextHolder.getContext().setAuthentication(myAuth);
}
}
chain.doFilter(request, response)
}
void afterPropertiesSet() {
def providers = authenticationManager.providers
providers.add(interchangeAuthenticationProvider)
authenticationManager.providers = providers
}
}
最后,我配置了一些bean:
beans = {
interchangeAuthenticationProvider(com.bc.commerce.core.InterchangeAuthenticationProvider) {
}
interchangeFilterSecurityInterceptor(com.bc.commerce.core.InterchangeFilterSecurityInterceptor) {
authenticationManager = ref('authenticationManager')
interchangeAuthenticationProvider = ref('interchangeAuthenticationProvider')
securityMetadataSource = ref('objectDefinitionSource')
}
}
并对插件进行一些配置:
grails.plugins.springsecurity.dao.hideUserNotFoundExceptions = true //not setting this causes exception
grails.plugins.springsecurity.providerNames = [
'interchangeAuthenticationProvider',
'daoAuthenticationProvider',
'anonymousAuthenticationProvider',
'rememberMeAuthenticationProvider'
]
并在Bootstrap.groovy中设置过滤器顺序:
def init = {servletContext ->
//insert our custom filter just after the filter security interceptor
SpringSecurityUtils.clientRegisterFilter('interchangeFilterSecurityInterceptor', SecurityFilterPosition.SECURITY_CONTEXT_FILTER.order + 10)
<snip />
}
那么,我到底把事情搞砸了,还是我把它弄得太复杂了,错过了一些简单的东西?考虑到它失败的地方(相当不相关),我猜是嵌套属性。试一试
grails.plugins.springsecurity.dao.hideUserNotFoundExceptions = true
grails.plugins.springsecurity.providerNames = [
'interchangeAuthenticationProvider',
'daoAuthenticationProvider',
'anonymousAuthenticationProvider',
'rememberMeAuthenticationProvider'
]
我的猜测是,它正在重置配置的其余部分(Grails/ConfigSlurper的一个怪癖),而这将合并到属性中。您不需要设置“active=true”,但我猜您需要添加它,因为它也会被重置
顺便说一句,您可以从InterchangeAuthenticationToken中删除getter,因为公共字段会自动生成getter。这似乎是spring security核心插件中的一个错误,因为
securityMetadataSource
未注入默认的spring security过滤器安全Interceptor
。也许插件会混淆,并将securityMetadataSource
注入到您的自定义FilterSecurityInterceptor
中,而忽略了另一个(默认)?伯特可能愿意深入了解这方面的信息
也许您可以尝试使用grails.plugins.springsecurity.filterNames属性将默认的
FilterSecurityInterceptor
替换为自定义的。我在OOTB记忆过滤器之后插入了过滤器。此外,我发现我的身份验证实现在某种程度上是资源密集型的,而且速度较慢,所以我让它在成功的身份验证上设置了rememberMe cookie。总的来说,这是一次痛苦的经历,所以我将尝试在这里记录它
我的筛选器实现如下所示:
import javax.servlet.FilterChain
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import javax.servlet.ServletException
import javax.servlet.ServletRequest
import javax.servlet.ServletResponse
import org.springframework.context.ApplicationEventPublisher
import org.springframework.context.ApplicationEventPublisherAware
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.Authentication
import org.springframework.security.core.AuthenticationException
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.web.filter.GenericFilterBean
class CustomRememberMeAuthenticationFilter extends GenericFilterBean implements ApplicationEventPublisherAware {
def authenticationManager
def eventPublisher
def customService
def rememberMeServices
def springSecurityService
//make certain that we've specified our beans
void afterPropertiesSet() {
assert authenticationManager != null, 'authenticationManager must be specified'
assert customService != null, 'customService must be specified'
assert rememberMeServices != null, 'rememberMeServices must be specified'
}
void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req
HttpServletResponse response = (HttpServletResponse) res
if (SecurityContextHolder.getContext().getAuthentication() == null) {
Authentication auth
try {
auth = customService.getUsernamePasswordAuthenticationToken(request)
if (auth != null) {
springSecurityService.reauthenticate(auth.getPrincipal(), auth.getCredentials())
logger.debug("SecurityContextHolder populated with auth: '"
+ SecurityContextHolder.getContext().getAuthentication() + "'")
onSuccessfulAuthentication(request, response, SecurityContextHolder.getContext().getAuthentication())
} else {
logger.debug('customService did not return an authentication from the request')
}
} catch (AuthenticationException authenticationException) {
logger.warn("SecurityContextHolder not populated with auth, as "
+ "springSecurityService rejected Authentication returned by customService: '"
+ auth + "'", authenticationException)
onUnsuccessfulAuthentication(request, response, auth)
} catch(e) {
logger.warn("Unsuccessful authentication in customRememberMeAuthenticationFilter", e)
onUnsuccessfulAuthentication(request, response, auth)
}
} else {
logger.debug("SecurityContextHolder not populated with auth, as it already contained: '"
+ SecurityContextHolder.getContext().getAuthentication() + "'")
}
chain.doFilter(request, response)
}
protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) {
//sets the rememberMe cookie, but cannot call "loginSuccess" because that filters out requests
//that don't set the rememberMe cookie, like this one
rememberMeServices.onLoginSuccess(request, response, authResult)
}
protected void onUnsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {
//clear the rememberMe cookie
rememberMeServices.loginFail(request, response)
}
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher
}
}
希望其他人在实现自定义身份验证解决方案时会发现这一点很有帮助。当我们将应用程序与遗留系统集成时,我们发现这一点非常有效。顺便说一句,下面是NPE的代码行:我尝试重新启动配置,得到了相同的结果。如果我将它们注释掉并构建,那么我就无法编译:[groovyc]/Users/calef/community services/src/groovy/com/bc/commerce/core/InterchangeAuthenticationToken.groovy:7:在非抽象类中不能有抽象方法。类“com.bc.commerce.core.InterchangeAuthenticationToken”必须声明为抽象的,或者必须实现方法“java.lang.Object getCredentials()”。[groovyc]@第7行第1列。[groovyc]类交换AuthenticationToken扩展了AbstractAuthenticationToken{[groovyc]^请确保运行“grails clean”以强制进行完全重新编译。您还可以安装spring security ui插件,该插件具有显示已注册的身份验证程序、筛选器、属性等的信息页面,您可以使用该页面来验证是否按预期进行了设置。我尝试了设置filterNames以替换FilterSecurity Inter的方法ceptor,但奇怪的是,当我设置属性时,过滤器名称没有改变。我在这里发布了对实现的更全面的描述: