Ao utilizar Spring Security, que é a maneira correta de obter o usuário atual (ou seja SecurityContext) informações em um bean?

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

Pergunta

Eu tenho um aplicativo web Spring MVC que usa Spring Security. Eu quero saber o nome de usuário do usuário conectado no momento. Estou usando o fragmento de código dado abaixo. É este o caminho aceita?

Eu não gosto de ter uma chamada para um método estático dentro deste controlador - que derrota o propósito de Primavera, IMHO. Existe uma maneira de configurar o aplicativo para que o atual SecurityContext, ou autenticação atual, injetado em vez disso?

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

Solução

Se você estiver usando Spring 3 , a maneira mais fácil é:

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

     final String currentUser = principal.getName();

 }

Outras dicas

Muita coisa mudou no mundo da Primavera uma vez que esta pergunta foi respondida. Primavera simplificou obter o usuário atual em um controlador. Para outros feijões, Primavera adotou as sugestões do autor e simplificou a injeção de 'SecurityContextHolder'. Mais detalhes estão nos comentários.


Esta é a solução que eu acabei indo com. Em vez de usar SecurityContextHolder no meu controlador, eu quero injetar algo que usa SecurityContextHolder sob o capô, mas abstrai que Singleton-like classe do meu código. Eu encontrei nenhuma maneira de fazer isso que não rolar minha própria interface, assim:

public interface SecurityContextFacade {

  SecurityContext getContext();

  void setContext(SecurityContext securityContext);

}

Agora, meu controlador (ou qualquer POJO) ficaria assim:

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
  }

}

E, por causa da interface de ser um ponto de desacoplamento, o teste de unidade é simples. Neste exemplo, eu uso 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();
  }

}

A implementação padrão dos olhares de interface como este:

public class SecurityContextHolderFacade implements SecurityContextFacade {

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

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

}

E, finalmente, a produção da Primavera de configuração esta aparência:

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

Parece mais do que um pouco bobo que Spring, um contêiner de injeção de dependência de todas as coisas, não forneceu uma maneira de algo injetar similar. Eu entendo SecurityContextHolder foi herdada de Acegi, mas ainda assim. A coisa é, eles estão tão perto - mesmo que apenas SecurityContextHolder tinha um getter para obter a instância SecurityContextHolderStrategy subjacente (que é uma interface), você pode injetar isso. Na verdade, eu mesmo abriu uma questão Jira para esse efeito.

Uma última coisa - eu mudei apenas substancialmente a resposta que eu tinha aqui antes. Verifique o histórico se você estiver curioso, mas, como um colega de trabalho apontou para mim, a minha resposta anterior não iria trabalhar em um ambiente multi-threaded. O SecurityContextHolderStrategy subjacente usado por SecurityContextHolder é, por padrão, uma instância de ThreadLocalSecurityContextHolderStrategy, que armazena SecurityContexts em um ThreadLocal. Portanto, não é necessariamente uma boa idéia para injetar o SecurityContext diretamente em um bean em tempo de inicialização - ele pode precisar de ser recuperado do ThreadLocal cada vez, em um ambiente multi-threaded, então o correto é recuperado

Eu concordo que ter que consultar o SecurityContext para os fede usuário atuais, parece uma maneira muito un-Spring para lidar com este problema.

Eu escrevi uma classe estática "helper" para lidar com este problema; É sujo, em que é um método global e estática, mas eu percebi que desta forma se mudar qualquer coisa relacionada à segurança, pelo menos, eu só tenho que mudar os detalhes em um só 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 torná-lo simplesmente aparecer em suas páginas JSP, você pode usar o Tag Spring Security Lib:

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

Para usar qualquer uma das marcas, você deve ter o taglib segurança declarou em sua JSP:

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

Então, em uma página jsp fazer algo como isto:

<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 mencionado nos comentários por @ SBerg413, você vai precisar adicionar

Utilize-expressões = "true"

a tag "http" na configuração security.xml para que isso funcione.

Se você estiver usando Spring Security ver> = 3.2, você pode usar a anotação @AuthenticationPrincipal:

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

Aqui, CustomUser é um objeto personalizado que implementa UserDetails que é devolvido por um UserDetailsService personalizado.

Mais informação pode ser encontrada na @ AuthenticationPrincipal capítulo dos documentos de referência do Spring Security.

Eu fico usuário autenticado por HttpServletRequest.getUserPrincipal ();

Exemplo:

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

Na Primavera 3+ você tem as seguintes opções.

Opção 1:

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

Opção 2:

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

Opção 3:

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

}

Opção 4: Fantasia One: Veja isto para mais detalhes

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

Sim, estática são geralmente ruim - geralmente, mas neste caso, a estática é o código mais seguro você pode escrever. Desde o contexto de segurança associados um principal com o segmento em execução no momento, o código mais seguro seria acessar a estática do fio o mais diretamente possível. Escondendo o acesso por trás de uma classe de invólucro que é injectado fornece um intruso com mais pontos de ataque. Eles não precisam de ter acesso ao código (que eles teriam dificuldade em mudar se o frasco foi assinado), eles só precisam de uma maneira para substituir a configuração, que pode ser feito em tempo de execução ou escorregar alguns XML para o classpath. Mesmo usando injeção de anotação no código assinado seria substituível com XML externo. Tal XML pode injetar o sistema funcionando com um principal desonestos. Isto é provavelmente porque Primavera está fazendo algo tão un-Spring-como neste caso.

Gostaria apenas de fazer isso:

request.getRemoteUser();

Para o último Spring MVC aplicativo que eu escrevi, eu não injetar titular da SecurityContext, mas eu tinha um controlador de base que eu tinha dois métodos utilitários relacionados a este ... IsAuthenticated () & getUsername (). Internamente eles fazem a chamada de método estático que você descreveu.

Pelo menos, então é só uma vez lugar se você precisa refatorar mais tarde.

Você pode usar Spring AOP aproach. Por exemplo, se você tem algum serviço, que as necessidades de saber principal atual. Você poderia introduzir personalizado anotação ou seja @Principal, que indicam que este serviço deve ser o principal dependente.

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

Então, em seu conselho, que eu acho que precisa para estender MethodBeforeAdvice, verifique se o serviço particular tem anotação @Principal e injetar nome principal, ou defini-lo como 'anónimo' em seu lugar.

O único problema é que, mesmo depois de autenticar com Spring Security, o usuário / bean principal não existe no recipiente, assim dependência injetar será difícil. Antes de utilizar Spring Security que iria criar um bean com escopo de sessão que teve a corrente principal, injetar isso em um "AuthService" e, em seguida, injetar esse serviço na maioria dos outros serviços no Application. Então, esses serviços seria simplesmente chamar authService.getCurrentUser () para obter o objeto. Se você tem um lugar em seu código onde você obter uma referência para o mesmo princípio na sessão, você pode simplesmente defini-lo como uma propriedade em seu bean com escopo de sessão.

Tente este

Autenticação de autenticação = . SecurityContextHolder.getContext () getAuthentication ();
Corda nomeUsuário = authentication.getName ();

A melhor solução se você estiver usando Spring 3 e precisam da autenticado principais no seu controlador é fazer algo parecido com isto:

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();
            }
            ...

    }

Eu estou usando a anotação @AuthenticationPrincipal nas aulas @Controller, bem como nos mais @ControllerAdvicer anotada. Ex:.

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


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

Onde UserActive é a classe que eu uso para serviços de usuários conectados, e se estende 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.

Definir Principal como uma dependência em seu método de controlador e na primavera vai injetar o usuário autenticado atual em seu método de invocação.

Eu gosto de partilhar a minha forma de apoiar os detalhes do usuário na página freemarker. Tudo é muito simples e funcionando perfeitamente!

Você apenas tem que colocar Authentication rerequest em default-target-url (página após form-login) Este é o meu método Controler para essa 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");
}

E este é o meu código FTL:

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

E é isso, nome de usuário aparecerá em cada página após a autorização.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top