Cuando se utiliza Spring Security, ¿cuál es la forma correcta de obtener la información actual del nombre de usuario (es decir, SecurityContext) en un bean?

StackOverflow https://stackoverflow.com/questions/248562

Pregunta

Tengo una aplicación web Spring MVC que utiliza Spring Security. Quiero saber el nombre de usuario del usuario conectado actualmente. Estoy usando el fragmento de código dado a continuación. ¿Es esta la forma aceptada?

No me gusta hacer una llamada a un método estático dentro de este controlador, que anula todo el propósito de Spring, IMHO. ¿Hay alguna forma de configurar la aplicación para que se inyecte el SecurityContext o la Autenticación actual?

  @RequestMapping(method = RequestMethod.GET)
  public ModelAndView showResults(final HttpServletRequest request...) {
    final String currentUser = SecurityContextHolder.getContext().getAuthentication().getName();
    ...
  }
¿Fue útil?

Solución

Si está utilizando Spring 3 , la forma más fácil es:

 @RequestMapping(method = RequestMethod.GET)   
 public ModelAndView showResults(final HttpServletRequest request, Principal principal) {

     final String currentUser = principal.getName();

 }

Otros consejos

Muchas cosas han cambiado en el mundo de Spring desde que se respondió esta pregunta. Spring ha simplificado la obtención del usuario actual en un controlador. Para otros frijoles, Spring adoptó las sugerencias del autor y simplificó la inyección de 'SecurityContextHolder'. Más detalles están en los comentarios.


Esta es la solución con la que terminé yendo. En lugar de usar SecurityContextHolder en mi controlador, quiero inyectar algo que use SecurityContextHolder bajo el capó, pero aleja esa clase de código único de mi código. No he encontrado otra forma de hacer esto que no sea rodar mi propia interfaz, de esta forma:

public interface SecurityContextFacade {

  SecurityContext getContext();

  void setContext(SecurityContext securityContext);

}

Ahora, mi controlador (o lo que sea POJO) se vería así:

public class FooController {

  private final SecurityContextFacade securityContextFacade;

  public FooController(SecurityContextFacade securityContextFacade) {
    this.securityContextFacade = securityContextFacade;
  }

  public void doSomething(){
    SecurityContext context = securityContextFacade.getContext();
    // do something w/ context
  }

}

Y, debido a que la interfaz es un punto de desacoplamiento, la prueba de la unidad es sencilla. En este ejemplo utilizo Mockito:

public class FooControllerTest {

  private FooController controller;
  private SecurityContextFacade mockSecurityContextFacade;
  private SecurityContext mockSecurityContext;

  @Before
  public void setUp() throws Exception {
    mockSecurityContextFacade = mock(SecurityContextFacade.class);
    mockSecurityContext = mock(SecurityContext.class);
    stub(mockSecurityContextFacade.getContext()).toReturn(mockSecurityContext);
    controller = new FooController(mockSecurityContextFacade);
  }

  @Test
  public void testDoSomething() {
    controller.doSomething();
    verify(mockSecurityContextFacade).getContext();
  }

}

La implementación predeterminada de la interfaz se ve así:

public class SecurityContextHolderFacade implements SecurityContextFacade {

  public SecurityContext getContext() {
    return SecurityContextHolder.getContext();
  }

  public void setContext(SecurityContext securityContext) {
    SecurityContextHolder.setContext(securityContext);
  }

}

Y, finalmente, la configuración de primavera de producción se ve así:

<bean id="myController" class="com.foo.FooController">
     ...
  <constructor-arg index="1">
    <bean class="com.foo.SecurityContextHolderFacade">
  </constructor-arg>
</bean>

Parece más que un poco tonto que Spring, un contenedor de inyección de dependencia de todas las cosas, no haya proporcionado una forma de inyectar algo similar. Entiendo que SecurityContextHolder fue heredado de acegi, pero aún así. El problema es que están tan cerca. Si solo SecurityContextHolder tuviera un getter para obtener la instancia subyacente de SecurityContextHolderStrategy (que es una interfaz), podría inyectar eso. De hecho, incluso abrí un problema de Jira a tal efecto.

Una última cosa: acabo de cambiar sustancialmente la respuesta que tenía aquí antes. Verifique el historial si tiene curiosidad pero, como me comentó un compañero de trabajo, mi respuesta anterior no funcionaría en un entorno de múltiples subprocesos. La SecurityContextHolderStrategy subyacente utilizada por SecurityContextHolder es, por defecto, una instancia de ThreadLocalSecurityContextHolderStrategy , que almacena SecurityContext en un ThreadLocal . Por lo tanto, no es necesariamente una buena idea inyectar el SecurityContext directamente en un bean en el momento de la inicialización; es posible que deba ser recuperado del ThreadLocal cada vez, en múltiples - Entorno roscado, por lo que se recupera el correcto.

Estoy de acuerdo en que tener que consultar a SecurityContext por el usuario actual apesta, parece una forma muy poco inteligente de manejar este problema.

Escribí una estática " helper " clase para hacer frente a este problema; está sucio porque es un método global y estático, pero me di cuenta de que si cambiamos algo relacionado con la Seguridad, al menos solo tengo que cambiar los detalles en un lugar:

/**
* Returns the domain User object for the currently logged in user, or null
* if no User is logged in.
* 
* @return User object for the currently logged in user, or null if no User
*         is logged in.
*/
public static User getCurrentUser() {

    Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal()

    if (principal instanceof MyUserDetails) return ((MyUserDetails) principal).getUser();

    // principal object is either null or represents anonymous user -
    // neither of which our domain User object can represent - so return null
    return null;
}


/**
 * Utility method to determine if the current user is logged in /
 * authenticated.
 * <p>
 * Equivalent of calling:
 * <p>
 * <code>getCurrentUser() != null</code>
 * 
 * @return if user is logged in
 */
public static boolean isLoggedIn() {
    return getCurrentUser() != null;
}

Para que aparezca en las páginas de JSP, puedes usar Spring Security Tag Lib:

http://static.springsource.org/spring-security /site/docs/3.0.x/reference/taglibs.html

Para usar cualquiera de las etiquetas, debe tener la etiqueta de seguridad etiquetada en su JSP:

<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>

Luego, en una página jsp, haga algo como esto:

<security:authorize access="isAuthenticated()">
    logged in as <security:authentication property="principal.username" /> 
</security:authorize>

<security:authorize access="! isAuthenticated()">
    not logged in
</security:authorize>

NOTA: Como se menciona en los comentarios de @ SBerg413, deberá agregar

  

use-expressions = " true "

al " http " etiqueta en la configuración security.xml para que esto funcione.

Si está usando Spring Security ver > = 3.2, puede usar la anotación @AuthenticationPrincipal :

@RequestMapping(method = RequestMethod.GET)
public ModelAndView showResults(@AuthenticationPrincipal CustomUser currentUser, HttpServletRequest request) {
    String currentUsername = currentUser.getUsername();
    // ...
}

Aquí, CustomUser es un objeto personalizado que implementa UserDetails que es devuelto por un UserDetailsService personalizado.

Puede encontrar más información en @ AuthenticationPrincipal capítulo de los documentos de referencia de Spring Security.

Obtengo usuario autenticado por HttpServletRequest.getUserPrincipal ();

Ejemplo:

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.support.RequestContext;

import foo.Form;

@Controller
@RequestMapping(value="/welcome")
public class IndexController {

    @RequestMapping(method=RequestMethod.GET)
    public String getCreateForm(Model model, HttpServletRequest request) {

        if(request.getUserPrincipal() != null) {
            String loginName = request.getUserPrincipal().getName();
            System.out.println("loginName : " + loginName );
        }

        model.addAttribute("form", new Form());
        return "welcome";
    }
}

En Spring 3+ tienes las siguientes opciones.

Opción 1:

@RequestMapping(method = RequestMethod.GET)    
public String currentUserNameByPrincipal(Principal principal) {
    return principal.getName();
}

Opción 2:

@RequestMapping(method = RequestMethod.GET)
public String currentUserNameByAuthentication(Authentication authentication) {
    return authentication.getName();
}

Opción 3:

@RequestMapping(method = RequestMethod.GET)    
public String currentUserByHTTPRequest(HttpServletRequest request) {
    return request.getUserPrincipal().getName();

}

Opción 4: Fancy one: Consulte esto para obtener más detalles

public ModelAndView someRequestHandler(@ActiveUser User activeUser) {
  ...
}

Sí, las estadísticas están mal, generalmente, pero en este caso, la estática es el código más seguro que puede escribir. Dado que el contexto de seguridad asocia un Principal con el subproceso que se está ejecutando actualmente, el código más seguro accederá a la estática desde el subproceso lo más directamente posible. Ocultar el acceso detrás de una clase de envoltorio que se inyecta proporciona a un atacante más puntos para atacar. No necesitarían acceso al código (que les sería difícil cambiar si se firmara el archivo jar), solo necesitan una forma de anular la configuración, que se puede hacer en tiempo de ejecución o deslizando algunos XML en el classpath. Incluso el uso de la inyección de anotación en el código firmado sería reemplazable con XML externo. Dicho XML podría inyectar el sistema en ejecución con un principal deshonesto. Probablemente esta sea la razón por la que Spring está haciendo algo tan poco parecido a Spring en este caso.

Simplemente haría esto:

request.getRemoteUser();

Para la última aplicación Spring MVC que escribí, no inyecté el titular de SecurityContext, pero sí tenía un controlador base que tenía dos métodos de utilidad relacionados con esto ... isAuthenticated () & amp; getUsername (). Internamente hacen el método estático llamado que describiste.

Al menos entonces solo está en un solo lugar si necesitas refactorizar más tarde.

Podrías usar el enfoque de Spring AOP. Por ejemplo, si tiene algún servicio, debe conocer el principal actual. Podría introducir una anotación personalizada, es decir, @Principal, que indica que este Servicio debe ser dependiente del principal.

public class SomeService {
    private String principal;
    @Principal
    public setPrincipal(String principal){
        this.principal=principal;
    }
}

Luego, en su consejo, que creo que necesita extender MethodBeforeAdvice, verifique que el servicio en particular tenga @Principal anotación e inyecte el nombre del director, o configúrelo en "ANONYMOUS" en su lugar.

El único problema es que incluso después de autenticarse con Spring Security, el usuario / bean principal no existe en el contenedor, por lo que la inyección de dependencia será difícil. Antes de usar Spring Security, crearíamos un bean de ámbito de sesión que tenía el Principal actual, inyectarlo en un " AuthService " y luego inyecte ese Servicio en la mayoría de los otros servicios en la Aplicación. Así que esos Servicios simplemente llamarían a authService.getCurrentUser () para obtener el objeto. Si tiene un lugar en su código donde obtiene una referencia al mismo Principal en la sesión, simplemente puede establecerlo como una propiedad en su bean de ámbito de sesión.

Prueba esto

  

Autenticación de autenticación =   SecurityContextHolder.getContext (). GetAuthentication ();
  String userName = authentication.getName ();

La mejor solución si usa Spring 3 y necesita el principal autenticado en su controlador es hacer algo como esto:

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;

    @Controller
    public class KnoteController {
        @RequestMapping(method = RequestMethod.GET)
        public java.lang.String list(Model uiModel, UsernamePasswordAuthenticationToken authToken) {

            if (authToken instanceof UsernamePasswordAuthenticationToken) {
                user = (User) authToken.getPrincipal();
            }
            ...

    }

Estoy utilizando la anotación @AuthenticationPrincipal en las clases @Controller así como en las anotadas @ControllerAdvicer . Ej .:

@ControllerAdvice
public class ControllerAdvicer
{
    private static final Logger LOGGER = LoggerFactory.getLogger(ControllerAdvicer.class);


    @ModelAttribute("userActive")
    public UserActive currentUser(@AuthenticationPrincipal UserActive currentUser)
    {
        return currentUser;
    }
}

Donde UserActive es la clase que uso para los servicios de usuarios registrados y se extiende desde org.springframework.security.core.userdetails.User . Algo como:

public class UserActive extends org.springframework.security.core.userdetails.User
{

    private final User user;

    public UserActive(User user)
    {
        super(user.getUsername(), user.getPasswordHash(), user.getGrantedAuthorities());
        this.user = user;
    }

     //More functions
}

Realmente fácil.

Defina Principal como una dependencia en su método de control y Spring inyectará al usuario autenticado actual en su método en la invocación.

Me gusta compartir mi manera de apoyar los detalles de los usuarios en la página de freemarker. ¡Todo es muy simple y funciona perfectamente!

Solo tiene que colocar la solicitud de autenticación en default-target-url (página después del inicio de sesión del formulario) Este es mi método de Controler para esa página:

@RequestMapping(value = "/monitoring", method = RequestMethod.GET)
public ModelAndView getMonitoringPage(Model model, final HttpServletRequest request) {
    showRequestLog("monitoring");


    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    String userName = authentication.getName();
    //create a new session
    HttpSession session = request.getSession(true);
    session.setAttribute("username", userName);

    return new ModelAndView(catalogPath + "monitoring");
}

Y este es mi código ftl:

<@security.authorize ifAnyGranted="ROLE_ADMIN, ROLE_USER">
<p style="padding-right: 20px;">Logged in as ${username!"Anonymous" }</p>
</@security.authorize> 

Y eso es todo, el nombre de usuario aparecerá en cada página después de la autorización.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top