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:authorize는 th: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”]

