복붙노트

[SPRING] 스프링 보안을 이용한 유닛 테스트

SPRING

스프링 보안을 이용한 유닛 테스트

우리 회사는 다음 프로젝트 중 하나에서 사용해야 하는지를 결정하기 위해 Spring MVC를 평가 해 왔습니다. 지금까지 내가 본 것을 좋아하고, 지금은 봄 보안 모듈을 살펴봄으로써 우리가 사용할 수 있거나 사용할 수 있는지를 결정합니다.

우리의 보안 요구 사항은 매우 기본입니다. 사용자는 사이트의 특정 부분에 액세스 할 수 있도록 사용자 이름과 비밀번호를 제공 할 수 있어야합니다 (예 : 계정 정보 얻기). 익명 사용자에게 액세스 권한을 부여해야하는 사이트 (FAQ, 지원 등)가 몇 페이지 있습니다.

내가 만든 프로토 타입에서는 인증 된 사용자의 Session에 "LoginCredentials"개체 (사용자 이름과 암호 만 포함)를 저장하고 있습니다. 일부 컨트롤러는이 개체가 세션에 있는지 확인하여 로그인 된 사용자 이름에 대한 참조를 가져옵니다. 나는이 자국어 논리를 대신 Spring Security로 대체하고자합니다. "로그인 한 사용자를 어떻게 추적합니까?" "사용자를 어떻게 인증합니까?" 내 컨트롤러 / 비즈니스 코드에서.

그것은 봄 보안 (응용 프로그램에서 어디서나 사용자 이름 / 교장 정보에 액세스 할 수 있도록 스레드 당) "컨텍스트"개체를 제공하는 것 ...

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

...이 개체는 마치 (글로벌) 싱글 톤과 같이 매우 유엔처럼 보입니다.

제 질문은 이것입니다 : 이것이 Spring Security의 인증 된 사용자에 대한 정보에 액세스하는 표준 방법이라면 Authentication 객체를 SecurityContext에 삽입하여 단위 테스트에서 유닛 테스트에 사용할 수 있도록 허용되는 방법은 무엇입니까? 인증 된 사용자입니까?

각 테스트 케이스의 초기화 메소드에서 이것을 연결해야합니까?

protected void setUp() throws Exception {
    ...
    SecurityContextHolder.getContext().setAuthentication(
        new UsernamePasswordAuthenticationToken(testUser.getLogin(), testUser.getPassword()));
    ...
}

이것은 지나치게 자세한 것으로 보입니다. 더 쉬운 방법이 있습니까?

SecurityContextHolder 객체 자체는 매우 유엔처럼 보인다 ...

해결법

  1. ==============================

    1.문제는 Spring Security가 Authentication 객체를 컨테이너의 빈처럼 사용할 수 없도록하기 때문에 상자 밖으로 쉽게 주입하거나 autowire 할 수있는 방법이 없다는 것입니다.

    문제는 Spring Security가 Authentication 객체를 컨테이너의 빈처럼 사용할 수 없도록하기 때문에 상자 밖으로 쉽게 주입하거나 autowire 할 수있는 방법이 없다는 것입니다.

    Spring Security를 ​​사용하기 전에 컨테이너에 세션 범위 bean을 만들어 Principal을 저장하고이를 "AuthenticationService"(singleton)에 주입 한 다음이 bean을 현재 Principal에 대한 지식이 필요한 다른 서비스에 주입합니다.

    자체 인증 서비스를 구현하는 경우 기본적으로 동일한 작업을 수행 할 수 있습니다. "주체"속성을 사용하여 세션 범위의 Bean을 만들고이를 인증 서비스에 주입하고 인증 서비스가 성공적인 인증에 대한 속성을 설정하도록 한 다음 필요에 따라 인증 서비스를 다른 Bean이 사용할 수있게하십시오.

    SecurityContextHolder를 사용하는 것에 대해 나쁘지 않을 것입니다. 그래도. 나는 그것이 정적 / 싱글 톤이라는 것을 알고 Spring은 그런 것들을 사용하는 것을 꺼린다.하지만 그 구현은 환경에 따라 적절하게 행동한다 : 서블릿 컨테이너에서 세션 스코프, JUnit 테스트에서 쓰레드 스코프 등 실질적인 제한 요소 싱글 톤은 다른 환경에 유연하지 못한 구현을 제공 할 때입니다.

  2. ==============================

    2.그냥 일반적인 방법으로 다음 테스트 클래스에서 SecurityContextHolder.setContext ()를 사용하여 삽입하십시오. 예를 들면 다음과 같습니다.

    그냥 일반적인 방법으로 다음 테스트 클래스에서 SecurityContextHolder.setContext ()를 사용하여 삽입하십시오. 예를 들면 다음과 같습니다.

    제어 장치:

    Authentication a = SecurityContextHolder.getContext().getAuthentication();
    

    테스트:

    Authentication authentication = Mockito.mock(Authentication.class);
    // Mockito.whens() for your authorization object
    SecurityContext securityContext = Mockito.mock(SecurityContext.class);
    Mockito.when(securityContext.getAuthentication()).thenReturn(authentication);
    SecurityContextHolder.setContext(securityContext);
    
  3. ==============================

    3.정적 메서드 호출은 단위 테스트에서 특히 문제가됩니다. 종속성을 쉽게 모방 할 수 없기 때문입니다. 제가 보여 드릴 내용은 Spring IoC 컨테이너가 당신을 위해 더러운 작업을 수행하도록하는 방법입니다. SecurityContextHolder는 프레임 워크 클래스이며 낮은 수준의 보안 코드를 연결하는 것이 좋지만 UI 구성 요소 (예 : 컨트롤러)에 더 깔끔한 인터페이스를 제공하고자 할 수 있습니다.

    정적 메서드 호출은 단위 테스트에서 특히 문제가됩니다. 종속성을 쉽게 모방 할 수 없기 때문입니다. 제가 보여 드릴 내용은 Spring IoC 컨테이너가 당신을 위해 더러운 작업을 수행하도록하는 방법입니다. SecurityContextHolder는 프레임 워크 클래스이며 낮은 수준의 보안 코드를 연결하는 것이 좋지만 UI 구성 요소 (예 : 컨트롤러)에 더 깔끔한 인터페이스를 제공하고자 할 수 있습니다.

    cliff.meyers는 한 가지 방법을 언급했습니다. 자신의 "principal"유형을 만들고 인스턴스를 소비자에게 주입합니다. 2.x에서 소개 된 Spring 태그는 request scope bean 정의와 결합되어 있으며, factory-method 지원은 가장 읽기 쉬운 코드의 티켓 일 수 있습니다.

    다음과 같이 작동 할 수 있습니다.

    public class MyUserDetails implements UserDetails {
        // this is your custom UserDetails implementation to serve as a principal
        // implement the Spring methods and add your own methods as appropriate
    }
    
    public class MyUserHolder {
        public static MyUserDetails getUserDetails() {
            Authentication a = SecurityContextHolder.getContext().getAuthentication();
            if (a == null) {
                return null;
            } else {
                return (MyUserDetails) a.getPrincipal();
            }
        }
    }
    
    public class MyUserAwareController {        
        MyUserDetails currentUser;
    
        public void setCurrentUser(MyUserDetails currentUser) { 
            this.currentUser = currentUser;
        }
    
        // controller code
    }
    

    지금까지 복잡한 것은 없습니다. 실제로 당신은 아마 이것의 대부분을 이미해야만했습니다. 다음으로, Bean 컨텍스트에서 주체를 보유 할 요청 범위의 Bean을 정의하십시오.

    <bean id="userDetails" class="MyUserHolder" factory-method="getUserDetails" scope="request">
        <aop:scoped-proxy/>
    </bean>
    
    <bean id="controller" class="MyUserAwareController">
        <property name="currentUser" ref="userDetails"/>
        <!-- other props -->
    </bean>
    

    aop : scoped-proxy 태그의 도움으로 새로운 HTTP 요청이 들어올 때마다 정적 메서드 getUserDetails가 호출되고 currentUser 속성에 대한 참조가 올바르게 해석됩니다. 이제 단위 테스트는 간단 해집니다.

    protected void setUp() {
        // existing init code
    
        MyUserDetails user = new MyUserDetails();
        // set up user as you wish
        controller.setCurrentUser(user);
    }
    

    희망이 도움이!

  4. ==============================

    4.인증 객체를 생성하고 삽입하는 방법에 대한 질문에 답하지 않고 Spring Security 4.0은 테스트와 관련하여 몇 가지 환영받을만한 대안을 제공합니다. @WithMockUser 주석은 개발자가 mock 사용자 (선택적 권한, 사용자 이름, 비밀번호 및 역할 포함)를 깔끔하게 지정할 수 있도록합니다.

    인증 객체를 생성하고 삽입하는 방법에 대한 질문에 답하지 않고 Spring Security 4.0은 테스트와 관련하여 몇 가지 환영받을만한 대안을 제공합니다. @WithMockUser 주석은 개발자가 mock 사용자 (선택적 권한, 사용자 이름, 비밀번호 및 역할 포함)를 깔끔하게 지정할 수 있도록합니다.

    @Test
    @WithMockUser(username = "admin", authorities = { "ADMIN", "USER" })
    public void getMessageWithMockUserCustomAuthorities() {
        String message = messageService.getMessage();
        ...
    }
    

    @WithUserDetails를 사용하여 UserDetailsService에서 반환 된 UserDetails를 에뮬레이트하는 옵션도 있습니다.

    @Test
    @WithUserDetails("customUsername")
    public void getMessageWithUserDetailsCustomUsername() {
        String message = messageService.getMessage();
        ...
    }
    

    Spring Security 레퍼런스 문서의 @WithMockUser와 @WithUserDetails 장에서 더 자세한 내용을 볼 수 있습니다 (여기에서 위 예제를 복사 한 곳)

  5. ==============================

    5.개인적으로 저는 Powockock을 Mockito 또는 Easymock과 함께 사용하여 유닛 / 통합 테스트에서 정적 SecurityContextHolder.getSecurityContext ()를 조롱했습니다.

    개인적으로 저는 Powockock을 Mockito 또는 Easymock과 함께 사용하여 유닛 / 통합 테스트에서 정적 SecurityContextHolder.getSecurityContext ()를 조롱했습니다.

    @RunWith(PowerMockRunner.class)
    @PrepareForTest(SecurityContextHolder.class)
    public class YourTestCase {
    
        @Mock SecurityContext mockSecurityContext;
    
        @Test
        public void testMethodThatCallsStaticMethod() {
            // Set mock behaviour/expectations on the mockSecurityContext
            when(mockSecurityContext.getAuthentication()).thenReturn(...)
            ...
            // Tell mockito to use Powermock to mock the SecurityContextHolder
            PowerMockito.mockStatic(SecurityContextHolder.class);
    
            // use Mockito to set up your expectation on SecurityContextHolder.getSecurityContext()
            Mockito.when(SecurityContextHolder.getSecurityContext()).thenReturn(mockSecurityContext);
            ...
        }
    }
    

    분명히 보일러 플레이트 코드가 꽤 있습니다. 예를 들어 Authentication 객체를 모의 해보고 Authentication을 반환하는 SecurityContext를 모의하고 마지막으로 SecurityContextHolder를 모방하여 SecurityContext를 얻습니다. 그러나 매우 유연하며 null 인증 객체와 같은 시나리오에 대한 단위 테스트를 허용합니다. (비 테스트) 코드를 변경하지 않고도

  6. ==============================

    6.이 경우 정적을 사용하면 보안 코드를 작성하는 가장 좋은 방법입니다.

    이 경우 정적을 사용하면 보안 코드를 작성하는 가장 좋은 방법입니다.

    예, 통계는 일반적으로 일반적으로 좋지 않지만이 경우 정적은 원하는 것입니다. 보안 컨텍스트가 주체를 현재 실행중인 스레드와 연관시키기 때문에 가장 안전한 코드는 스레드에서 가능한 한 직접 정적으로 액세스합니다. 주입 된 래퍼 클래스 뒤에 액세스를 숨기면 공격자가 더 많은 공격 지점을 제공합니다. 그들은 코드에 액세스 할 필요가 없습니다 (항아리가 서명 된 경우 변경하기가 어려울 것입니다). 런타임에 구성을 완료하거나 클래스 패스에 일부 XML을 전달할 수있는 구성을 재정의하는 방법이 필요합니다. 어노테이션 삽입을 사용해도 외부 XML로 대체 할 수 있습니다. 이러한 XML은 실행중인 시스템에 불량 주체를 주입 할 수 있습니다.

  7. ==============================

    7.나는이 질문을 나 자신에게 물었고, 최근에 발견 한 대답을 게시했다. 간단한 대답은 : SecurityContext를 삽입하고 SpringContent에서만 SecurityContextHolder를 참조하여 SecurityContext를 얻는 것입니다.

    나는이 질문을 나 자신에게 물었고, 최근에 발견 한 대답을 게시했다. 간단한 대답은 : SecurityContext를 삽입하고 SpringContent에서만 SecurityContextHolder를 참조하여 SecurityContext를 얻는 것입니다.

  8. ==============================

    8.나는 여기에서 이야기되는 Spring의 추상 테스트 클래스와 모의 객체를 살펴볼 것이다. 그것들은 유닛을 만들고 통합 테스트를 더 쉽게 해주는 Spring 관리 오브젝트의 자동 배선의 강력한 방법을 제공합니다.

    나는 여기에서 이야기되는 Spring의 추상 테스트 클래스와 모의 객체를 살펴볼 것이다. 그것들은 유닛을 만들고 통합 테스트를 더 쉽게 해주는 Spring 관리 오브젝트의 자동 배선의 강력한 방법을 제공합니다.

  9. ==============================

    9.그동안 (버전 3.2, 2013 년, SEC-2298 덕택에) @AuthenticationPrincipal 주석을 사용하여 MVC 메소드에 인증을 주입 할 수 있습니다.

    그동안 (버전 3.2, 2013 년, SEC-2298 덕택에) @AuthenticationPrincipal 주석을 사용하여 MVC 메소드에 인증을 주입 할 수 있습니다.

    @Controller
    class Controller {
      @RequestMapping("/somewhere")
      public void doStuff(@AuthenticationPrincipal UserDetails myUser) {
      }
    }
    

    유닛 테스트에서는이 Method를 직접 호출 할 수 있습니다. org.springframework.test.web.servlet.MockMvc를 사용하는 통합 테스트에서 org.springframework.security.test.web.servlet.request.SecurityMockcvRequestPostProcessors.user ()를 사용하여 다음과 같이 사용자를 주입 할 수 있습니다.

    mockMvc.perform(get("/somewhere").with(user(myUserDetails)));
    

    그러나 이는 SecurityContext를 직접 채울 것입니다. 테스트에서 세션에서로드 된 사용자인지 확인하려면 다음을 사용할 수 있습니다.

    mockMvc.perform(get("/somewhere").with(sessionUser(myUserDetails)));
    /* ... */
    private static RequestPostProcessor sessionUser(final UserDetails userDetails) {
        return new RequestPostProcessor() {
            @Override
            public MockHttpServletRequest postProcessRequest(final MockHttpServletRequest request) {
                final SecurityContext securityContext = new SecurityContextImpl();
                securityContext.setAuthentication(
                    new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities())
                );
                request.getSession().setAttribute(
                    HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, securityContext
                );
                return request;
            }
        };
    }
    
  10. ==============================

    10.인증은 OS의 프로세스 속성과 동일한 방식으로 서버 환경의 스레드의 속성입니다. 인증 정보에 액세스하기위한 bean 인스턴스가 있으면 아무런 이점도없이 구성 및 배선 오버 헤드가 불편할 수 있습니다.

    인증은 OS의 프로세스 속성과 동일한 방식으로 서버 환경의 스레드의 속성입니다. 인증 정보에 액세스하기위한 bean 인스턴스가 있으면 아무런 이점도없이 구성 및 배선 오버 헤드가 불편할 수 있습니다.

    테스트 인증에 관해서는 당신의 인생을 더 쉽게 할 수있는 방법이 몇 가지 있습니다. 필자가 선호하는 것은 @Authenticated라는 커스텀 어노테이션을 만들고 그것을 관리하는 테스트 실행 리스너를 만드는 것이다. 영감을 얻으려면 DirtiesContextTestExecutionListener를 확인하십시오.

  11. ==============================

    11.꽤 많은 일을 한 후에 원하는 행동을 재현 할 수있었습니다. MockMvc를 통해 로그인을 에뮬레이트했습니다. 대부분의 단위 테스트에는 너무 무거 우나 통합 테스트에는 도움이됩니다.

    꽤 많은 일을 한 후에 원하는 행동을 재현 할 수있었습니다. MockMvc를 통해 로그인을 에뮬레이트했습니다. 대부분의 단위 테스트에는 너무 무거 우나 통합 테스트에는 도움이됩니다.

    물론 Spring Security 4.0의 새로운 기능을 통해 테스트를 더 쉽게 할 수 있습니다.

    package [myPackage]
    
    import static org.junit.Assert.*;
    
    import javax.inject.Inject;
    import javax.servlet.http.HttpSession;
    
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.experimental.runners.Enclosed;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.mock.web.MockHttpServletRequest;
    import org.springframework.security.core.context.SecurityContext;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.web.FilterChainProxy;
    import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    import org.springframework.test.context.web.WebAppConfiguration;
    import org.springframework.test.web.servlet.MockMvc;
    import org.springframework.test.web.servlet.setup.MockMvcBuilders;
    import org.springframework.web.context.WebApplicationContext;
    
    import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
    import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
    
    @ContextConfiguration(locations={[my config file locations]})
    @WebAppConfiguration
    @RunWith(SpringJUnit4ClassRunner.class)
    public static class getUserConfigurationTester{
    
        private MockMvc mockMvc;
    
        @Autowired
        private FilterChainProxy springSecurityFilterChain;
    
        @Autowired
        private MockHttpServletRequest request;
    
        @Autowired
        private WebApplicationContext webappContext;
    
        @Before  
        public void init() {  
            mockMvc = MockMvcBuilders.webAppContextSetup(webappContext)
                        .addFilters(springSecurityFilterChain)
                        .build();
        }  
    
    
        @Test
        public void testTwoReads() throws Exception{                        
    
        HttpSession session  = mockMvc.perform(post("/j_spring_security_check")
                            .param("j_username", "admin_001")
                            .param("j_password", "secret007"))
                            .andDo(print())
                            .andExpect(status().isMovedTemporarily())
                            .andExpect(redirectedUrl("/index"))
                            .andReturn()
                            .getRequest()
                            .getSession();
    
        request.setSession(session);
    
        SecurityContext securityContext = (SecurityContext)   session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
    
        SecurityContextHolder.setContext(securityContext);
    
            // Your test goes here. User is logged with 
    }
    
  12. from https://stackoverflow.com/questions/360520/unit-testing-with-spring-security by cc-by-sa and MIT license