Как реализовать пользовательский FilterSecurityInterceptor, используя grails 1.3.2 и плагин spring-security-core 1? - PullRequest
3 голосов
/ 03 августа 2010

Я пишу приложение grails 1.3.2 и внедряю безопасность с помощью spring-security-core 1.0. По причинам, выходящим за рамки этого вопроса, я реализую пользовательский FilterSecurityInterceptor в дополнение к стандартным перехватчикам. Я начал с записи в блоге на эту тему и попытался изменить ее для Spring Security 3 без особого успеха.

Слегка следуя блогу (поскольку он основан на более старой версии Spring Security), я создал следующие классы:

  1. Подкласс org.springframework.security.authentication.AbstractAuthenticationToken для хранения моих учетных данных.
  2. Подкласс org.springframework.security.authentication.AuthenticationProvider для реализации проверки подлинности и поддерживает методы для заполнения экземпляра Authentication данными из моего UserDetailsService.
  3. Подкласс org.springframework.security.web.access.intercept.FilterSecurityInterceptor для реализации методов doFilter и afterPropertiesSet.
  4. Некоторая конфигурация 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
 }
}           

Наконец, я настраиваю некоторые компоненты:

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)

Так где я все испортил, или я сделал это слишком сложным, и я упустил что-то простое?

Ответы [ 3 ]

3 голосов
/ 13 августа 2010

В конце концов, я реализовал пользовательский фильтр, но не FilterSecurityInterceptor.Я вставил свой фильтр после фильтра OOTB запомнитьMe.Более того, я обнаружил, что моя реализация аутентификации является довольно ресурсоемкой и медленной, поэтому я установил cookie-файл RememberMe для успешной аутентификации.В целом это был болезненный опыт, поэтому я попытаюсь описать это здесь.

Моя реализация фильтра была следующей:

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

Надеемся, что другие найдут это полезным при реализацииих индивидуальные решения для аутентификации.Мы обнаружили, что это работает для нас при интеграции нашего приложения с нашей устаревшей системой.

1 голос
/ 04 августа 2010

Похоже, что это ошибка в плагине spring-security-core, потому что securityMetadataSource не вставляется в стандартную пружину безопасности FilterSecurityInterceptor.Возможно, плагин запутывается и вставляет securityMetadataSource в ваш пользовательский FilterSecurityInterceptor и игнорирует другой (по умолчанию)?Берт, возможно, захочет узнать более подробную информацию.

Возможно, вы можете попробовать заменить значение по умолчанию FilterSecurityInterceptor на свое, используя свойство grails.plugins.springsecurity.filterNames ...

1 голос
/ 03 августа 2010

Учитывая, где это терпит неудачу (довольно не связано), я предполагаю, что это вложенные свойства. Попробуйте

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

Я предполагаю, что он сбрасывает оставшуюся часть конфигурации (причуду Grails / ConfigSlurper) и вместо этого она сливается в свойствах. Вам не нужно устанавливать «active = true», но я полагаю, вам нужно было добавить это, поскольку он также сбрасывается.

Кстати - вы можете удалить получатели из InterchangeAuthenticationToken, так как открытые поля генерируют получатели автоматически.

...