tutorial example ejemplo java spring spring-security

java - example - spring security xml



Implemente AuthenticationProvider personalizado en Spring Security 2.06 (2)

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

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

Según entiendo, el caso de uso típico de Spring Security usa 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 permitir 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>

Para resumir, esto es lo que necesito:

  1. El usuario inicia sesión a través de un formulario web
  2. Autentica el usuario usando la API interna de administración de usuarios
  3. Para usuarios autenticados con éxito, complete GrantedAuthories, etc.
  4. Devolver una entidad de usuario que contenga roles / autoridades y otros atributos como correo electrónico, nombre, etc. Debería poder 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!

Actualizar:

Creo que hice algunos progresos.

Tengo 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 actualicé 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 devuelvo un objeto UsernamePasswordAuthenticationToken, pero si intento devolver CustomAuthentication, 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)

Es como si algo estuviera esperando no solo cualquier objeto de Autenticación, sino una implementación específica de él: UsernamePasswordAuthenticationToken. Esto me hace pensar que me puede estar perdiendo otro componente personalizado ... ¿tal vez un filtro?


Si está implementando su propio AuthenticationProvider , no tiene que implementar un servicio 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 usando un nombre de usuario y contraseña, debe instanciar un DaoAuthenticationProvider e inyectarlo con un 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í?" comenta en tu 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 del usuario será accesible usando el

Authentication.getPrincipal()

método, y puede acceder a las propiedades adicionales (correo electrónico, etc.) transfiriéndolo a 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 las contraseñas hash y validar la contraseña proporcionada utilizando el mismo algoritmo, en lugar de una simple comprobación de igualdad.


gracias por publicar este Luke!

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

La única cosa de la nota que encontré, para cualquiera que se preocupe:

Mi configuración:

  • Grails 2.0.4
  • Groovy 1.8
  • spring-security-core 1.2.7.3
  • spring-security-ui 0.2
  • hibernar 2.0.4

Al utilizar el muy apreciado enfoque simplificado / elegante que Luke sugiere, NO implementando un objeto UserDetails (o UserDetailsService) personalizado, y usando su propio objeto de dominio de usuario que no se extiende a nada especial, debe dar un paso adicional si está utilizando el " seg "etiquetas personalizadas de seguridad de primavera (en sus páginas, por supuesto):

Cuando instancia un UsernamePasswordAuthenticationToken básico y no personalizado, DEBE pasarle una instancia de algo que amplíe Principal, nuevamente, si quiere que funcionen las etiquetas de brecha personalizadas de seguridad de primavera. Hice algo como esto, para mantenerlo lo más simple posible (haciendo referencia a los valores de mis objetos de dominio de usuario donde sea útil / apropiado):

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 debería satisfacer las condiciones probadas en grails.plugins.springsecurity.SecurityTagLib.determineSource () así que, ya sabes, las páginas que usan <sec:loggedInUserInfo> realidad representarán:

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

De lo contrario, si crea una instancia de UsernamePasswordAuthenticationToken con su objeto de dominio User (como lo muestra Luke en su ejemplo), ese método lib lib de seguridad (determineSource ()) lo hará mejor y devolverá el valor (meta) de org.codehaus.groovy .grails.commons.DefaultGrailsDomainClass y obtendrá un error cuando la etiqueta busque la variable de miembro de nombre de usuario que indica:

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

En lugar de volver a implementar / subclasificar los taglibs de plugin spring-security-core en mi proyecto Grails, simplemente no hay forma de que ambos utilicen los taglibs Y usar su clase de usuario de dominio personalizado para instanciar el token que pasa de su filtro a su proveedor.

Por otra parte, una línea adicional de código es un precio muy bajo para pagar :)