Post

Spring Boot: 시큐리티(Security) - 1

[rcblock id=”3197”]

 

스프링에서 로그인, 권한별 접근 기능 등을 구현하고 싶다면 스프링 시큐리티(Spring Security)를 사용해야 합니다.

1. 처음 스타트 프로젝트 생성 시 디펜던시에서 Security를 선택합니다.

나중에 수동으로 추가할 경우에는 아래를 pom.xml<dependencies> 내에 추가합니다.

1
2
3
4
5
6
7
8
9
10
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-test</artifactId>
  <scope>test</scope>
</dependency>

 

2. 프로젝트를 생성하고 서버를 가동하면 어떤 URL에 접속해도 기본 로그인 화면만 나오게 됩니다. 기본 로그인 화면이 뜨지 않게 하기 위해 초기 세팅을 합니다.

[caption id=”attachment_1514” align=”alignnone” width=”296”] 기본 로그인 화면[/caption]

 

메인 스프링 애플리케이션인 SecurityApplication은 따로 변경하지 않고, SecurityConfig 클래스를 생성합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.example.security;

import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().antMatchers("/**").permitAll();
  }

}

컨트롤러인 TestController 도 작성합니다.

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
package com.example.security;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class TestController {
  
  @RequestMapping("/")
  public String home(ModelAndView mav) {
    return "home";	
  }
  
  @ResponseBody
  @RequestMapping("/test")
  public String test() {
    return "OK";
  }
  
  @ResponseBody
  @RequestMapping("/adminOnly")
  public String adminOnly() {
    return "Secret Page";
  }
}

 

3. 일단은 현재 연결된 데이터베이스가 없으므로 메모리상에 운영자 계정을 하나 만들고 로그인 페이지도 만들어서 운영자만 adminOnly 페이지에 접근 가능하도록 하는 예제를 만들기로 합니다.

SecurityConfig: withUser 부분은 사용자를 추가하는 부분이고 roles는 그 이름으로 된 역할을 부여합니다. WebSecurityConfigurerAdapter를 상속받았으므로 configure 메소드를 반드시 오버라이딩 해야 합니다. 주의할 점은 암호를 인코딩하는 부분인 passwordEncoder를 사용하지 않으면 에러를 유발합니다. ({noop}을 쓰는 다른 방법도 있는데 여기서는 다루지 않습니다.)

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
36
37
38
39
40
41
42
43
44
package com.example.security;

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
    .antMatchers("/adminOnly").hasAuthority("ROLE_ADMIN")
    .antMatchers("/**").permitAll()	// 넓은 범위의 URL을 아래에 배치한다.
    .anyRequest().authenticated()
    .and()
    .formLogin().defaultSuccessUrl("/")	
     // formLogin: 다른 옵션 설정이 없는 경우 시큐리티가 제공하는 기본 로그인 form 페이지 사용
    .and()
    .logout().logoutSuccessUrl("/");	
    // 로그아웃 기본 url은 (/logout)
    // 새로 설정하려면 .logoutUrl("url") 사용	
    
  }
  
  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
    .withUser("admin").password(passwordEncoder().encode("1234")).roles("ADMIN")
    .and()
    .withUser("guest").password(passwordEncoder().encode("guest")).roles("GUEST");

  }
  
  // passwordEncoder() 추가
  @Bean
  public BCryptPasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }

}
  • HttpSecurity http를 받는 첫 번째 configure 메소드는 일반적인 사항을 설정합니다. http는 내장된 대다수의 메소드들이 http 객체 자신을 반환하기 때문에 제이쿼리의 그것처럼 체이닝 하여 사용할 수 있습니다.
  • authorizeRequests는 특정 리퀘스트에 권한을 설정합니다.
  • antMatchers는 특정 URL을 지정합니다. hasAuthority는 왼쪽의 antMatchers에 권한을 설정하는데, 여기서는 "ROLE_ADMIN"이라는 권한을 가진 자들만 접속 가능하게 한다는 뜻입니다.
  • antMatcher의 파라미터가 "/**"인 경우 위의 "/adminOnly" URL을 제외한 나머지 모든 URL을 지정한다는 뜻이고, permitAll은 접근한 모든 사람(손님 포함)에게 접근을 허용하겠다는 뜻입니다.
  • .anyRequest().authenticated()는 권한별 접속을 활성화시킨다는 의미입니다. and()는 말 그대로 AND 조건절입니다.
  • defaultSuccessUrl("/")은 로그인 성공시 이동할 URL을 지정하는 곳이며 “/”를 입력한 경우 기본 페이지로 이동합니다. logoutSuccessUrl은 로그아웃 성공 시 이동할 페이지를 지정합니다.

 

TestController는 변경할 부분이 없습니다. pom.xml에 다음 부분을 추가 (주의: 버전 4가 아니라 5) 합니다. 이 예제는 뷰 렌더링 엔진으로 기본 제공되는 Thymeleaf를 사용한다고 가정합니다.

1
2
3
4
5
6
7
8
9
10
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<!-- 시큐리티 유틸리티, 4가 아니라 5를 써야된다. -->
<dependency>
  <groupId>org.thymeleaf.extras</groupId>
  <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>

 

뷰 페이지(Thymeleaf)를 작성합니다. 다른 설정을 하지 않았다면 src/main/resources/template/ 폴더 밑에 home.html이라는 이름으로 작성합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"  
    xmlns:th="http://www.thymeleaf.org"
    xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<meta charset="UTF-8">
<title>Select Menu</title>
</head>
<body>
  <a href="test">Test Page</a>
  <a sec:authorize="hasRole('ADMIN')" href="adminOnly">adminOnly Page</a>
  <a sec:authorize="!isAuthenticated()" href="login">Login</a>
  <a sec:authorize="isAuthenticated()" href="logout">Logout</a>
  <p sec:authorize="hasRole('ADMIN')">[이 부분은 운영자(ADMIN)에게만 나타남]</p>
  <p sec:authorize="hasRole('GUEST')">[이 부분은 손님(GUEST)에게만 나타남]</p>
  <p sec:authorize="isAuthenticated()">[이 부분은 로그인한 사용자(isAuthenticated) 모두에게 나타남]<p>
</body>
</html>

isAuthenticated()는 멤버가 로그인 했을 때 true를 반환하고 !를 붙이면 그 반대의 상황(false)에서 true를 반환합니다(!false이므로). hasRole('')은 로그인한 멤버가 특정 역할을 가지고 있을 때 true를 반환합니다.sec:authorizeth:if 비슷한 역할인데 시큐리티 전용이라고 생각하면 되겠습니다. /login, /logout은 스프링 시큐리티에서 기본으로 제공하는 로그인, 로그아웃 URL입니다.

 

[caption id=”attachment_1517” align=”alignnone” width=”264”] 첫 페이지[/caption]

 

[caption id=”attachment_1518” align=”alignnone” width=”418”] 운영자(admin)으로 로그인 한 경우[/caption]

 

[caption id=”attachment_1519” align=”alignnone” width=”315”] 운영자가 전용 페이지에 접근했을 때[/caption]

 

[caption id=”attachment_1520” align=”alignnone” width=”396”] 손님(guest)으로 로그인한 경우[/caption]

 

[caption id=”attachment_1521” align=”alignnone” width=”393”] 손님이 운영자 전용 페이지에 접속하려고 할 때[/caption]

 

[rcblock id=”3197”]

This post is licensed under CC BY 4.0 by the author.