
스프링 기반의 애플리케이션의 보안(인증과 권한)을 담당하는 프레임워크로서 Spring Framework의 하위 프로젝트 중 하나이다. Spring Security는 애플리케이션의 인증(Authentication)과 인가(Authorization)의 개념을 기반으로 웹 애플리케이션의 보안을 관리할 수 있도록 구성되어 있다.

Filter : Spring 웹 애플리케이션의 진입점과도 같은 DispatcherServlet에 요청이 도달하기 이전에 해당 요청이 애플리케이션에 들어가도 되는지 확인 (인증)한다. 전처리가 끝나면 DispatcherServlet에 전달한다.
Interceptor : Dispatcher Servlet과 Controller 사이에 존재하며, 컨트롤러에게 요청이 전달되기 전에 해당 컨트롤러에서 처리해도 되는 요청인지 확인 (인가)하고, 그 요청에 부가적인 작업이 필요한지 확인하여 필요하다면 그걸 돕는 역할을 한다.
Servlet Filter는 클라이언트로부터 들어오는 요청을 가로채어 특정한 작업을 수행한 후에, 원래의 요청을 서블릿이나 다른 리소스로 전달할 수 있도록 해주는 도구이다. 필터는 모든 요청에 적용하지 않고 특정한 요청 경로(URL) 또는 특정 서블릿에만 작동하도록 설정할 수 있다.
⛓️ 필터 체인 (Filter Chain) 으로 각 필터 체인이 자신이 수행할 (필터링할) 부분을 처리하고, 다음 필터로 요청을 전달하거나, 처리 중단을 결정할 수 있다. 필터 체인은 여러 개의 필터로 구성될 수 있고, 필터는 필터 체인 내에서 순차적으로 실행된다.
http
.csrf(csrf -> csrf.disable())
.formLogin(Customizer.withDefaults())
.authorizeHttpRequests(auth -> {
auth.requestMatchers("/signup", "/signin").anonymous();
auth.requestMatchers("/users/**").hasRole("MEMBER");
auth.anyRequest().authenticated();
})
.csrf , .formLogin, .authorizeHttpRequests() 의 메서드를 체이닝해두었다. 이 메서드들 자체가 필터는 아니고, 보안 정책을 구성하는 설정이다.SecurityFilterChain으로 완성된다.사용자 또는 시스템의 신원을 확인하는 과정이다. 즉, 자신이 누구인지 증명하도록 요구하는 과정이다. Spring Security에서 인증 과정은 사용자가 제공하는 자격 증명, 보통 사용자 이름과 비밀번호를 기반으로 이루어진다.
인증을 통해 확인된 사용자가 특정 자원이나 기능에 접근할 수 있는 권한을 가지는지를 결정하는 과정이다. 인가 과정을 통해 인증을 거친 사용자가 특정 페이지에 접근하거나, 특정 데이터를 조회하거나, 관리자 권한이 필요한 기능을 사용할 수 있는지를 결정한다.
SecurityContextHolder는 현재 로그인한 사용자 정보를 어디서든 꺼내 쓸 수 있게 해주는 저장소이다. ThreadLocal을 이용해서 각 요청(스레드)마다 독립된 SecurityContext를 저장한다.
스레드마다 독립된 공간을 제공하는 자바 기능으로, 스레드마다 독립적인 값을 저장할 수 있게 해준다. SecurityContextHolder도 ThreadLocal<SecurityContext>로 구현되어 있기 때문에 요청마다 각 스레드는 자기만의 SecurityContext를 가지게 된다.
SecurityContext는 현재 인증된 사용자의 정보를 담고 있는 인터페이스이다. 이 객체는 Authentication 객체를 포함하고 있으며, 이 Authentication 객체는 사용자의 인증 상태와 권한 정보를 나타낸다.
사용자의 인증 및 권한 정보를 표현하는 인터페이스로, 사용자가 누구인지, 인증이 성공적으로 이루어졌는지, 그리고 사용자가 어떤 권한을 가지고 있는지 등의 정보를 담고 있다.
UserDetails 객체이거나, 사용자명(username)을 나타내는 String일 수 있다.UserDetails와 UserDetailsService는 Spring Security에서 사용자 인증을 처리할 때 사용된다. UserDetails는 사용자의 이름, 비밀번호, 권한 등을 포함한 사용자 정보를 캡슐화하는 인터페이스로, 이를 통해 사용자가 인증 가능한지 확인한다.
UserDetailsService는 사용자명(username)을 기반으로 UserDetails 객체를 로드하는 역할을 하며, 사용자 인증 과정에서 필요한 사용자 정보를 데이터베이스나 다른 저장소에서 가져오는 데 사용된다.
UsernamePasswordAuthenticationFilter 필터가 사용되고, UsernamePasswordAuthenticationToken 객체가 생성된다.DaoAuthenticationProvider 구현체에게 전달된다.UserDetailsService를 통해 사용자 정보를 불러오고, 인증을 검증한다.PasswordEncoder를 사용하여 비밀번호를 UserDetails에 저장된 암호화된 비밀번호와 비교한다. SecurityContextHolder에 담긴다. 만약, 인증에 실패할 경우, Authentication 객체는 저장되지 않고 버려진다.
- Authentication Manager : 인증 요청을 처리하는 총괄 관리자
authenticate()메서드를 이용한다.- Authentication Provider : 실제 인증 로직을 수행하는 인증 실행자
support()메서드를 호출하여 현재 요청이 처리 가능한 타입인지 확인한다.- Spring Security는 폼 방식, LDAP 인증, OAuth 2.0 인증, JWT 인증 등 여러 종류의 인증 방식을 지원하는데,
AuthenticationManager를 통해 여러 Provider들을 가지고 있다가 로그인 요청이 들어오면 적절한 Provider가 골라져서 인증을 수행한다.
SecurityConfig는 어플리케이션의 보안 정책을 종합적으로 정의하고 관리하는 중심적 구성 요소로서, 웹 어플리케이션의 안정성과 접근 제어를 보장하는 역할을 한다.
SecurityConfig는 사용자 인증을 위한 설정을 담당한다. 여기에는 사용자 정보의 출처를 정의하는 UserDetailsService, 비밀번호 암호화 방식, 그리고 인증 요청 처리 방식을 설정하는 작업이 포함된다. 이 과정에서 개발자는 데이터베이스, LDAP, OAuth 2.0 등 다양한 인증 방법을 선택하고 구현할 수 있다.
권한 부여는 사용자가 애플리케이션 내의 자원에 접근할 수 있는지 여부를 결정하는 과정이다. SecurityConfig는 URL 기반 접근 제어와 메서드 기반 접근 제어를 설정하여, 특정 역할 (Role)이나 권한(Authority)을 가진 사용자만이 특정 자원에 접근할 수 있도록 보장한다.
SecurityConfig는 HTTPS를 통한 통신 강제, CSRF 보호, 세션 관리 등 웹 애플리케이션의 보안과 관련된 다양한 HTTP 설정을 제공한다. 예를 들어, HTTP 헤더를 통한 보안 강화나 특정 요청에 대한 필터링 정책 등을 정의할 수 있다.
Spring Security는 높은 수준의 유연성을 제공하며, SecurityConfig 파일을 통해 애플리케이션의 특수한 보안 요구 사항에 맞게 보안을 커스터마이징 할 수 있다.
Security Form 로그인 방식은 사용자가 애플리케이션에 접근하려 할 때, 먼저 인증을 거치도록 요구하는 표준화된 보안 절차이다. 사용자에게 로그인 폼을 제공하여, 이를 바탕으로 사용자의 신원을 확인한다. 이 과정은 주로 HTML 폼을 통해 구현되며, POST 요청을 통해 서버로 전송된 사용자 입력값을 서버에서 처리하게 된다.
UsernamePasswordAuthenticationFilter 필터를 통해 요청을 처리한다. 인코딩(Encoding)과 디코딩(Decoding)은 정보의 전달과 저장 과정에서 데이터를 변환하고 복원하는 필수적인 과정이다. 이 과정은 데이터 표현 방식과 형태를 바꾸어, 효율적인 전송과 저장을 가능하게 한다.
Spring Security에서 주로 비밀번호를 안전하게 처리하는 데 사용된다. 사용자가 입력한 비밀번호는 원문 그대로 저장되는 것이 아니라, 특정한 인코딩 방식, 주로 해시 함수를 사용하여 변환된 후 저장된다. 이러한 인코딩은 비밀번호를 단방향 암호화하여, 원본 비밀번호를 알 수 없게 함으로써 정보 유출 시에도 사용자의 비밀번호가 보호되도록 한다.
PasswordEncoder 인터페이스를 통해 다양한 인코딩 알고리즘을 지원하며, 가장 일반적으로는 BCryptPasswordEncoder 가 사용된다. 이 알고리즘은 비밀번호를 해시화하고, 추가적인 솔트(Salt)를 적용하여 동일한 비밀번호더라도 서로 다른 해시값을 생성하도록 한다.Spring Security에서 인코딩된 데이터를 원래 상태로 복원하기보다는, 주로 검증하는 역할을 수행한다. 비밀번호 인증 과정에서 입력된 비밀번호는 사용자가 저장한 인코딩된 비밀번호와 비교된다. 이 과정에서 입력된 비밀번호는 같은 방식으로 인코딩되어, 저장된 해시값과 일치하는지 확인한다. 이는 단방향 인코딩의 특성상 실제로 원래의 비밀번호를 복원하지 않으며, 대신 비교를 통해 인증을 수행한다.
CSRF (Cross-Site Request Forgery)는 공격자가 사용자의 인증 정보를 이용해 악의적인 요청을 서버에 보내는 방식으로 이루어진다. 이 공격은 사용자가 인증된 상태에서 요청을 위조해서 보내고, 사용자의 의도와는 관계없이 서버에서 원치 않는 작업이 수행되도록 유도한다.
CSRF 공격을 방지하기 위한 주요한 방법 중 하나로, 서버가 발급한 임의의 문자열로, 사용자가 웹 폼을 제출할 때, 이 토큰을 포함시키는 방식으로 구현된다.
웹 애플리케이션에서 서로 다른 출처 간의 리소스 공유를 가능하게 하는 보안 메커니즘으로, 웹 브라우저의 기본적인 동일 출처 정책(Same-Origin Policy)을 보완한다.
Access-Control-Allow-Origin)를 통해 원칙적으로 허용된 출처를 명시하고, 이를 통해 교차 출처 요청의 안정성을 관리한다.출처란 3가지로 결정된다. 프로토콜 (http, https), 도메인 (example.com, localhost), 포트번호 (:8080, :3000) 이 3개 중 하나라도 다르면 다른 출처이다.
기본적으로 웹 페이지는 자신과 동일한 출처에서만 자원을 요청할 수 있도록 제한하며, 이는 보안상 중요한 기능이나, 다양한 출처의 자원에 접근할 필요가 있는 현대의 웹 앺프리케이션에서는 제약이 될 수 있다.
인증을 위한 개방향 표준 프로토콜이다.
요즘 대다수의 웹 서비스는 로그인 시 외부 소셜 계정을 기반으로 간편하게 인증하는 인증 서비스를 제공한다. 카카오, 구글, 페이스북 등 자신이 해당 플랫폼의 계정을 갖고만 있다면 로그인할 수 있다.
이렇게 Third-Party 프로그램 (개발 중인 웹 애플리케이션)이 Client를 대신해 리소스 서버에서 제공하는 자원에 대한 접근 권한을 위임받는 방식을 OAuth 방식이라고 한다.
1. Resource Owner
데이터를 소유하고 있는 사용자로, 로그인하려고 하는 사용자를 의미한다.
로그인에 필요한 개인정보를 가지고 있는 소유자라고 이해하면 된다.
2. Client
리소스 소유자의 데이터를 사용하려는 애플리케이션
즉, 우리가 개발하고 있는 애플리케이션이 클라이언트다.
3. Authorization Server
인증 서버로, 외부 플랫폼에서는 인증을 해주는 서버가 따로 있다.
4. Resource Server
자원 서버로, 외부 플랫폼에서 실제 이 리소스(개인정보)를 가지고 있는 서버를 말한다.
OAuth 동작을 위해 우선, Cleint를 Resource Server에 등록해야 한다. 이 때 Redirect URI도 함께 등록해야 하는데, 해당 위치는 사용자가 OAuth 2.0 서비스에서 인증을 마치고 리다이렉션 시킬 위치이다.
웹 서비스 등록을 성공적으로 마치면, Client ID와 Client Secret을 얻을 수 있다.

위는 OAuth 2.0 동작과정 시퀀스 다이어그램이다.
Resource Owner가 외부 플랫폼을 통한 로그인을 시도한다.
Client는 OAuth 프로세스를 시작하기 위해 Resource Owner의 브라우저를 Authoriztion Server로 보낸다.
Client는 이 때 Authorization URL에 response_type, client_id, redirect_uri, scope 등의 매개변수를 쿼리 스트링으로 포함하여 보낸다.
Client로부터 Authorization URL로 이동된 Resource Owner는 제공된 로그인 페이지에서 ID/PW을 입력하여 인증을 시도한다.
Authorization URL에서 인증이 성공했다면, Authorization server는 기존에 설정한 Redirect URL에 Authorization Code 를 포함하여 사용자를 리다이렉션(GET방식) 시킨다.
Authorization code(권한부여 승인코드)란 리소스 접근을 위한 Access Token을 획득하기 위해 사용하는 임시코드이다.
Client는 다시 Authorization Server에 Authorization Code를 전달하고, Access Token을 발급받는다. Client는 자신이 발급받은 Resource Owner의 Access Token을 데이터베이스에 저장하고, 이후 Resource Server에서 Resource Owner의 리소스에 접근하기 위해 Access Token을 사용한다.
Access Token은 절대 유출되면 안된다 !
Client는 Resource Owner에게 로그인이 성공했음을 알린다. 이제 Access Token을 가지고 접근 가능한 Resource에 접근할 수 있다.
이제 Access Token을 발급 받았기 때문에 정해진 Scope 내에서 다양한 리소스를 이용 할 수 있다.
JWT(Json Web Token)은 Json 객체에 인증에 필요한 정보들을 담은 후 비밀키로 서명한 토큰이다. 그리고 JWT 기반 인증은 JWT 토큰 (Access Token)을 HTTP 헤더에 실어 서버가 클라이언트를 식별하는 방식이다. 인터넷 표준 인증 방식으로, 공식적으로 인증 & 권한 허가 방식으로 사용된다.
JWT는 .를 구분자로 나누어지는 세 가지 문자열의 조합이다.

JWT에서 사용할 타입과 해시 알고리즘의 종류가 담겨있다.
{
"alg" : "HS256",
"typ" : "JWT"
}
서버에서 첨부한 사용자 권한 정보와 데이터 본문이 담겨있다.
{
"sub" : "1234567890",
"name" : "John Doe",
"iat" ; 1516239022
}
Header, Payload를 Base64 URL-safe Encode를 한 이후 Header에 명시된 해시 함수를 적용하고, 서버 측 키(key)로 서명한 전자 서명이 담겨있다.
@Slf4j
@Configuration
public class SecurityConfig {
@Configuration 을 통해 이 클래스가 설정 클래스라는 것을 알려준다.@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
SecurityFilterChain은 Spring Security의 필터 체인을 설정하는 핵심 메서드이다. HttpSecurity 는 보안 설정을 구성할 수 있게 도와주는 도구(빌더)이다. Spring Security에서는 HTTP 요청에 대한 보안 설정을 해야 하는데, 로그인 방식, CSRF를 막을지 말지, 어떤 URL에 인증이 필요한지, 로그아웃, 세션 처리 등을 어떻게 다룰지 보안 관련 모든 설정을 도와주는 API이다. http.cors( cors -> cors.disable() )
.csrf ( csrf -> csrf.disable() );
http.formLogin(Customizer.withDefaults());
/login에 있다..formLogin(
form -> {
form.failureUrl("/login?error=true")
.successForwardUrl("/")
.defaultSuccessUrl("/", true)
.usernameParameter("loginId")
.passwordParameter("loginPwd")
.loginProcessingUrl("/signin")
.loginPage("/signin")
.permitAll();
}
)
loginPage("/signin") : 사용자가 직접 만든 로그인 페이지 경로 (GET /signin)loginProcessingUrl("signin") : 로그인 요청을 처리할 경로 (POST /signin)action="/signin"과 매칭된다.usernameParameter, passwordParameter : form 에서 사용하는 input name 이름을 지정해주는 것이다. (기본은 username, password)defaultSuccessUrl("/", true) : 로그인 성공 시 무조건 /로 이동, true가 없으면, 원래 가려던 페이지로 리다이렉트 한다. (POST -> GET)successForwardUrl("/") : 서버 내부에서 forward 하며, 주소창 URL이 바뀌지 않는다. (POST -> POST)failureUrl("/login?error=true") : 로그인 실패 시 이동할 URLpermitAll() : 로그인 페이지는 아무나 접근할 수 있도록 허용한다. http.logout( logout -> {
logout.logoutUrl("/signout")
.logoutSuccessUrl("/")
.clearAuthentication(true)
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID");
})
logoutUrl("/signout") : 클라이언트가 로그아웃을 요청할 URL을 설정한다. (기본값은 /logout)logoutSuccessUrl("/") : 로그아웃이 성공한 후 이동할 URL로, Redirect 방식이다. (브라우저 주소창 바뀜)아래는 다 default로 설정되어있기는 하다.
clearAuthentication(true) : 현재 SecurityContext 안에 들어있는 Authentication 객체를 null로 제거한다.invalidateHttpSession(true) : 세션을 날려 세션을 무효화한다.deleteCookies("JSESSIONID") : 쿠키를 날려준다.http.authorizeHttpRequests(
auth -> {
auth.requestMatchers("/signup", "/signin")
.anonymous()
.requestMatchers("/users/**")
.hasRole("MEMBER")
.anyRequest()
.authenticated(); // 인증만 받으면 나머지 URL에 접근할 수 있도록 풀어주겠다.
//.denyAll(); // 다른 URL로는 접근 자체가 안되도록 막는 것
}
)
requestMatchers("/signup", "/signin").anonymous() : URL에 대해서 인증, 인가 작업을 어떻게 할 건지 설정할 수 있다. /signup과 /signin은 로그인하지 않은 사람만 접근 가능하다. 가변인자로 받기 때문에 여러 URL을 넣어줄 수 있다. anonymous 는 인증을 받지 않은 익명 상태를 의미한다. 로그인한 (인증을 받은) 사용자가 접근하면 403 에러가 난다. .requestMatchers("/users/**").hasRole("MEMBER") : /user/로 시작하는 모든 경로는 MEMBER 권한이 있는 사용자만 접근 가능하다. hasRole("MEMBER") 은 내부적으로 "ROLE_MEMBER"로 인식된다. .anyRequest().authenticated() : 지정한 URL 외의 모든 요청은 인증된 사용자만 접근 할 수 있다. 권한은 필요 없고 로그인만 되어 있으면 된다. denyAll()을 사용하면 로그인 여부와 관계없이 모든 요청은 거절된다. permitAll()을 사용하면 로그인을 안 해도 누구나 접근 할 수 있다. @Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
BCrpytPaswordEncoder로 명시적으로 설정해둬도 되고, PasswordEncoderFactories에서 메서드로 인코더를 생성해줘도 된다. (자동으로 BCrpyt 방식을 채택한다.)@Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
String targetPwd = "1234";
String encoded = passwordEncoder().encode(targetPwd);
log.info("encoded = {}", encoded);
manager.createUser(
User.withUsername("user")
.password(encoded)
.build()
);
return manager;
}
InMemoryUserDetailsManager : 메모리 내에서 사용자 정보를 관리한다. 주로 개발 및 테스트에 사용된다. UserDetailsManager은 UserDetailsService의 확장으로, 새 사용자를 생성하고 기존 사용자를 업데이트하는 기능을 제공한다. InMemoryUserDetailsManager, JdbcUserDetailsManager, LdapUserDetailsManager 등과 같은 구현체들이 있다. passwordEncoder()를 통해 인코더를 생성하고 비밀번호를 인코딩한다.manager.createUser() : UserDetails를 인자로 받는다. UserDetails를 구현한 User 객체를 빌더 패턴으로 만들어 인자로 넣어준다. 와.. ㅎ 양 많고.. 어렵네 어려워 쉽지 않아.. 근데 또 오늘은 수업할 때 이해됐냐 O/X에 O를 누르는 숫자가 유난히 많은 기분.. (눈물) 하하하 다 정리하고 나니 굉장히 재밌긴 한데, 수업 들을 땐 이론도 뭐 처음 듣는 클래스 이름 다 나오고 ~ 토큰 블라블라~~ 하니까 어지러웠고,, 이론도 살짝 꼬이니까 어렵고... 하하 난 일단 이론부터 좀 잘 알고 가고 싶은 타입인 것 같다. 이론도 잘 이해하고 실습 코드 보니까 잘 보인다 !
어제 분명히 겁없이 부딪혀주겠다고 했는데 진짜 겁없이 부딪힌 것 같다. 아야.. ㅋ 오늘 열심히 공부했으니 내일은 쉽겠지... 하고 생각해봅니다..! 아자 아자 !! 파이팅 ~!~!~!
아 그리고 31일 기념으로 썸네일을 바꿔보았다. 예뻐서 만족스럽군