Java 如何使用Grails1.3.2和插件SpringSecurityCore1实现自定义FilterSecurityInterceptor?

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

我正在编写一个Grails1.3.2应用程序,并使用SpringSecurityCore1.0实现安全性。出于本问题范围之外的原因,除了开箱即用的拦截器之外,我还实现了一个自定义FilterSecurityInterceptor。我从一个关于这个主题的例子开始,并尝试将它调整为SpringSecurity3,但没有太大成功

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

  • 用于保存我的凭据的org.springframework.security.authentication.AbstractAuthenticationToken子类
  • 一个org.springframework.security.authentication.AuthenticationProvider子类,用于实现身份验证,并支持使用my UserDetails服务中的数据填充身份验证实例的方法
  • 一个org.springframework.security.web.access.intercept.FilterSecurityInterceptor子类,用于实现doFilter和afterPropertiesSet方法
  • 对bean和spring安全核心插件进行一些配置,以识别我的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
     }
    }
    
    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,但奇怪的是,当我设置属性时,过滤器名称没有改变。我在这里发布了对实现的更全面的描述: