如何使用 grails 1.3.2 和插件 spring-security-core 1 实现自定义 FilterSecurityInterceptor?

Posted

技术标签:

【中文标题】如何使用 grails 1.3.2 和插件 spring-security-core 1 实现自定义 FilterSecurityInterceptor?【英文标题】:How do I implement a custom FilterSecurityInterceptor using grails 1.3.2 and the plugin spring-security-core 1? 【发布时间】:2011-03-24 11:33:43 【问题描述】:

我正在编写一个 grails 1.3.2 应用程序并使用 spring-security-core 1.0 实现安全性。由于此问题范围之外的原因,除了开箱即用的拦截器之外,我还实现了一个自定义 FilterSecurityInterceptor 。我从 blog entry 开始讨论该主题,并尝试针对 Spring Security 3 调整它,但没有取得多大成功。

大致遵循博客(因为它基于旧版本的 Spring Security),我创建了以下类:

    一个 org.springframework.security.authentication.AbstractAuthenticationToken 子类来保存我的凭据。 一个 org.springframework.security.authentication.AuthenticationProvider 子类,用于实现身份验证并支持使用来自我的 UserDetailsS​​ervice 的数据填充身份验证实例的方法。 一个 org.springframework.security.web.access.intercept.FilterSecurityInterceptor 子类来实现 doFilter 和 afterPropertiesSet 方法。 bean 和 spring-security-core 插件的一些配置,用于识别我的 AuthenticationProvider 并将我的过滤器插入过滤器链。

我的 AbstractAuthenticationToken 非常简单:

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
 

我的 AuthenticationProvider 非常简单:

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)
 


我已经实现了一个简单的 FilterSecurityInterceptor。最终这会做一些有趣的事情:

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

当我点击一个 URL 时,我得到以下异常,这让我很困惑:

2010-07-30 15:07:16,763 [http-8080-1] ERROR [/community-services].[default]  - Servlet.service() for servlet default threw exception
java.lang.NullPointerException
 at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:171)
 at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:106)
 at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:83)
 at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:355)
 at org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter.doFilter(RememberMeAuthenticationFilter.java:112)
 at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:355)
 at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:54)
 at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:355)
 at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:187)
 at org.codehaus.groovy.grails.plugins.springsecurity.RequestHolderAuthenticationFilter.doFilter(RequestHolderAuthenticationFilter.java:40)
 at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:355)
 at org.codehaus.groovy.grails.plugins.springsecurity.MutableLogoutFilter.doFilter(MutableLogoutFilter.java:79)
 at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:355)
 at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:79)
 at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:355)
 at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:149)
 at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:237)
 at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:167)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
 at org.codehaus.groovy.grails.web.servlet.filter.GrailsReloadServletFilter.doFilterInternal(GrailsReloadServletFilter.java:104)
 at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
 at org.codehaus.groovy.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:67)
 at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
 at org.codehaus.groovy.grails.web.filters.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:66)
 at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
 at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
 at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
 at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:237)
 at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:167)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
 at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
 at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
 at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
 at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
 at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
 at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
 at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:849)
 at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
 at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:454)
 at java.lang.Thread.run(Thread.java:637)

那么我在哪里搞砸了,还是我把它弄得太复杂了,我错过了一些简单的东西?

【问题讨论】:

顺便说一下,这是带有 NPE 的代码行:grepcode.com/file/repo1.maven.org/maven2/… 【参考方案1】:

最后,我实现了一个自定义过滤器,但没有实现 FilterSecurityInterceptor。我在 OOTB rememberMe 过滤器之后插入了过滤器。此外,我发现我的身份验证实现有点资源密集和缓慢,所以我让它在成功的身份验证时设置了 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
    

希望其他人在实施他们的自定义身份验证解决方案时发现这很有帮助。在将我们的应用程序与旧系统集成时,我们发现这对我们很有效。

【讨论】:

我在这里发布了一个更全面的实现描述:cricker.tumblr.com/post/953860464/…【参考方案2】:

鉴于它失败的地方(相当不相关),我猜它是嵌套属性。试试看

grails.plugins.springsecurity.dao.hideUserNotFoundExceptions = true
grails.plugins.springsecurity.providerNames = [
    'interchangeAuthenticationProvider',
    'daoAuthenticationProvider',
    'anonymousAuthenticationProvider',
    'rememberMeAuthenticationProvider'
]

我的猜测是它正在重置配置的其余部分(一个 Grails/ConfigSlurper 怪癖),而这将合并到属性中。您不需要设置“active = true”,但我猜您需要添加它,因为它也正在重置。

顺便说一句 - 您可以从 InterchangeAuthenticationToken 中删除 getter,因为公共字段会自动生成 getter。

【讨论】:

我尝试重新配置配置,得到了相同的结果。重新获取吸气剂,如果我将它们注释掉并构建,那么我无法编译:[groovyc] /Users/calef/community-services/src/groovy/com/bc/commerce/core/InterchangeAuthenticationToken.groovy: 7: Can't在非抽象类中有一个抽象方法。必须将类“com.bc.commerce.core.InterchangeAuthenticationToken”声明为抽象类,或者必须实现方法“java.lang.Object getCredentials()”。 [groovyc] @ 第 7 行,第 1 列。 [groovyc] 类 InterchangeAuthenticationToken 扩展 AbstractAuthenticationToken [groovyc] ^ 确保运行 'grails clean' 以强制重新编译。您还可以安装 spring-security-ui 插件,该插件具有显示已注册身份验证器、过滤器、属性等的信息页面,您可以使用它来验证设置是否符合您的预期。【参考方案3】:

这似乎是 spring-security-core 插件中的一个错误,因为 securityMetadataSource 没有注入到默认的 spring security FilterSecurityInterceptor 中。也许插件会混淆并将securityMetadataSource 注入您的自定义FilterSecurityInterceptor 而忽略另一个(默认)? Burt 可能愿意查看更深入的信息。

也许您可以尝试使用grails.plugins.springsecurity.filterNames 属性将默认FilterSecurityInterceptor 替换为您的自定义FilterSecurityInterceptor...

【讨论】:

我尝试了设置filterNames来替换FilterSecurityInterceptor的方法,但奇怪的是,当我设置属性时,filternames并没有改变。

以上是关于如何使用 grails 1.3.2 和插件 spring-security-core 1 实现自定义 FilterSecurityInterceptor?的主要内容,如果未能解决你的问题,请参考以下文章

如何从另一个 grails 插件配置 grails 插件

如何使用 Grails Asset 插件引用静态 HTML rsource?

如何在 grails 中实现表单构建器插件

什么是 Grails 插件?安装插件是什么意思?

Grails:如何引用位于已安装插件中的资源?

Grails 插件如何添加自己的 URLMappings?