[java] Spring Security를 ​​사용할 때 Bean에서 현재 사용자 이름 (예 : SecurityContext) 정보를 얻는 올바른 방법은 무엇입니까?

Spring Security를 ​​사용하는 Spring MVC 웹 앱이 있습니다. 현재 로그인 한 사용자의 사용자 이름을 알고 싶습니다. 아래 주어진 코드 스 니펫을 사용하고 있습니다. 이것이 허용되는 방법입니까?

나는이 컨트롤러 내에서 정적 메소드를 호출하는 것을 좋아하지 않는다. 대신 현재 SecurityContext 또는 현재 인증을 주입하도록 앱을 구성하는 방법이 있습니까?

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



답변

Spring 3을 사용하는 경우 가장 쉬운 방법은 다음과 같습니다.

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

     final String currentUser = principal.getName();

 }


답변

이 질문에 대한 답을 얻은 후 봄 세계에서 많은 변화가있었습니다. Spring은 컨트롤러에서 현재 사용자를 얻는 것을 단순화했습니다. 다른 빈의 경우 Spring은 저자의 제안을 채택하고 ‘SecurityContextHolder’의 주입을 단순화했습니다. 자세한 내용은 의견에 있습니다.


이것이 내가 끝낸 해결책입니다. SecurityContextHolder내 컨트롤러에서 사용하는 대신 SecurityContextHolder후드에서 사용하지만 코드에서 단일 클래스와 같은 클래스를 추상화 하는 것을 주입하고 싶습니다. 내 인터페이스를 롤링하는 것 외에는 이렇게 할 수있는 방법이 없습니다.

public interface SecurityContextFacade {

  SecurityContext getContext();

  void setContext(SecurityContext securityContext);

}

이제 내 컨트롤러 (또는 POJO)는 다음과 같습니다.

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
  }

}

또한 인터페이스가 디커플링 지점이되기 때문에 단위 테스트가 간단합니다. 이 예에서는 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();
  }

}

인터페이스의 기본 구현은 다음과 같습니다.

public class SecurityContextHolderFacade implements SecurityContextFacade {

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

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

}

마지막으로 프로덕션 Spring 구성은 다음과 같습니다.

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

모든 것의 의존성 주입 컨테이너 인 Spring이 비슷한 것을 주입하는 방법을 제공하지 않았다는 것은 조금 어리석은 것처럼 보입니다. 나는 SecurityContextHolderacegi에서 상속되었지만 여전히 여전히 이해 합니다. 문제는, 그것들이 너무 가깝다는 것입니다- SecurityContextHolder기본 SecurityContextHolderStrategy인스턴스 (인터페이스) 를 얻는 게터 만 있다면 그것을 주입 할 수 있습니다. 사실, 나는 심지어 그 효과에 대한 Jira 문제열었습니다 .

마지막 한 가지-나는 이전에 내가 가진 대답을 크게 바 꾸었습니다. 궁금한 점이 있다면 역사를 확인하십시오.하지만 동료가 지적했듯이 이전 답변은 멀티 스레드 환경에서 작동하지 않습니다. 기본적으로 SecurityContextHolderStrategy사용되는 SecurityContextHolder기본은의 인스턴스이며 ThreadLocalSecurityContextHolderStrategy를에 저장 SecurityContext합니다 ThreadLocal. 따라서 SecurityContext초기화 시간에 Bean에 직접 직접 삽입하는 것이 좋은 아이디어는 아닙니다 ThreadLocal. 멀티 스레드 환경에서 매번 검색해야 할 수 있으므로 올바른 것을 검색해야합니다.


답변

현재 사용자의 악취에 대해 SecurityContext를 쿼리해야한다는 점에 동의합니다.이 문제를 처리하는 매우 봄의 방법이 아닙니다.

이 문제를 해결하기 위해 정적 “헬퍼”클래스를 작성했습니다. 전역적이고 정적 인 방법이라는 것은 더럽지 만 보안과 관련된 것을 변경하면 적어도 한 곳에서만 세부 정보를 변경해야한다고 생각합니다.

/**
* 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;
}


답변

JSP 페이지에 표시되도록 Spring Security Tag Lib을 사용할 수 있습니다.

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

태그를 사용하려면 JSP에 보안 taglib가 선언되어 있어야합니다.

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

그런 다음 jsp 페이지에서 다음과 같이하십시오.

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

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

참고 : @ SBerg413의 의견에서 언급했듯이 추가해야합니다.

use-expressions = “true”

이것이 작동하려면 security.xml 구성의 “http”태그에 추가하십시오.


답변

Spring Security ver> = 3.2를 사용하는 경우 @AuthenticationPrincipal주석을 사용할 수 있습니다 .

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

여기, CustomUsercustom에 UserDetails의해 반환되는 것을 구현하는 커스텀 객체가 UserDetailsService있습니다.

자세한 정보는 Spring Security 참조 문서 의 @AuthenticationPrincipal 장 에서 찾을 수 있습니다 .


답변

HttpServletRequest.getUserPrincipal ()에 의해 인증 된 사용자를 얻습니다.

예:

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


답변

Spring 3+에는 다음과 같은 옵션이 있습니다.

옵션 1 :

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

옵션 2 :

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

옵션 3 :

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

}

옵션 4 : 팬시 원 : 자세한 내용은 이것을 확인하십시오

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