[SPRING] Spring Test & Security : 인증 방법 모의?
SPRINGSpring Test & Security : 인증 방법 모의?
내 컨트롤러의 URL이 제대로 보호되어 있는지 테스트하는 방법을 알아 내려고했습니다. 누군가가 주변 환경을 변경하고 실수로 보안 설정을 제거하는 경우를 대비하여.
내 컨트롤러 메서드는 다음과 같습니다.
@RequestMapping("/api/v1/resource/test")
@Secured("ROLE_USER")
public @ResonseBody String test() {
return "test";
}
필자는 다음과 같이 WebTestEnvironment를 설정했습니다.
import javax.annotation.Resource;
import javax.naming.NamingException;
import javax.sql.DataSource;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.context.ActiveProfiles;
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;
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration({
"file:src/main/webapp/WEB-INF/spring/security.xml",
"file:src/main/webapp/WEB-INF/spring/applicationContext.xml",
"file:src/main/webapp/WEB-INF/spring/servlet-context.xml" })
public class WebappTestEnvironment2 {
@Resource
private FilterChainProxy springSecurityFilterChain;
@Autowired
@Qualifier("databaseUserService")
protected UserDetailsService userDetailsService;
@Autowired
private WebApplicationContext wac;
@Autowired
protected DataSource dataSource;
protected MockMvc mockMvc;
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
protected UsernamePasswordAuthenticationToken getPrincipal(String username) {
UserDetails user = this.userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
user,
user.getPassword(),
user.getAuthorities());
return authentication;
}
@Before
public void setupMockMvc() throws NamingException {
// setup mock MVC
this.mockMvc = MockMvcBuilders
.webAppContextSetup(this.wac)
.addFilters(this.springSecurityFilterChain)
.build();
}
}
내 실제 테스트에서 나는 다음과 같은 것을 시도했다.
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Test;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import eu.ubicon.webapp.test.WebappTestEnvironment;
public class CopyOfClaimTest extends WebappTestEnvironment {
@Test
public void signedIn() throws Exception {
UsernamePasswordAuthenticationToken principal =
this.getPrincipal("test1");
SecurityContextHolder.getContext().setAuthentication(principal);
super.mockMvc
.perform(
get("/api/v1/resource/test")
// .principal(principal)
.session(session))
.andExpect(status().isOk());
}
}
나는 이것을 여기에서 골랐다.
그러나이 기능을 면밀히 살펴보면 실제 요청을 URL로 보내지 않을 때만 도움이되지만 기능 수준에서 서비스를 테스트 할 때만 도움이됩니다. 내 경우에는 "액세스 거부"예외가 throw되었습니다.
org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:83) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:206) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:60) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) ~[spring-aop-3.2.1.RELEASE.jar:3.2.1.RELEASE]
...
다음 두 로그 메시지는 기본적으로 Principal 설정이 작동하지 않거나 덮어 쓰기되었음을 나타내는 사용자가 인증되지 않았 음을 의미합니다.
14:20:34.454 [main] DEBUG o.s.s.a.i.a.MethodSecurityInterceptor - Secure object: ReflectiveMethodInvocation: public java.util.List test.TestController.test(); target is of class [test.TestController]; Attributes: [ROLE_USER]
14:20:34.454 [main] DEBUG o.s.s.a.i.a.MethodSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
해결법
-
==============================
1.스프링 보안 필터 체인의 일부인 SecurityContextPersistenceFilter는 항상 SecurityContextHolder.getContext (). setAuthentication (principal) (또는 .principal (principal) 메서드 사용)을 설정하는 SecurityContext를 재설정합니다. 이 필터는 이전에 설정 한 SecurityContextRepository를 오버라이드 (override) 한 SecurityContext를 사용하여 SecurityContextHolder의 SecurityContext를 SecurityContext로 설정합니다. 저장소는 기본적으로 HttpSessionSecurityContextRepository입니다. HttpSessionSecurityContextRepository는 주어진 HttpRequest를 검사하고 해당 HttpSession에 액세스를 시도합니다. 존재하는 경우 HttpSession에서 SecurityContext를 읽으려고 시도합니다. 이것이 실패하면 저장소는 빈 SecurityContext를 생성합니다.
스프링 보안 필터 체인의 일부인 SecurityContextPersistenceFilter는 항상 SecurityContextHolder.getContext (). setAuthentication (principal) (또는 .principal (principal) 메서드 사용)을 설정하는 SecurityContext를 재설정합니다. 이 필터는 이전에 설정 한 SecurityContextRepository를 오버라이드 (override) 한 SecurityContext를 사용하여 SecurityContextHolder의 SecurityContext를 SecurityContext로 설정합니다. 저장소는 기본적으로 HttpSessionSecurityContextRepository입니다. HttpSessionSecurityContextRepository는 주어진 HttpRequest를 검사하고 해당 HttpSession에 액세스를 시도합니다. 존재하는 경우 HttpSession에서 SecurityContext를 읽으려고 시도합니다. 이것이 실패하면 저장소는 빈 SecurityContext를 생성합니다.
따라서, 내 솔루션은 SecurityContext를 보유하고있는 요청과 함께 HttpSession을 전달하는 것입니다.
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.junit.Test; import org.springframework.mock.web.MockHttpSession; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import eu.ubicon.webapp.test.WebappTestEnvironment; public class Test extends WebappTestEnvironment { public static class MockSecurityContext implements SecurityContext { private static final long serialVersionUID = -1386535243513362694L; private Authentication authentication; public MockSecurityContext(Authentication authentication) { this.authentication = authentication; } @Override public Authentication getAuthentication() { return this.authentication; } @Override public void setAuthentication(Authentication authentication) { this.authentication = authentication; } } @Test public void signedIn() throws Exception { UsernamePasswordAuthenticationToken principal = this.getPrincipal("test1"); MockHttpSession session = new MockHttpSession(); session.setAttribute( HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, new MockSecurityContext(principal)); super.mockMvc .perform( get("/api/v1/resource/test") .session(session)) .andExpect(status().isOk()); } }
-
==============================
2.응답을위한 Seaching 나는 동시에 쉽고 유연한 프로그램을 찾지 못했고 Spring Security Reference를 찾았습니다. 완벽한 솔루션이 거의 존재 함을 깨달았습니다. AOP 솔루션은 종종 테스트를위한 가장 훌륭한 솔루션이며, Spring은이 아티팩트에서 @WithMockUser, @WithUserDetails 및 @WithSecurityContext를 제공한다.
응답을위한 Seaching 나는 동시에 쉽고 유연한 프로그램을 찾지 못했고 Spring Security Reference를 찾았습니다. 완벽한 솔루션이 거의 존재 함을 깨달았습니다. AOP 솔루션은 종종 테스트를위한 가장 훌륭한 솔루션이며, Spring은이 아티팩트에서 @WithMockUser, @WithUserDetails 및 @WithSecurityContext를 제공한다.
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <version>4.2.2.RELEASE</version> <scope>test</scope> </dependency>
대부분의 경우 @WithUserDetails는 내가 필요로하는 유연성과 힘을 모은다.
기본적으로 테스트하려는 모든 가능한 사용자 프로필로 사용자 정의 UserDetailsService를 만들어야합니다. 예 :
@TestConfiguration public class SpringSecurityWebAuxTestConfig { @Bean @Primary public UserDetailsService userDetailsService() { User basicUser = new UserImpl("Basic User", "user@company.com", "password"); UserActive basicActiveUser = new UserActive(basicUser, Arrays.asList( new SimpleGrantedAuthority("ROLE_USER"), new SimpleGrantedAuthority("PERM_FOO_READ") )); User managerUser = new UserImpl("Manager User", "manager@company.com", "password"); UserActive managerActiveUser = new UserActive(managerUser, Arrays.asList( new SimpleGrantedAuthority("ROLE_MANAGER"), new SimpleGrantedAuthority("PERM_FOO_READ"), new SimpleGrantedAuthority("PERM_FOO_WRITE"), new SimpleGrantedAuthority("PERM_FOO_MANAGE") )); return new InMemoryUserDetailsManager(Arrays.asList( basicActiveUser, managerActiveUser )); } }
이제 사용자를 준비 시켰습니다. 따라서이 컨트롤러 기능에 대한 액세스 제어를 테스트하고 싶다고 상상해보십시오.
@RestController @RequestMapping("/foo") public class FooController { @Secured("ROLE_MANAGER") @GetMapping("/salute") public String saluteYourManager(@AuthenticationPrincipal User activeUser) { return String.format("Hi %s. Foo salutes you!", activeUser.getUsername()); } }
여기서는 / foo / salute 라우트에 매핑 된 함수를 가져오고 @PreAuthorize 및 @PostAuthorize도 테스트 할 수 있지만 @Secured 주석으로 역할 기반 보안을 테스트하고 있습니다. 유효한 사용자가이 경례 응답을 볼 수 있는지 확인하고 다른 응답자가 실제로 금지되어 있는지 확인하는 두 가지 테스트를 작성해 보겠습니다.
@RunWith(SpringRunner.class) @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SpringSecurityWebAuxTestConfig.class ) @AutoConfigureMockMvc public class WebApplicationSecurityTest { @Autowired private MockMvc mockMvc; @Test @WithUserDetails("manager@company.com") public void givenManagerUser_whenGetFooSalute_thenOk() throws Exception { mockMvc.perform(MockMvcRequestBuilders.get("/foo/salute") .accept(MediaType.ALL)) .andExpect(status().isOk()) .andExpect(content().string(containsString("manager@company.com"))); } @Test @WithUserDetails("user@company.com") public void givenBasicUser_whenGetFooSalute_thenForbidden() throws Exception { mockMvc.perform(MockMvcRequestBuilders.get("/foo/salute") .accept(MediaType.ALL)) .andExpect(status().isForbidden()); } }
보시다시피 SpringSecurityWebAuxTestConfig를 가져와 테스트를 위해 사용자를 제공합니다. 각 코드 케이스는 간단한 주석을 사용하여 코드와 복잡성을 줄임으로써 해당 테스트 케이스에서 사용되었습니다.
@WithUserDetails는 대부분의 응용 프로그램에 필요한 모든 유연성을 제공합니다. 역할이나 권한과 같은 GrantedAuthority가있는 사용자 지정 사용자를 사용할 수 있습니다. 그러나 만약 당신이 단지 역할로 작업한다면, 테스트는 더욱 쉬울 수 있으며 커스텀 UserDetailsService를 생성하는 것을 피할 수 있습니다. 이 경우 @WithMockUser와 사용자, 비밀번호 및 역할의 간단한 조합을 지정하십시오.
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @WithSecurityContext( factory = WithMockUserSecurityContextFactory.class ) public @interface WithMockUser { String value() default "user"; String username() default ""; String[] roles() default {"USER"}; String password() default "password"; }
주석은 매우 기본적인 사용자의 기본값을 정의합니다. 우리의 경우와 같이 우리가 테스트하는 라우트는 인증 된 사용자를 관리자로 요구하며 SpringSecurityWebAuxTestConfig를 사용하여 종료 할 수 있습니다.
@Test @WithMockUser(roles = "MANAGER") public void givenManagerUser_whenGetFooSalute_thenOk() throws Exception { mockMvc.perform(MockMvcRequestBuilders.get("/foo/salute") .accept(MediaType.ALL)) .andExpect(status().isOk()) .andExpect(content().string(containsString("user"))); }
이제 manager@company.com이라는 사용자 대신 @WithMockUser : user가 제공하는 기본값을 얻습니다. 그러나 우리가 정말로 관심을 갖는 것이 그의 역할 ROLE_MANAGER이기 때문에 중요하지 않습니다.
@WithUserDetails 및 @WithMockUser와 같은 특수 효과를 통해 알 수 있듯이 간단한 테스트 만하기 위해 아키텍처에서 소외된 클래스를 만들지 않고도 인증 된 다른 사용자 시나리오로 전환 할 수 있습니다. 또한 @WithSecurityContext가 어떻게 더 유연하게 작동하는지 보도록 권장했습니다.
-
==============================
3.pom.xml에 추가 :
pom.xml에 추가 :
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <version>4.0.0.RC2</version> </dependency>
Authorization 요청을 위해 org.springframework.security.test.web.servlet.request.SecurityMockCreationPostProcessors를 사용한다. 샘플 사용법은 https://github.com/rwinch/spring-security-test-blog에서 확인하십시오. (https://jira.spring.io/browse/SEC-2592).
최신 정보:
4.0.0.RC2는 스프링 보안 3.x에서 작동합니다. 스프링 보안의 경우 4 스프링 보안 테스트는 스프링 보안의 일부가됩니다 (http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test, 버전은 동일 함). ).
설정이 변경되었습니다. http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test-mockmvc
public void setup() { mvc = MockMvcBuilders .webAppContextSetup(context) .apply(springSecurity()) .build(); }
기본 인증 샘플 : http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#testing-http-basic-authentication.
-
==============================
4.Spring 4.0+ 이후, 가장 좋은 해결책은 @WithMockUser로 테스트 메소드에 주석을 달아주는 것이다.
Spring 4.0+ 이후, 가장 좋은 해결책은 @WithMockUser로 테스트 메소드에 주석을 달아주는 것이다.
@Test @WithMockUser(username = "user1", password = "pwd", roles = "USER") public void mytest1() throws Exception { mockMvc.perform(get("/someApi")) .andExpect(status().isOk()); }
프로젝트에 다음 종속성을 추가하는 것을 잊지 마십시오.
'org.springframework.security:spring-security-test:4.2.3.RELEASE'
-
==============================
5.다음은 Base64 기본 인증을 사용하여 Spring MockMvc Security Config를 테스트하려는 사람들을위한 예제이다.
다음은 Base64 기본 인증을 사용하여 Spring MockMvc Security Config를 테스트하려는 사람들을위한 예제이다.
String basicDigestHeaderValue = "Basic " + new String(Base64.encodeBase64(("<username>:<password>").getBytes())); this.mockMvc.perform(get("</get/url>").header("Authorization", basicDigestHeaderValue).accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk());
Maven 종속성
<dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.3</version> </dependency>
-
==============================
6.짧은 답변:
짧은 답변:
@Autowired private WebApplicationContext webApplicationContext; @Autowired private Filter springSecurityFilterChain; @Before public void setUp() throws Exception { final MockHttpServletRequestBuilder defaultRequestBuilder = get("/dummy-path"); this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext) .defaultRequest(defaultRequestBuilder) .alwaysDo(result -> setSessionBackOnRequestBuilder(defaultRequestBuilder, result.getRequest())) .apply(springSecurity(springSecurityFilterChain)) .build(); } private MockHttpServletRequest setSessionBackOnRequestBuilder(final MockHttpServletRequestBuilder requestBuilder, final MockHttpServletRequest request) { requestBuilder.session((MockHttpSession) request.getSession()); return request; }
봄 보안 테스트에서 formLogin을 수행 한 후 각 요청은 자동으로 로그인 한 사용자로 호출됩니다.
긴 대답 :
이 해결책을 확인하십시오. (답은 봄 4입니다.) 봄 3.2 사용자에 로그인하는 방법
-
==============================
7.테스트에서 SecurityContextHolder 사용을 피하기위한 옵션 :
테스트에서 SecurityContextHolder 사용을 피하기위한 옵션 :
from https://stackoverflow.com/questions/15203485/spring-test-security-how-to-mock-authentication by cc-by-sa and MIT license
'SPRING' 카테고리의 다른 글
[SPRING] Spring MVC에서 캐시 헤더를 어떻게 설정합니까? (0) | 2018.12.28 |
---|---|
[SPRING] EntityManager 대 삽입 EntityManagerFactory (0) | 2018.12.28 |
[SPRING] 봄과 빈혈 도메인 모델 (0) | 2018.12.28 |
[SPRING] Spring MVC를 사용하여 애플리케이션 시작시 Java 클래스 실행 [duplicate] (0) | 2018.12.28 |
[SPRING] @ComponentScan 주석을 사용하여 여러 경로를 검색하는 방법은 무엇입니까? (0) | 2018.12.28 |