2011-12-27 37 views
32

Estoy usando Spring Security para asegurar una aplicación web Struts2. Debido a las limitaciones del proyecto, estoy usando Spring Security 2.06.Implementar AuthenticationProvider personalizado en Spring Security 2.06

Mi equipo construyó una API de administración de usuario personalizado que autentica a un usuario después de tomar en los parámetros de usuario y contraseña, y devuelve un objeto de usuario personalizada que contiene una lista de funciones y otros atributos como el correo electrónico, nombre, etc.

De Según entiendo, el caso de uso típico de Spring Security utiliza un UserDetailsService predeterminado para recuperar un objeto UserDetails; este objeto contendrá (entre otras cosas) un campo de contraseña que será utilizado por el marco para autenticar al usuario.

En mi caso, quiero que nuestra API personalizada realice la autenticación, luego devuelva un objeto UserDetails personalizado que contenga los roles y otros atributos (correo electrónico, etc.).

Después de algunas investigaciones, descubrí que puedo hacer esto a través de una implementación personalizada de AuthenticationProvider. También tengo implementaciones personalizadas de UserDetailsService y UserDetails.

Mi problema es que realmente no entiendo lo que se supone que debo devolver en CustomAuthenticationProvider. ¿Utilizo aquí mi objeto UserDetailsService personalizado? ¿Es eso incluso necesario? Lo siento, estoy realmente confundido.

CustomAuthenticationProvider:

public class CustomAuthenticationProvider implements AuthenticationProvider { 

private Logger logger = Logger.getLogger(CustomAuthenticationProvider.class); 

private UserDetailsService userDetailsService; //what am i supposed to do with this? 

@Override 
public Authentication authenticate(Authentication authentication) throws AuthenticationException { 
    UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) authentication; 
    String username = String.valueOf(auth.getPrincipal()); 
    String password = String.valueOf(auth.getCredentials()); 

    logger.info("username:" + username); 
    logger.info("password:" + password); 
    /* what should happen here? */ 

    return null; //what do i return? 
} 

@Override 
public boolean supports(Class aClass) { 
    return true; //To indicate that this authenticationprovider can handle the auth request. since there's currently only one way of logging in, always return true 
} 

public UserDetailsService getUserDetailsService() { 
    return userDetailsService; 
} 

public void setUserDetailsService(UserDetailsService userDetailsService) { 
    this.userDetailsService = userDetailsService; 
} 

}

applicationContext-security.xml:

<beans:bean id="customUserDetailsService" scope="prototype" class="com.test.testconsole.security.CustomUserDetailsService"/> 

<beans:bean id="customAuthenticationProvider" class="com.test.testconsole.security.CustomAuthenticationProvider"> 
    <custom-authentication-provider /> 
    <beans:property name="userDetailsService" ref="customUserDetailsService" /> 
</beans:bean> 

En resumen, esto es lo que necesito:

  1. usuario se conecta a través un formulario web
  2. Autenticar usuario utilizando en el local de la API de administración de usuarios
  3. Para los usuarios autenticados con éxito, poblar GrantedAuthories, etc.
  4. devolver una entidad de usuario que contiene roles/autoridades y otros atributos como el correo electrónico, nombre, etc. que debería a continuación, acceder a este objeto como tal ..

    //spring security get user name 
    Authentication auth = SecurityContextHolder.getContext().getAuthentication(); 
    userName = auth.getName(); //get logged in username 
    logger.info("username: " + userName); 
    
    //spring security get user role 
    GrantedAuthority[] authorities = auth.getAuthorities(); 
    userRole = authorities[0].getAuthority(); 
    logger.info("user role: " + userRole); 
    

espero que esto tenga sentido. Cualquier ayuda o consejos serán apreciados!

Gracias!

Actualización:

He hecho algunos progresos, creo.

que tienen un objeto de autenticación personalizado que implementa la interfaz de autenticación:

public class CustomAuthentication implements Authentication { 

    String name; 
    GrantedAuthority[] authorities; 
    Object credentials; 
    Object details; 
    Object principal; 
    boolean authenticated; 

    public CustomAuthentication(String name, GrantedAuthority[] authorities, Object credentials, Object details, Object principal, boolean 
           authenticated){ 
     this.name=name; 
     this.authorities=authorities; 
     this.details=details; 
     this.principal=principal; 
     this.authenticated=authenticated; 

    } 
    @Override 
    public GrantedAuthority[] getAuthorities() { 
     return new GrantedAuthority[0]; //To change body of implemented methods use File | Settings | File Templates. 
    } 

    @Override 
    public Object getCredentials() { 
     return null; //To change body of implemented methods use File | Settings | File Templates. 
    } 

    @Override 
    public Object getDetails() { 
     return null; //To change body of implemented methods use File | Settings | File Templates. 
    } 

    @Override 
    public Object getPrincipal() { 
     return null; //To change body of implemented methods use File | Settings | File Templates. 
    } 

    @Override 
    public boolean isAuthenticated() { 
     return false; //To change body of implemented methods use File | Settings | File Templates. 
    } 

    @Override 
    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { 
     //To change body of implemented methods use File | Settings | File Templates. 
    } 

    @Override 
    public String getName() { 
     return null; 
    } 
} 

y actualizada mi clase CustomerAuthenticationProvider:

@Override 
    public Authentication authenticate(Authentication authentication) throws AuthenticationException { 
     UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) authentication; 
     String username = String.valueOf(auth.getPrincipal()); 
     String password = String.valueOf(auth.getCredentials()); 

     logger.info("username:" + username); 
     logger.info("password:" + password); 

     //no actual validation done at this time 

     GrantedAuthority[] authorities = new GrantedAuthorityImpl[1]; 
     authorities[0] = new GrantedAuthorityImpl("ROLE_USER"); 

     CustomAuthentication customAuthentication = new CustomAuthentication("TestMerchant",authorities,"details",username,password,true); 

    return customAuthentication; 

    //return new UsernamePasswordAuthenticationToken(username,password,authorities); 
} 

Funciona si vuelvo un objeto UsernamePasswordAuthenticationToken, pero si intento volver CustomAuthentication, me aparece el siguiente error:

java.lang.ClassCastException: com.test.testconsole.security.CustomAuthentication cannot be cast to org.springframework.security.providers.UsernamePasswordAuthenticationToken 
    at com.test.testconsole.security.CustomAuthenticationProvider.authenticate(CustomAuthenticationProvider.java:27) 
    at org.springframework.security.providers.ProviderManager.doAuthentication(ProviderManager.java:188) 
    at org.springframework.security.AbstractAuthenticationManager.authenticate(AbstractAuthenticationManager.java:46) 
    at org.springframework.security.intercept.AbstractSecurityInterceptor.authenticateIfRequired(AbstractSecurityInterceptor.java:319) 
    at org.springframework.security.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:258) 
    at org.springframework.security.intercept.web.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:106) 
    at org.springframework.security.intercept.web.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:83) 
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390) 
    at org.springframework.security.ui.SessionFixationProtectionFilter.doFilterHttp(SessionFixationProtectionFilter.java:67) 
    at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53) 
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390) 
    at org.springframework.security.ui.ExceptionTranslationFilter.doFilterHttp(ExceptionTranslationFilter.java:101) 
    at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53) 
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390) 
    at org.springframework.security.providers.anonymous.AnonymousProcessingFilter.doFilterHttp(AnonymousProcessingFilter.java:105) 
    at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53) 
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390) 
    at org.springframework.security.ui.rememberme.RememberMeProcessingFilter.doFilterHttp(RememberMeProcessingFilter.java:116) 
    at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53) 
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390) 
    at org.springframework.security.wrapper.SecurityContextHolderAwareRequestFilter.doFilterHttp(SecurityContextHolderAwareRequestFilter.java:91) 
    at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53) 
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390) 
    at org.springframework.security.ui.basicauth.BasicProcessingFilter.doFilterHttp(BasicProcessingFilter.java:174) 
    at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53) 
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390) 
    at org.springframework.security.ui.AbstractProcessingFilter.doFilterHttp(AbstractProcessingFilter.java:278) 
    at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53) 
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390) 
    at org.springframework.security.ui.logout.LogoutFilter.doFilterHttp(LogoutFilter.java:89) 
    at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53) 
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390) 
    at org.springframework.security.context.HttpSessionContextIntegrationFilter.doFilterHttp(HttpSessionContextIntegrationFilter.java:235) 
    at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53) 
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390) 
    at org.springframework.security.util.FilterChainProxy.doFilter(FilterChainProxy.java:175) 
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:236) 
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:167) 
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157) 
    at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:388) 
    at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216) 
    at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182) 
    at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765) 
    at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:418) 
    at org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:230) 
    at org.mortbay.jetty.handler.HandlerCollection.handle(HandlerCollection.java:114) 
    at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152) 
    at org.mortbay.jetty.Server.handle(Server.java:326) 
    at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:536) 
    at org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:915) 
    at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:539) 
    at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:212) 
    at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:405) 
    at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:409) 
    at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582) 

Parece que algo no está esperando cualquier objeto de Autenticación, sino una implementación específica del mismo - UsernamePasswordAuthenticationToken. Esto me hace pensar que me puede estar perdiendo otro componente personalizado ... ¿tal vez un filtro?

Respuesta

44

Si está implementando su propio AuthenticationProvider, no tiene que implementar un UserDetailsService si no lo desea. UserDetailsService solo proporciona un DAO estándar para cargar información del usuario y algunas otras clases dentro del marco se implementan para usarlo.

Normalmente, para autenticarse utilizando un nombre de usuario y contraseña, creará una instancia de DaoAuthenticationProvider e inyectará eso con UserDetailsService. Ese puede ser tu mejor enfoque. Si implementa su propio proveedor, asume la responsabilidad de asegurarse de que el usuario haya proporcionado la contraseña correcta, y así sucesivamente. Sin embargo, en algunos casos, este es un enfoque más simple.

Para responder a su "¿qué debería pasar aquí?" comentar en el código, sería algo así como

@Override 
public Authentication authenticate(Authentication authentication) throws AuthenticationException { 
    UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) authentication; 
    String username = String.valueOf(auth.getPrincipal()); 
    String password = String.valueOf(auth.getCredentials()); 

    logger.info("username:" + username); 
    logger.info("password:" + password); // Don't log passwords in real app 

    // 1. Use the username to load the data for the user, including authorities and password. 
    YourUser user = .... 

    // 2. Check the passwords match (should use a hashed password here). 
    if (!user.getPassword().equals(password)) { 
    throw new BadCredentialsException("Bad Credentials"); 
    } 

    // 3. Preferably clear the password in the user object before storing in authentication object 
    user.clearPassword(); 

    // 4. Return an authenticated token, containing user data and authorities 

    return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()) ; 
} 

El objeto de usuario ello, podrá acceder mediante el método

Authentication.getPrincipal() 

, y se puede acceder a las propiedades adicionales (correo electrónico, etc.) echándola por su implementación de usuario personalizada.

Cómo cargar los datos del usuario depende de usted. Todas las preocupaciones de Spring Security aquí son la interfaz AuthenticationProvider.

También debe almacenar contraseñas hash y validar la contraseña proporcionada utilizando el mismo algoritmo, en lugar de una simple comprobación de igualdad.

+0

Hola Lucas, gracias por su respuesta! De hecho, hice un pequeño progreso después de publicar la pregunta y lo que hice se parece a lo que sugirió. Todavía tengo el problema de no poder agregar campos adicionales como (correo electrónico, etc.) al objeto Autenticación. Implementé una clase de Autenticación personalizada pero recibo errores. Consulte mi actualización ... – shaunlim

+0

Parece que está realizando un yeso no válido en su CustomAuthenticationProvider, línea 27 a UsernamePasswordAuthenticationToken. Normalmente no debería haber ninguna necesidad de crear un objeto de Autenticación personalizado. –

+0

oh .. lo entiendo. ¿Quiere decir que en lugar de pasar el nombre de usuario como principal al constructor UsernamePasswordAuthenticationToken, debería pasar en mi objeto de usuario personalizado que contiene todos los campos adicionales que necesito? luego, cuando necesite los datos, ¿solo llamaré a getPrincipal luego de recuperar el objeto Autenticación? – shaunlim

3

gracias por publicar esto Luke!

Me salvó de más daño cerebral.

Sólo cosa de la nota me encontré, para cualquier persona que se preocupa:

Mi configuración:

  • Grails 2.0.4
  • Groovy 1.8
  • seguridad de primavera-core 1.2.7.3
  • spring-security-ui 0.2
  • hibernate 2.0.4

Cuando se utiliza el muy apreciado elegante enfoque simplificado/Lucas sugiere, no implementar DetallesUsuario personalizados (o UserDetailsService) -y- objeto utilizando su propio dominio del usuario objeto que no se extiende nada especial, debe dar un paso más si está utilizando la "SEC" etiquetas personalizadas de seguridad de la primavera (en sus páginas, por supuesto):

Cuando se instancia un básico, no personalizada UsernamePasswordAuthenticationToken, debe pasar una instancia de algo que se extiende Principal, de nuevo, si quieres tu primavera etiquetas personalizadas de brecha de seguridad para trabajar. Hice algo como esto, para mantenerlo lo más simple posible (referencia a mis valores de objeto de dominio de usuario cuando sea útil/adecuada):

def principalUser = new org.springframework.security.core.userdetails.User(user.username, user.password, user.enabled, !user.accountExpired, !user.passwordExpired,!user.accountLocked, authorities) 
def token = new UsernamePasswordAuthenticationToken(principalUser, presentedPassword, authorities) 

Esto debe satisfacer las condiciones ensayadas en grails.plugins.springsecurity.SecurityTagLib.determineSource() por lo que, ya sabes, las páginas que utilizan <sec:loggedInUserInfo> en realidad se render:

if (principal.metaClass.respondsTo(principal, 'getDomainClass')) { 
      return principal.domainClass 
} 

de lo contrario, si se ejemplariza la UsernamePasswordAuthenticationToken con su objeto de dominio del usuario (como Lucas espectáculo en su ejemplo), ese método lib etiqueta de seguridad (DeterminarSource()) simplemente hará su mejor nivel y devolverá el valor (meta) de org.codehaus.groovy.grails.commons.DefaultGrailsDomainClass y obtendrá un error cuando la etiqueta va en busca de la variable miembro nombre de usuario indicando:

Error executing tag <sec:ifLoggedIn>: Error executing tag <sec:loggedInUserInfo>: No such property: username for class: org.codehaus.groovy.grails.commons.DefaultGrailsDomainClass 

corto de la re-implementación/subclasificación de la primavera-seguridad -core plugin taglibs in my grails project, simplemente no hay forma de que ambos utilicen los taglibs Y use su clase de usuario de dominio personalizada para instanciar el token que pasa de su filtro a su proveedor.

Por otra parte, una línea adicional de código es un muy pequeño precio a pagar :)

Cuestiones relacionadas