스프링 테스트 및 보안:인증을 모의하는 방법
컨트롤러의 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());
}
}
여기서 이걸 주웠어요.
- http://java.dzone.com/articles/spring-test-mvc-junit-testing 여기:
- http://techdive.in/solutions/how-mock-securitycontextholder-perfrom-junit-tests-spring-controller 또는 여기:
- 스프링 MVC 컨트롤러에 의해 지정된 @PreAuthorize 주석 및 스프링 EL을 JUnit에서 테스트하는 방법
그러나 자세히 살펴보면 실제 요청을 URL로 전송하지 않고 기능 수준에서 서비스를 테스트할 때만 도움이 됩니다.이 경우 "접근 거부" 예외가 발생하였습니다.
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]
...
가 인증되지 을 나타냅니다.은, 「Da」의 이 「2」로 되어 있는 것을 .★★★★★★★★★★★★★★★★★★★★★★★,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
답변을 검색해 보니 쉽고 유연한 솔루션을 찾을 수 없었습니다.그 후 스프링 보안 레퍼런스를 발견하고 거의 완벽한 솔루션이 있다는 것을 깨달았습니다.AOP 솔루션은 대부분의 경우 테스트에 가장 적합한 솔루션이며 Spring은 AOP 솔루션을 제공합니다.@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
필요한 유연성과 파워를 얻을 수 있습니다.
@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에 대한 get mapping 함수가 있으며 이 함수에 의해@Secured
할 수 시험할 수 있습니다.@PreAuthorize
★★★★★★★★★★★★★★★★★」@PostAuthorize
이 의 응답이 가 볼 수를 두 가지 두 가지 테스트를 만듭니다. 하나는 유효한 사용자가 이 경례 응답을 볼 수 있는지 확인하는 테스트이고 다른 하나는 실제로 금지된지를 확인하는 테스트입니다.
@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
사용자에게 테스트를 제공합니다.각 테스트 케이스는 간단한 주석을 사용하여 코드와 복잡성을 줄임으로써 해당 테스트 케이스에 사용됩니다.
@WithMockUser를 사용하면 역할 기반 보안을 쉽게 할 수 있습니다.
바와 같이요.@WithUserDetails
는 대부분의 애플리케이션에 필요한 모든 유연성을 갖추고 있습니다.를 통해 임의의 GrantedGranted와 함께 커스텀 할 수 .하지만 더 쉬워지고 커스텀 구성을 수 있습니다.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
사용자. 하지만 우리가 정말로 관심을 가지는 것은 그의 역할이기 때문에 그것은 중요하지 않습니다.ROLE_MANAGER
.
결론들
바와 같이, 시와 as as as as 입니다.@WithUserDetails
★★★★★★★★★★★★★★★★★」@WithMockUser
간단한 테스트를 위해 아키텍처에서 소외된 클래스를 구축하지 않고 인증된 다른 사용자 시나리오 간에 전환할 수 있습니다.또, @WithSecurityContext가 어떻게 기능하는지를 확인하는 것도 추천합니다.
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'
보니 그 ★★★★★★★★★★★★★★★★★★★★.SecurityContextPersistenceFilter
Security는 Spring Security를 SecurityContext
를 호출하도록 설정했습니다.SecurityContextHolder.getContext().setAuthentication(principal)
(을) 하여).principal(principal)
「」). 이는, 「」를 설정합니다.SecurityContext
SecurityContextHolder
a SecurityContext
SecurityContextRepository
이전에 설정한 것을 덮어씁니다.저장소는HttpSessionSecurityContextRepository
디폴트입니다.그HttpSessionSecurityContextRepository
된 「」를 합니다.HttpRequest
거기에 합니다.HttpSession
요, 읽으려고 합니다.SecurityContext
HttpSession
이는 빈 저장소로 SecurityContext
.
저의 은 ' 정도', '어느 정도', ' 정도',HttpSession
와 함께, 「」를 보관 하고 있습니다.SecurityContext
:
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());
}
}
pom.xml 추가:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<version>4.0.0.RC2</version>
</dependency>
를 사용합니다.org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors
허가요청용입니다.사용 예시는 https://github.com/rwinch/spring-security-test-blog (https://jira.spring.io/browse/SEC-2592) )를 참조해 주세요.
업데이트:
4.0.0.RC2는 spring-security 3.x에서 동작합니다.spring-security의 경우 4 spring-security-test는 spring-security의 일부가 됩니다(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 - test - mvc
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
basic-authentication 샘플:http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/ #syslog-syslog-basic-authentication.
Base64 기본 인증을 사용하여 Spring MockMvc 보안 구성을 테스트하려는 사용자를 위한 예를 다음에 나타냅니다.
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());
메이븐 의존 관계
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.3</version>
</dependency>
간단한 답변:
@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용).Spring 3.2 신규 MVC 테스트로 사용자를 로그인하는 방법
테스트에서 SecurityContextHolder를 사용하지 않는 옵션:
- 옵션 1: 모크 사용 - 모의
SecurityContextHolder
일부 모의 라이브러리 사용 - EasyMock 등 - 옵션 2: 랩콜
SecurityContextHolder.get...
- - in in in in in in in in in in in in in in in in in in in in in in in in.SecurityServiceImpl
【중략】getCurrentPrincipal
SecurityService
에서는, 이 을 간단하게 해, 「」에 액세스 , 의 원본을 할 수 .이 실장은 액세스 없이 원하는 원본을 반환합니다.SecurityContextHolder
답장이 꽤 늦었네요.하지만 이것은 나에게 효과가 있었고, 유용할 수 있었다.
Spring Security와 mockMvc를 사용하는 동안 다른 것과 마찬가지로 @WithMockUser 주석을 사용하면 됩니다.
스프링 보안은 인증되지 않은 요청을 테스트하기 위해 호출된 또 다른 주석을 제공합니다.하지만 여기서는 조심해야 합니다.당신은 401을 예상하고 있을 것입니다만, 디폴트로 403 Forbidden Error를 받았습니다.실제 시나리오에서는 실제 서비스를 실행하면 서비스가 리다이렉트되고 올바른 401 응답 코드가 표시됩니다.익명 요청에 이 주석을 사용합니다.
또, 어노타이온을 생략하고, 허가 없이 보관하는 것도 생각할 수 있습니다.그러나 이 경우 보통 올바른 예외가 발생합니다(인증 등).예외) 단, 올바르게 처리되면(커스텀 핸들러를 사용하는 경우) 올바른 상태 코드를 얻을 수 있습니다.이거 500달러 받곤 했는데따라서 디버거에서 발생한 예외를 찾아 올바르게 처리되었는지 확인하고 올바른 상태 코드를 반환합니다.
" " " 를 만듭니다.TestUserDetailsImpl
다음 중 하나:
@Service
@Primary
@Profile("test")
public class TestUserDetailsImpl implements UserDetailsService {
public static final String API_USER = "apiuser@example.com";
private User getAdminUser() {
User user = new User();
user.setUsername(API_USER);
SimpleGrantedAuthority role = new SimpleGrantedAuthority("ROLE_API_USER");
user.setAuthorities(Collections.singletonList(role));
return user;
}
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
if (Objects.equals(username, ADMIN_USERNAME))
return getAdminUser();
throw new UsernameNotFoundException(username);
}
}
휴지 끝점:
@GetMapping("/invoice")
@Secured("ROLE_API_USER")
public Page<InvoiceDTO> getInvoices(){
...
}
테스트 끝점:
@Test
@WithUserDetails("apiuser@example.com")
public void testApi() throws Exception {
...
}
「」를 사용하고 MockMvcBuilders.webAppContextSetup(wac).addFilters(...)
springSecurityFilterChain
으로 말하면)SecurityContextPersistenceFilter
되어 ( )가 됩니다.SecurityContext
by by by by @WithMockUser
어리석다); 은 ( 쪽인가); (어느 쪽인가) 때문에 .SecurityContextPersistenceFilter
복원을 하려고 .SecurityContext
HttpSession
찾을 수 없습니다.하세요.AutoStoreSecurityContextHttpFilter
되어 있습니다.이것에 의해, 래에정 defined defined defined defined defined defined defined defined defined defined defined defined defined defined defined defined defined defined defined defined defined defined defined defined defined 。@WithMockUser
미리 한 " " SecurityContext
HttpSession
SecurityContextPersistenceFilter
찾을 수 있을 거예요
@ContextConfiguration(...) // the issue doesn't occur when using @SpringBootTest
public class SomeTest {
@Autowired
private Filter springSecurityFilterChain;
private MockMvc mockMvc;
@BeforeEach
void setup(WebApplicationContext wac) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac)
.addFilters(new AutoStoreSecurityContextHttpFilter(), springSecurityFilterChain).build();
}
@WithMockUser
@Test
void allowAccessToAuthenticated() {
...
}
}
// don't use this Filter in production because it's only intended for tests, to solve the
// @WithMockUser & springSecurityFilterChain (more specifically SecurityContextPersistenceFilter) "misunderstandings"
public class AutoStoreSecurityContextHttpFilter extends HttpFilter {
protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
req.getSession().setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext());
super.doFilter(req, res, chain);
}
}
언급URL : https://stackoverflow.com/questions/15203485/spring-test-security-how-to-mock-authentication
'code' 카테고리의 다른 글
ReactJ에서는 동기 호출 시 setState가 다르게 동작하는 이유는 무엇입니까? (0) | 2023.03.04 |
---|---|
AngularJS HTML을 동적으로 추가하고 컨트롤러에 바인드하는 방법 (0) | 2023.03.04 |
Mongo에서는 샤딩과 복제의 차이점은 무엇입니까? (0) | 2023.03.04 |
jq를 사용하여 이중 따옴표를 사용하지 않는 JSON의 Concat 번호 (0) | 2023.03.04 |
스프링 부트 유닛 테스트에서 JWT 인증을 모의하는 방법 (0) | 2023.03.04 |