부트 개념과 활용-30.Starter-Security(스프링 시큐리티)


스프링 시큐리티 1부: Starter-Security

스프링 시큐리티

  • 웹 시큐리티
  • 메소드 시큐리티
  • 다양한 인증 방법 지원
    • LDAP, 폼 인증, Basic 인증, OAuth, …

스프링 부트 시큐리티 자동 설정

  • SecurityAutoConfiguration
  • UserDetailsServiceAutoConfiguration
  • spring-boot-starter-security
    • 스프링 시큐리티 5.* 의존성 추가
  • 모든 요청에 인증이 필요함.
  • 기본 사용자 생성
  • Username: user
    • Password: 애플리케이션을 실행할 때 마다 랜덤 값 생성 (콘솔에 출력 됨.)
    • spring.security.user.name
    • spring.security.user.password
  • 인증 관련 각종 이벤트 발생
    • DefaultAuthenticationEventPublisher 빈 등록
    • 다양한 인증 에러 핸들러 등록 가능

실습

의존성 추가(thymeleaf) (참고. 이거 안하면 / -> index.html 못찾음.. 구글링..)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

컨트롤러에서 다른 로직 없이 뷰로만 전달할 때 (controller로 구현해도 동일)

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/hello").setViewName("hello");
        registry.addViewController("/my").setViewName("my");
    }
}

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>
<h1>index</h1>
<a href="/hello">hello</a>
<a href="/my">my</a>
</body>
</html>

test

@ExtendWith(SpringExtension.class)
@WebMvcTest(WebConfig.class)
class HomeControllerTest {

    @Autowired
    MockMvc mockMvc;

    @Test
    public void hello() throws Exception {
        mockMvc.perform(get("/hello"))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(view().name("hello"));
    }

    @Test
    public void my() throws Exception {
        mockMvc.perform(get("/my"))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(view().name("my"));
    }
}

현재 상태에서는 정상적으로 테스트가 되나.. 스프링 시큐리티 의존성을 추가하면 에러가 뜬다.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
MockHttpServletResponse:
           Status = 401
    Error message = Unauthorized
          Headers = [WWW-Authenticate:"Basic realm="Realm"", X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
     Content type = null
             Body = 
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

Error message = Unauthorized
스프링 시큐리티를 추가하면 모든 요청이 다 스프링 시큐리티를 통해 인증이 필요로 하게 된다.
(폼 인증, Basic 인증 둘 다 적용이 됨)
현재는 베이직 인증으로 받는 것을 확인이 가능하다.

그런데 이 인증 방식은 보내주는 accept 헤더에 따라 달라진다.

이 요청이 원하는 응답의 형태가 현재는 아무것도 지정하지 않아서 폼 인증에 대한 응답으로 보내지 않고, Basic 인증의 형태로 보내준 것인데,

보통 대부분의 경우는 TEXT_HTML을 accept 헤더에 담아서 보낸다. 이런 경우에는 폼 인증으로 넘어가게 된다.

이에 대한 테스트를 확인하면

@Test
public void hello() throws Exception {
    mockMvc.perform(get("/hello")
                .accept(MediaType.TEXT_HTML))
            .andDo(print())
            .andExpect(status().isOk())
            .andExpect(view().name("hello"));
}

TEXT_HTML 미디어 타입으로 보내면,

MockHttpServletResponse:
           Status = 302
    Error message = null
          Headers = [X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY", Location:"http://localhost/login"]
     Content type = null
             Body = 
    Forwarded URL = null
   Redirected URL = http://localhost/login
          Cookies = []

아까와 다른 응답을 확인할 수 있다. (401 -> 302)

이제 테스트가 아닌 메인 애플리케이션을 실행하면

Using generated security password: 1dafcde2-29b1-4032-bc1a-2f2a541ecc7e

콘솔창에 패스워드가 생성되는 것을 확인할 수 있고,

/ 에서 다른 링크로 가려고 한다면,
브라우저에서 내장하고 있는 기본 로그인 폼이 뜨는 것을 확인할 수 있다. (인증이 되지 않았기 때문.)

id : user이고 비밀번호는 매 실행시마다 생성되는 것을 확인할 수 있다.

스프링 시큐리티에서 기본적으로는
모든 요청에 대해 인증을 필요로 하고,
유저를 하나 생성하고 랜덤으로 패스워드를 생성한다. (콘솔 출력)

스프링 부트 시큐리티 테스트 (인증을 추가한 테스트)

@WithMockUser를 활용해 가능하다.

의존성 추가

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <version>${spring-security.version}</version>
    <scope>test</scope>
</dependency>
@ExtendWith(SpringExtension.class)
@WebMvcTest(WebConfig.class)
//@WithMockUser
class HomeControllerTest {

    @Autowired
    MockMvc mockMvc;

    @Test
    @WithMockUser // 개별 적용도 가능하고, 클래스에 적용도 가능
    public void hello() throws Exception {
        mockMvc.perform(get("/hello")
                    .accept(MediaType.TEXT_HTML))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(view().name("hello"));
    }

    @Test
    public void my() throws Exception {
        mockMvc.perform(get("/my"))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(view().name("my"));
    }
}





© 2019. by jaeuk