다음 챕터는 Spring Data JPA 의 트러블슈팅 부분이었지만 실습 환경이 갖추어지지 않아서 차라리 다음 챕터인 Spring Security 의 개요 챕터를 보기로 했습니다.
알아야 하는 내용들
앞에서 이런저런 내용들을 이미 배우고 왔다고 가정합니다. 그 중 몇 가지 빠진 게 있는데요...
- SpEL (Spring Expression Language)
- UI 부분: thymeleaf
강의 자료는 gitlab과 github 에 올려주셨네요.
- https://gitlab.com/jongwons.choi/spring-boot-security-lecture
- https://github.com/jongwon/sp-fastcampus-spring-sec
언어가 "모던 자바" 라는 점이 좋네요. 모던 자바는 따로 시간을 내서 둘러보던지 해봐야겠습니다.
프로젝트 설정
gradle 에서 스크립팅을 통해 폴더 구조 구성 등을 하는 걸 보여주시는데, 어차피 한 번만 하면 되는건데 굳이 이럴 필요까지 있나라는 생각이 들더라구요. 그러므로 자세한 설명은 생략
도리어 spring security 를 사용하려면 포함시켜야 하는 의존성이 무엇인지가 빠져있습니다. 직접 해보려면 그게 제일 중요한데...
프로젝트 구성은 3개의 모듈로 구성한다는 약속입니다.
- comp: 공용 컴포넌트 aka cross-concern
- web: 프론트? 프론트가 들어갈까요? 설마 앞서 나왔던 thymeleaf 인가 하는게 자바 기반 프론트?
- server: 아마 spring이 여기에 들어갈듯
web과 server 의 차이는 잘 몰?루겠는데 아마 스프링은 server, web에는 프론트 리소스가 들어가지 않을까요? 강의 진행하면서 재차 확인하면 되겠죠.
bootJar.enabled = false 를 넣는 것도 프로젝트를 수동으로 설정해놨다면 그다지 의미가 있을 것 같진 않은...
Spring Security 의 개요:
Authentication(인증) vs Authorization(인가 or 권한)
스프링 시큐리티는 스프링 에코시스템의 보안 프레임워크로, 주로 authentication 과 authorization 에 대한 걸 주로 다룹니다. 사용자 데이터가 많은 곳이면 꼭 이 두 가지가 이슈가 되니까요.
이 두 개에 대한 개념은 fastcampus 강의를 시작하기 전에 이미 따로 공부하면서 개념의 차이에 대해서는 인지했으므로 자세히 설명하지는 않겠습니다.
- oauth 는 Authorization(인가 or 권한 - 너 이거 할 자격 있어요?) 를 위한 프로토콜이지만, 많은 곳에서 Authentication(인증 - 너는 누구세요?)을 위해 쓰이고 있습니다. 이 문제를 해결하기 위해 나온 게 OIDC 이구요. 제가 어떤 컨퍼런스의 동영상을 보고 OIDC 에 대해 정리했던 글을 읽어보실래요?
- M$의 ASP.NET Core 의 시큐리티 레이어 관련 문서에도 두 가지의 차이점이 잘 설명되어 있었습니다. 다만 당시 제가 원했던 건 커스텀 OAuth 외부 서비스 로그인을 만드는 거였는데 (*커스텀 로직 필요) 코드 쪽 문서는 당시에는 많이 부실했다능... (지금은 어떨지?)
물론 강의에서 유용했던 내용이 좀 더 있었으므로 걔들을 좀 정리하자면...
Authentication (인증)
- 요새에는 외부 서비스에 위임하는 경우가 많습니다. ID도 기억하기 힘들어서 이메일로 하는데, 이메일도 기억하기 힘들어서 이제 아예 외부 서비스로 로그인을 하는거죠. 그러니까 OIDC 쓰셔야...
- 구현적인 측면에서는 두 가지 방식이 있는데, 클래식하게 세션을 관리할 수도 있지만 이러면 상태 관리가 들어가므로 요즘에는 scale-out 이 가능하고 stateless 한 토큰 방식을 많이 쓴다네요. 메모리도 절약하고 일석이조? 분명 JWT 같은 걸 쓰겠죠. 다만 JWT 에 너무 많은 정보를 넣는 건 정말 주의해야...
Authorization (인가 / 권한확인)
일단 한글 번역이 "인가" 인 점이 이해하기 힘들어서 "권한" 이 낫지 않을까라고 말씀하시는 부분으로 시작하구요.
Spring Security 의 방식이 언급되었습니다.
- @Secured -> deprecated 돈유즈댓
- @PreAuthorize / @PostAuthorize -> 코드로 맛보기하는 부분에 나옵니다.
- 프로젝트의 인가 구조가 복잡해지면 AOP 를 이용해 다양한 방식으로 프로젝트에 알맞은 인가 구조를 만들어낸다는군요.
그은데 예전에 스프링 시큐리티 잠깐 깔짝 하고 공부해봤을 때에는 이거 말고도 CSRF 같은 얘기도 있었는데... 무튼 기본적인 보안 요소들도 처리해주는 게 맞겠죠? 그래도 이거 두 개가 중심인 건 맞을거구요.
프로젝트를 만들어서 맛보기
강의에서는 프로젝트를 처음부터 만들어보지 않습니다. 만드는 과정은... 사용하시는 gradle 프로젝트가 복잡해서 설명을 다 해주긴 하시는데요. 아무튼. 저는 클론을 하고싶지는 않아서 직접 Spring Initializr 로 (IJ IDE 내에서) 프로젝트를 생성했습니다. 잠깐 써볼거기도 하고 해서 프로젝트 분할도 따로 안 했구요.
- spring web
- spring security
- spring data jpa
- 쓰려는 DB 두 종류
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-web")
runtimeOnly("com.h2database:h2")
runtimeOnly("org.postgresql:postgresql")
기본 상태에서 유저 암호는 로그에 찍히는데, 귀찮으니 application.yml 에 넣어줍시다. 포트 변경은 덤이구요.
spring:
security:
user:
name: user
password: password
roles: USER
server:
port: 9050
# debug: true
이 상태로 접속해보면 로그인 화면이 뜹니다. Spring Security 는 기본으로 모든 경로가 로그인이 필요하도록 설정되어 있기 때문이라네요. 요거 설정은 좀 나중에 하고,
일단 컨트롤러부터 만들어봅시다.
package co.facam.secsandbox.home
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.security.core.Authentication
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
@RestController
class HomeController {
@GetMapping("/")
fun main() = "hello there!"
private fun curAuth(): Authentication = SecurityContextHolder.getContext().authentication
@GetMapping("/auth")
fun auth(): Authentication = curAuth()
@GetMapping("/user")
@PreAuthorize("hasAnyAuthority('ROLE_USER')")
fun user(): MyAuthInfo = MyAuthInfo(userKind = "user", auth = curAuth())
@GetMapping("/admin")
@PreAuthorize("hasAnyAuthority('ROLE_ADMIN')")
fun admin(): MyAuthInfo = MyAuthInfo(userKind = "admin", auth = curAuth())
}
이번에도 그냥 한번에 preauth 를 거는 예시까지 포함했습니다. authorization 이 막히는지 확인하기 위한 테스트용 클래스 MyAuthInfo 는 아래와 같습니다.
package co.facam.secsandbox.home
import org.springframework.security.core.Authentication
data class MyAuthInfo(
val userKind: String,
val auth: Authentication
)
이대로 실행하면 PreAuthorize 에 의해 막히거나 하지 않는다고 하네요. 직접 들어가보니 /admin 도 잘 들어가지는군요. 그래서 설정이 필요합니다.
package co.facam.secsandbox.conf
import org.springframework.context.annotation.Bean
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity
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.core.userdetails.User
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder
@EnableWebSecurity(debug = true) // @Configuration 포함인듯
@EnableGlobalMethodSecurity(prePostEnabled = true)
class SecurityConf: WebSecurityConfigurerAdapter() {
@Bean
fun passwordEncoder() = Argon2PasswordEncoder() // 강의에서는 Bcrypt
override fun configure(auth: AuthenticationManagerBuilder) {
// super.configure(auth)
// 이렇게 설정하면 application.yml 의 설정이 먹지 않게 됨
auth.inMemoryAuthentication()
.withUser(
User.builder()
.username("user1")
.password(passwordEncoder().encode("password1"))
.roles("USER")
)
.withUser(
User.builder()
.username("admin1")
.password(passwordEncoder().encode("password1"))
.roles("ADMIN")
)
}
override fun configure(http: HttpSecurity) {
// super.configure(http) // 강의에서는 super 를 잠깐 봤습니다.
http.authorizeRequests { req ->
req
.antMatchers("/").permitAll() // 디용 anyMatcher가 아니라 antMatcher 였네
.anyRequest().authenticated()
}
// 아래 두 개 빼먹었다가 로그인 폼이 안 나왔음
http.formLogin()
http.httpBasic()
}
}
먼저, 어노테이션을 단 설정에서 prePost를 활성화해주는 것만으로도 일단 pre/post 는 동작합니다.
@EnableWebSecurity(debug = true) // @Configuration 포함인듯
@EnableGlobalMethodSecurity(prePostEnabled = true)
class SecurityConf: WebSecurityConfigurerAdapter() {}
다만, 어차피 서로 다른 권한의 사용자를 테스트해봐야 하므로 두 가지 configuration 메서드가 추가되었습니다.
- 인메모리 사용자 소스를 제공해주는 configure(auth: AuthenticationManagerBuilder)
=> 사용자를 두 명 추가합니다. yml 에 설정했던 걸 읽어오지는 않았으므로 거기에서 설정했던 유저는 무시됩니다.- 사용자를 추가할 때, 암호 인코더를 넣어야 합니다 (강의에서 오류났었음). 저는 bcrypt 대신 좀 더 쎈 argon2 를 넣었습니다.
- 두 번째 configure(http: HttpSecurity) 에서는 접속 관련 설정을 추가해서, / 경로는 로그인 없이 접속할 수 있게 만듭니다. 일종의 덤이 되겠습니다. 끝에 두 개 빼먹었다가 낭패봄; 기본 로그인 폼을 추가하는 코드도 있어야 합니다.
근데 bean 만드는 건 저렇게 대충 해도 지정되는건가...? 싶네요. 아무튼.
그리고나서 되겠지 해봤는데 오류가 나더라구요. argon2 를 썼으므로 bouncycastle 이 필요합니다.
https://github.com/spring-projects/spring-security/issues/8842
runtimeOnly("org.bouncycastle:bcprov-jdk15on:1.70")
그리고 강의에서 자동으로 로그인이 되는 게 있다고 그것도 껐었는데 뭐였는지 잘. 뭐 앞으로 차차 자세히 배울거니 상관이 없으려나요?
anyway, this works as we expected.
본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다.
패스트캠퍼스: https://bit.ly/37BpXiC
#패스트캠퍼스 #패캠챌린지 #직장인인강 #직장인자기계발 #패스트캠퍼스후기 #한번에끝내는JavaSpring웹개발마스터초격차패키지Online
화라벨페이지 그냥 기분이 조음
이거 좀만 더 배우면 진짜로 내가 쓸 개인 프로젝트 같은거도 되겠는데...
'FastCampus - 한번에 끝내는 Java|Spring 웹 개발 > 04-2 시큐리티' 카테고리의 다른 글
Security 02-06: DB에 계정 넣기 - 패스트캠퍼스 챌린지 50일차 (0) | 2022.03.14 |
---|---|
Security 02-05: Basic Auth (2) - 패스트캠퍼스 챌린지 49일차 (0) | 2022.03.13 |
Security 02-05: Basic Auth - 패스트캠퍼스 챌린지 48일차 (0) | 2022.03.12 |
Security 02-04: Authentication 메커니즘 - 패스트캠퍼스 챌린지 47일차 (0) | 2022.03.11 |
Security 02: 전체 구조 - 패스트캠퍼스 챌린지 46일차 (0) | 2022.03.10 |