Contents

[Mastering Spring 5.0] 6.5 Spring Security - Basic authentication

Mastering Spring 5.0 ์Šคํ„ฐ๋””

์Šคํ”„๋ง 5.0 ๋งˆ์Šคํ„ฐ ์Šคํ„ฐ๋””
์Šคํ”„๋ง 5.0 ๋งˆ์Šคํ„ฐ ์Šคํ„ฐ๋”” ํ•™์Šต ๋‚ด์šฉ ์ •๋ฆฌ์ž…๋‹ˆ๋‹ค.

์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๋กœ REST ์„œ๋น„์Šค ๋ณดํ˜ธ

์ตœ๊ทผ์—๋Š” ์„œ๋น„์Šค ์‹œ์Šคํ…œ๋“ค๋ผ๋ฆฌ REST API ๊ธฐ๋ฐ˜์˜ ํ†ต์‹ ์ด ๋งŽ์ด ์ด๋ฃจ์–ด์ง€๊ณ  ์žˆ๋‹ค. ๋„ค์ดํ‹ฐ๋ธŒ ์•ฑ๊ณผ ์„œ๋ฒ„ ๊ฐ„ ํ†ต์‹ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์›น ํด๋ผ์ด์–ธํŠธ ์™€ ์„œ๋ฒ„๊ฐ„์—๋„ REST API ํ†ต์‹ ์„ ๋งŽ์ด ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— REST ์„œ๋น„์Šค(๋ฆฌ์†Œ์Šค) ์— ๋Œ€ํ•œ ๋ณด์•ˆ์ด ์ค‘์š”ํ•ด ์ง€๊ณ  ์žˆ๋‹ค.

์ธ์ฆ(Authentication) ๊ณผ ๊ถŒํ•œ (Authorization)
  • ์ธ์ฆ(Authentication): ์†Œ๋น„์ž(ํด๋ผ์ด์–ธํŠธ) ๊ฐ€ ์„œ๋น„์Šค(๋ฆฌ์†Œ์Šค) ์— ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•œ ์†Œ๋น„์ž์ธ์ง€
  • ์ธ๊ฐ€/๊ถŒํ•œ๋ถ€์—ฌ(Authorization) : ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜์ง€๋งŒ ํ•ด๋‹น ์ž‘์—…์„ ์†Œ๋น„์ž(ํด๋ผ์ด์–ธํŠธ) ์—๊ฒŒ ํ—ˆ์šฉํ• ๊ฒƒ์ธ์ง€

์ธ์ฆ๋ฐฉ์‹์€ ๋‹ค์–‘ํ•˜๋ฉฐ, ์ „ํ†ต์ ์ธ ์ธ์ฆ๋ฐฉ์‹์œผ๋กœ๋Š” ์‚ฌ์šฉ์ž๋ช…(Principle)๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ(Credential)๋กœ ์ธ์ฆํ•˜๋Š” Credential ๊ธฐ๋ฐ˜ ์ธ์ฆ ๋ฐฉ์‹ ๊ณผ OTP ๋“ฑ๊ณผ ๊ฐ™์ด ์ถ”๊ฐ€์ ์ธ ์ธ์ฆ๋ฐฉ์‹์„ ๋„์ž…ํ•ด 2๊ฐ€์ง€ ๋ฐฉ๋ฒ•์œผ๋กœ ์ธ์ฆํ•˜๋Š” ์ด์ค‘ ์ธ์ฆ ๋ฐฉ์‹ ๊ณผ ์ตœ๊ทผ์—๋Š” OAuth2 ์ธ์ฆ๋ฐฉ์‹ ๋„ ํ•„์ˆ˜์ ์œผ๋กœ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ๋‹ค.

์Šคํ”„๋ง์—์„œ๋Š” Spring Security ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ธ์ฆ๊ณผ ๊ถŒํ•œ ํ”„๋กœ์„ธ์Šค ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

์Šคํ”„๋ง๋ถ€ํŠธ์—์„œ ์ธ์ฆ๋ฐฉ์‹ ๊ตฌํ˜„ํ•˜๊ธฐ

์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๋กœ ์•„๋ž˜ ๋‘ ๊ฐ€์ง€ ํƒ€์ž…์˜ ์ธ์ฆ์„ ๊ฐ๊ฐ ๊ตฌํ˜„ํ•ด ๋ณธ๋‹ค.

  • ๊ธฐ๋ณธ์ธ์ฆ (Basic Authentication)
  • OAuth 2.0 ์ธ์ฆ

์˜์กด์„ฑ ์ถ”๊ฐ€ํ•˜๊ธฐ

spring-boot-starter-security ์˜์กด์„ฑ์„ pom.xml ๋˜๋Š” build.gradle ์— ์ถ”๊ฐ€ํ•œ๋‹ค.

pom.xml

1
2
3
4
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

build.gradle

1
implementation('org.springframework.boot:spring-boot-starter-security')
/posts/images/spring/page6-5-1.png#center
spring-boot-starter-security๋Š” ์„ธ๊ฐ€์ง€ ์˜์กด์„ฑ์„ ๊ฐ€์ ธ์˜จ๋‹ค

๊ธฐ๋ณธ์ธ์ฆ

  • spring-boot-starter-security ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ชจ๋“  ์„œ๋น„์Šค์— ๋Œ€ํ•ด ๊ธฐ๋ณธ ์ธ์ฆ์„ ์ž๋™์œผ๋กœ ์„ค์ •ํ•œ๋‹ค.
  • ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” ๊ธฐ๋ณธ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™๋˜๊ฑฐ๋‚˜, REST API ์š”์ฒญ ์‹œ ์‘๋‹ต ์ฝ”๋“œ 401 ์„ ๋ฆฌํ„ดํ•˜๊ฒŒ ๋œ๋‹ค.
  • ๊ธฐ๋ณธ ์ธ์ฆ์‹œ ์‚ฌ์šฉ์ž ID ๋Š” user ์ด๋ฉฐ, ํŒจ์Šค์›Œ๋“œ๋Š” ๋ณดํ†ต ์Šคํ”„๋ง ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ธฐ๋™ ์‹œ ๋กœ๊ทธ์— ํ‘œ์‹œ๋œ๋‹ค.

ํŠน์ • ID ์™€ ํŒจ์Šค์›Œ๋“œ๋ฅผ ์„ค์ •ํ•˜๋ ค๋ฉด application.properties (๋˜๋Š” application.yml) ์— ์•„๋ž˜์™€ ๊ฐ™์ด ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

application.yml

1
2
3
4
5
spring:
  security:
    user:
      name: devtest
      password: devtest!@#

ํ†ตํ•ฉ ํ…Œ์ŠคํŒ…

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Chapter06Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TodoControllerIT {

    @LocalServerPort
    private int port;

    private String createUrl(String uri) {
        return "http://localhost:" + port + uri;
    }

    private TestRestTemplate template = new TestRestTemplate();

    HttpHeaders headers = createHeaders("devtest", "devtest!@#");

    private HttpHeaders createHeaders(String username, String password) {
        return new HttpHeaders() {
            {
                String auth = username + ":" + password;
                byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes(Charset.forName("US-ASCII")));

                String authHeader = "Basic " + new String(encodedAuth);
                set("Authorization", authHeader);
            }
        };
    }

    @Test
    public void retrieveTodos() throws Exception {
        String expected = "[{\"id\":1,\"user\":\"Jack\",\"desc\":\"Learn Spring MVC\",\"targetDate\":\"2018-12-26T14:57:05.021+0000\",\"done\":false},{\"id\":2,\"user\":\"Jack\",\"desc\":\"Learn Struts\",\"targetDate\":\"2018-12-26T14:57:05.021+0000\",\"done\":false}]";

        ResponseEntity<String> response = template.exchange(createUrl("/users/Jack/todos"), HttpMethod.GET, new HttpEntity<String>(null, headers), String.class);
        JSONAssert.assertEquals(expected, response.getBody(), false);
    }
}
  • HTTP ์—์„œ Basic Authentication ์ธ์ฆ์€ HTTP ์‚ฌ์šฉ์ž Agent (ex. ์›น ๋ธŒ๋ผ์šฐ์ €) ๊ฐ€ ์š”์ฒญํ•  ๋•Œ ์‚ฌ์šฉ์ž ์ด๋ฆ„๊ณผ ์•”ํ˜ธ๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค. ๊ธฐ๋ณธ HTTP ์ธ์ฆ ์š”์ฒญ Header ํ•„๋“œ์— Authorization: Basic <credentials> ํ˜•ํƒœ๋กœ ํฌํ•จ๋œ๋‹ค. ์—ฌ๊ธฐ์„œ credentials ์€ ์ฝœ๋ก ์œผ๋กœ ๊ฒฐํ•ฉ ๋œ ID ๋ฐ ์•”ํ˜ธ์˜ base64 ์ธ์ฝ”๋”ฉ ํ˜•ํƒœ์ด๋‹ค.
  • Authorization : https://developer.mozilla.org/ko/docs/Web/HTTP/Headers/Authorization

๋‹จ์œ„ ํ…Œ์ŠคํŒ…

๋‹จ์œ„ ํ…Œ์ŠคํŠธ์— ๋ณด์•ˆ์„ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์ง€ ์•Š๋‹ค๋ฉด @WebMvcTest ์–ด๋…ธํ…Œ์ด์…˜์— secure=false ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

1
2
3
@RunWith(SpringRunner.class)
@WebMvcTest(TodoController.class, secure = false)
public class TodoControllerTest {