Spring Security를 ​​사용할 때 현재 사용자 이름을 얻는 적절한 방법은 무엇입니까(예:SecurityContext) 정보가 빈에 있습니까?

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

문제

Spring Security를 ​​사용하는 Spring MVC 웹 앱이 있습니다.현재 로그인한 사용자의 사용자 이름을 알고 싶습니다.아래에 제공된 코드 조각을 사용하고 있습니다.이것이 허용되는 방식입니까?

나는 이 컨트롤러 내에서 정적 메서드를 호출하는 것을 좋아하지 않습니다. 이는 IMHO, Spring의 전체 목적을 무너뜨립니다.대신 현재 SecurityContext 또는 현재 인증을 삽입하도록 앱을 구성하는 방법이 있나요?

  @RequestMapping(method = RequestMethod.GET)
  public ModelAndView showResults(final HttpServletRequest request...) {
    final String currentUser = SecurityContextHolder.getContext().getAuthentication().getName();
    ...
  }
도움이 되었습니까?

해결책

사용중인 경우 봄 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);
  }

}

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

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

그것은 그 봄, 모든 것의 의존성 주입 컨테이너 인 그 봄에 약간 어리석은 것 같아서 비슷한 것을 주입 할 수있는 방법을 제공하지 않았습니다. 이해합니다 SecurityContextHolder Acegi에서 물려 받았지만 여전히. 문제는 너무 가깝다는 것입니다. SecurityContextHolder 기초를 얻을 수있는 게터가있었습니다 SecurityContextHolderStrategy 인스턴스 (인터페이스),이를 주입 할 수 있습니다. 사실, 나는 심지어 Jira 문제를 열었습니다 그 효과에.

마지막으로 - 나는 전에 내가 여기에 있었던 대답을 실질적으로 바꾸었다. 궁금한 점이 있다면 역사를 확인하지만 동료가 나에게 지적했듯이, 나의 이전 대답은 다중 스레드 환경에서는 효과가 없을 것입니다. 기본 SecurityContextHolderStrategy 사용 SecurityContextHolder 기본적으로 인스턴스입니다 ThreadLocalSecurityContextHolderStrategy, 저장 SecurityContexts에서 s ThreadLocal. 따라서 반드시 주사하는 것이 좋은 생각은 아닙니다. SecurityContext 초기화 시간에 직접 콩으로 -에서 검색해야 할 수도 있습니다. 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의 의견에 언급 된 바와 같이, 추가해야합니다.

사용-표현 = "true"

보안에서 "http"태그에이를 작동하도록 구성하십시오.

Spring Security ver> = 3.2를 사용하는 경우 @AuthenticationPrincipal 주석:

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

여기, CustomUser 구현하는 사용자 정의 객체입니다 UserDetails 그것은 관습에 의해 반환됩니다 UserDetailsService.

자세한 내용은 @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";
    }
}

봄 3+에는 다음 옵션이 있습니다.

옵션 1 :

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

Option 2 :

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

옵션 3 : : 옵션 3.

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

}

옵션 4 : 공상 자 : 자세한 내용은이 정보를 확인하십시오

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

예, 정적은 일반적으로 나쁘지만 일반적 으로이 경우 정적은 가장 안전한 코드입니다. 보안 컨텍스트는 현재 실행중인 스레드와 교장을 연결하므로 가장 안전한 코드는 가능한 한 직접 스레드의 정적에 액세스 할 수 있습니다. 주입 된 래퍼 클래스 뒤에 액세스를 숨기면 공격자에게 더 많은 공격을 할 수 있습니다. 코드에 액세스 할 필요가 없습니다 (JAR에 서명 한 경우 변경하기가 어려울 것입니다). 런타임에 수행하거나 일부 XML을 클래스 경로로 미끄러 뜨릴 수있는 구성을 무시하는 방법 만 있으면됩니다. 서명 된 코드에서 주석 주입을 사용하더라도 외부 XML에서는 우선 할 수 있습니다. 이러한 XML은 Rogue Principal과 함께 실행 시스템을 주입 할 수 있습니다. 이것이 아마도 스프링 이이 경우에 그렇게 비 싱글 같은 일을하는 이유 일 것입니다.

나는 이것을 할 것이다 :

request.getRemoteUser();

내가 쓴 지난 봄 MVC 앱의 경우 SecurityContext 홀더를 주입하지는 않았지만 이와 관련된 두 가지 유틸리티 방법이있는 기본 컨트롤러가있었습니다. isauthenticated () & getUsername (). 내부적으로 그들은 당신이 설명한 정적 메소드 호출을합니다.

적어도 나중에 리팩터가 필요한 경우에만 한 번만 있습니다.

Spring AOP Aproach를 사용할 수 있습니다. 예를 들어 서비스가있는 경우 현재 교장을 알아야합니다. 사용자 정의 주석, 즉 @principal을 소개 할 수 있습니다.이 서비스는 주요 의존적이어야 함을 나타냅니다.

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

그런 다음 MethodbeForeadVice를 확장해야한다고 생각하는 조언에서 특정 서비스에 @principal 주석이 있는지 확인하고 기본 이름을 주입하거나 대신 '익명'으로 설정하십시오.

유일한 문제는 Spring Security로 인증한 후에도 사용자/주체 Bean이 컨테이너에 존재하지 않으므로 종속성 주입이 어렵다는 것입니다.Spring Security를 ​​사용하기 전에는 현재 Principal이 있는 세션 범위 Bean을 생성하고 이를 "AuthService"에 주입한 다음 해당 서비스를 애플리케이션의 다른 대부분의 서비스에 주입했습니다.따라서 해당 서비스는 단순히 authService.getCurrentUser()를 호출하여 객체를 가져옵니다.세션에서 동일한 Principal에 대한 참조를 얻는 코드 위치가 있는 경우 이를 세션 범위 Bean의 속성으로 간단히 설정할 수 있습니다.

이 시도

인증 인증 = SecurityContexTholder.getContext (). getAuthentication ();
문자열 username = augentication.getName ();

Spring 3을 사용하고 있고 컨트롤러의 인증 된 원금이 필요한 경우 가장 좋은 솔루션은 다음과 같은 작업을 수행하는 것입니다.

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

    }

나는 그것을 사용하고있다 @AuthenticationPrincipal 주석 @Controller 수업뿐만 아니라 수업 @ControllerAdvicer 주석이 달린 것. 전.:

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


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

어디에 UserActive 로그인 사용자 서비스에 사용하는 클래스이며 org.springframework.security.core.userdetails.User. 같은 것 :

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
}

정말 쉽습니다.

정의하다 Principal 컨트롤러 메소드의 종속성으로 Spring은 호출시 메소드에서 현재 인증 된 사용자를 주입합니다.

프리 마커 페이지에서 사용자 세부 정보를 지원하는 방법을 공유하고 싶습니다. 모든 것이 매우 간단하고 완벽하게 작동합니다!

인증 재주문을 배치하면됩니다 default-target-url (양식-로그 후 페이지) 이것은 해당 페이지의 제 컨트롤 메소드입니다.

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

그리고 이것은 내 FTL 코드입니다.

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

그리고 그것이 바로, 권한이 부여 된 후 모든 페이지에 사용자 이름이 나타납니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top