일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- CentOS
- visualvm
- 시큐리티
- 남궁성과 끝까지 간다
- 스프링의 정석
- 항해99
- JavaScript
- 쇼트유알엘
- @jsonproperty
- 패스트캠퍼스
- MYSQL
- Spring Security
- AWS
- 웹개발
- 데이터베이스
- 스웨거
- emqx
- DB
- 카프카
- 생성자 주입
- java
- 개인프로젝트
- JWT
- WEB SOCKET
- 프로그래머스
- Spring
- docker
- 스파르타코딩클럽
- Kafka
- EC2
- Today
- Total
Nellie's Blog
[항해99][강의정리][spring boot] Spring 심화주차(W5) - 시큐리티 총정리 (Spring Security에서 JWT를 사용하기) 본문
[항해99][강의정리][spring boot] Spring 심화주차(W5) - 시큐리티 총정리 (Spring Security에서 JWT를 사용하기)
Nellie Kim 2022. 12. 28. 17:57[목차]
1. Spring Security 란?
2. Spring Security 주요 컴포넌트 확인하기
3. Spring Security의 Default Form Login 방식 사용해보기
4. UserDetails, UserDetailsService Custom 해보기
5. 비밀번호 암호화 이해하고 적용하기
6. CustomSecurityFilter 적용해보기
7. @AuthenticationPrincipal
8. @Secured
9. 401, 403 Error ExceptionHandling 해보기
10. Security에서 JWT를 사용한 인증/인가 흐름 짚고 넘어가기
목차 중 1,2,10번만 정리했다.
1. Spring Security 란?
- 'Spring Security' 프레임워크는 스프링 서버에 필요한 사용자 인증 및 인가를 위해 많은 기능을 제공해주는 강력한 Acess제어 프레임워크이다.
- 마치 '스프링' 프레임워크가 웹 서버 구현에 편의를 제공해 주는 것과 같다.
- Spring Security는 표준 서블릿 필터를 사용한다.
- 다른 요청들과 마찬가지로 HttpServletRequest와 HttpServletResponse를 사용한다.
- Spring Security는 서비스 설정에 따라 필터를 내부적으로 구성한다.
1) 스프링 시큐리티 적용하는 방법
'스프링 시큐리티' 프레임워크 추가
//build.gradle에 추가
implementation 'org.springframework.boot:spring-boot-starter-security'
'스프링 시큐리티' 활성화 하기
//WebSecurityConfig
import org.springframework.context.annotation.Configuration;
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;
@Configuration
@EnableWebSecurity // 스프링 Security 지원을 가능하게 함
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// image 폴더를 login 없이 허용
.antMatchers("/images/**").permitAll()
// css 폴더를 login 없이 허용
.antMatchers("/css/**").permitAll()
// 어떤 요청이든 '인증'
.anyRequest().authenticated()
.and()
// 로그인 기능 허용
.formLogin()
.loginPage("/user/login")
.defaultSuccessUrl("/")
.failureUrl("/user/login?error")
.permitAll()
.and()
// 로그아웃 기능 허용
.logout()
.permitAll();
}
}
SpringBoot 3v 변경된 코드 확인
//SpringBoot 3v 변경된 코드 확인 authorizeRequests() → authorizeHttpRequests()
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// CSRF 설정
http.csrf().disable();
http.authorizeHttpRequests().anyRequest().authenticated();
// 로그인 사용
http.formLogin();
return http.build();
}
@EnableWebSecurity 어노테이션은 Spring Boot 를 사용하고 있을 경우,
SecurityAutoConfiguration 에서 import 되는 WebSecurityEnablerConfiguration 에 의해 자동으로
세팅 되므로 추가하지 않아도 된다.
* WebSecurityConfigurerAdaptor클래스가 Deprecated된 문제해결
시큐리티를 기본설정하는 SecurityConfig클래스는 WebSecurityConfigurerAdaptor라는 세부보안기능설정클래스를 상속받아서, configure 메소드를 오버라이딩해서 사용하는 방법으로 구현해 왔다. 그러나 이제는 사용할 수 없다.
SecurityFilterChain를 Bean으로 등록해서 사용해야 한다.
간단히 사용법 구/신버전을 비교해보자.
1. WebSecurityConfigurerAdaptor를 상속하여 cofigure메소드를 오버라이딩하는 방법
2. 직접 SecurityFilterChain , WebSecurityCostomizer를 bean으로 등록하는 방법
* WebSecurity VS HttpSecurity ?
Spring Security는 기본적으로 모든 자원에 접근하는 요청에 대해서 인증 + 인가를 확인해서 자원에 접근할 수 있는 경우에만 값을 내려준다. 이렇게 인증 / 인가를 모두 검사하는 것이 좋을 것처럼 보이지만 필요하지 않는 경우가 있다. 예를 들어 정적인 자원들에 대한 값이다.
예를 들어 특정 URL에 접근했을 때, View가 렌더링 된다고 가정해보자. 이미 URL에 접근할 수 있다는 인가를 받으면 그 화면이 바로 랜더링이 되면 된다. 그렇지만 스프링 시큐리티는 기본적으로 모든 접근에 대해서 인증/인가를 검색하기 때문에 이 URL을 랜더링 하는데 필요한 정적인 파일(CSS / HTML / JS) 파일들을 모두 다시 한번 검사한다.
사실 이렇게 동작하게 되면 쓸데없는 일의 반복이 될 수 있기 때문에 정적인 파일에 한해서는 검사를 Skip할 수 있도록 하는 기원을 지원한다. Spring Security에서 지원하는 이 기능을 Web Ignore라고 한다.
Web Ignore와 PermitAll은 같아보이지만 다르다. Web Ignore와 permitAll() 모두 인증없이 접근이 가능하도록 한다는 점에서는 같은 점이 있다. 그렇지만 이 동작 과정을 들여다보면 근본적인 차이가 있다.
PermitAll은 인증이 필요없다고 하지만, 반드시 FilterSecurityInterceptor까지 온 다음에 DispatcherServlet으로 넘어간다. 이 말은, 비록 인증이 필요없다고 하지만 반드시 Spring Security가 제공하는 보안 필터를 거친다는 의미이다. 반대로 WebIgnore는 SpringSecurity가 제공하는 Filter를 거치지 않는다는 점이다.
🔥 CSRF란?
CSRF(사이트 간 요청 위조, Cross-site request forgery)
- 공격자가 인증된 브라우저에 저장된 쿠키의 세션 정보를 활용하여 웹 서버에 사용자가 의도하지 않은 요청을 전달하는 것
- CSRF 설정이 되어있는 경우 html 에서 CSRF 토큰 값을 넘겨주어야 요청을 수신 가능
- 쿠키 기반의 취약점을 이용한 공격 이기 때문에 REST 방식의 API에서는 disable 가능
- POST 요청마다 처리해 주는 대신 CSRF protection 을 disable
- 예 )
사용자가 쇼핑몰에 접속하여 로그인 후 쿠키를 발급받은 뒤 공격자가 사용자의 이메일로 특정 링크를 전달하고 사용자가 해당 링크를 클릭하게 되면, 공격용 웹페이지에 접속하게 되고, 해당페이지에 '로또당첨'이라는 이미지가 노출된다. 그리고 유저가 로또당첨 이미지를 클릭하면 쇼핑몰에 특정 URL로 요청을 하게되는데 해당 쿠키정보를 가지고 있기 때문에 해당 요청에 대해서 정상적으로 동작을 하게 된다.
이처럼 사용자의 의도와는 무관하게 공격자가 심어놓은 특정 방식을 통해 자원 요청을 하게되고 그것을 응답을 받을 수 있도록 하는 것을 CSRF(사이트 간 요청 위조) 라 한다.
2) 스프링 시큐리티 구조
- 사용자가 입력한 정보를 가지고 인증을 요청한다. (Request)
- AuthenticationFilter가 이를 가로채 UsernamePasswordAuthenticationToken(인증용 객체)를 생성한다.
- 필터는 요청을 처리하고 AuthenticationManager의 구현체 ProviderManager에 Authentication과 UsernamePasswordAuthenticationToken을 전달한다.
- AuthenticationManager는 검증을 위해 AuthenticationProvider에게 Authentication과 UsernamePasswordAuthenticationToken을 전달한다.
- 이제 DB에 담긴 사용자 인증정보와 비교하기 위해 UserDetailsService에 사용자 정보를 넘겨준다.
- DB에서 찾은 사용자 정보인 UserDetails 객체를 만든다.
- AuthenticationProvider는 UserDetails를 넘겨받고 비교한다.
- 인증이 완료되면 권한과 사용자 정보를 담은 Authentication정보를 전달한다.
- AuthenticationFilter까지 Authentication 객체가 반환된다.
- Authentication을 SecurityContext에 저장한다.
Authentication정보는 결국 SecurityContextHolder 세션 영역에 있는 SecurityContext에 Authentication 객체를 저장한다. 세션에 사용자 정보를 저장한다는 것은 전통적인 세션-쿠키 기반의 인증 방식을 사용한다는 것을 의미한다.
스프링 시큐리티는 기본적으로 세션 기반 인증을 사용한다.
이번 5주차 심화 과제에서는 기본제공되는 세션기능을 Stateless처리 하고, JWT를 사용할 것이다.
즉, 이 실습에서는 토큰방식이 적용되어 있지 않기 때문에 Filter에서 사용자가 요청한 로그인을 검증하고 인증하고 있지만,
토큰방식을 적용하게 되면 사용자의 로그인,회원가입과 같은 요청은 Filter에서 인증되지 않게 permitAll 처리하여,
실제 검증 및 인증 처리는 service 에서 수행하고,
그외의 인증이 필요한 요청 에서는 로그인을 통해 발급받은 토큰을 같이 보내 Filter에서 토큰을 검증하고 인증처리를 하게 됩니다.
2. Spring Security 주요 컴포넌트 확인하기
1) Spring Security 와 Filter
Spring Security는 요청이 들어오면 Servlet FilterChain을 자동으로 구성한 후 거치게 한다.
FilterChain은 여러 Filter를 chain형태로 묶어놓은 것을 의미한다.
여기서 Filter 란, 톰캣과 같은 웹 컨테이너에서 관리되는 서블릿의 기술이다.
Filter는 Client 요청이 전달되기 전후의 URL 패턴에 맞는 모든 요청에 필터링을 해준다.
CSRF, XSS 등의 보안 검사를 통해 올바른 요청이 아닐 경우 이를 차단해 준다.
따라서 Spring Security는 이러한 기능을 활용하기위해 Filter를 사용하여 인증/인가를 구현하고 있다.
인가 API
2) SecurityFilterChain
Spring 의 보안 Filter를 결정하는데 사용되는 Filter session, jwt 등의 인증방식들을 사용하는데에 필요한 설정을 완전히 분리할 수 있는 환경을 제공한다.
클라이언트가 요청을 하면 DelegationFilterProxy가 요청을 가로채고 Spring Security의 빈으로 전달한다.
아래는 Security Filter 들의 실행 순서입니다. 우선적으로 우리는 여기서 UsernamePasswordAuthenticationFilter 만 한번 눈으로 확인하고 넘어가면 되겠습니다.
* 서블릿 컨테이너와 필터 참고
3) AbstractAuthenticationProcessingFilter
사용자의 credential을 인증하기 위한 베이스 Filter
4) UsernamePasswordAuthenticationFilter
UsernamePasswordAuthenticationFilter 는 위의 AbstractAuthenticationProcessingFilter를 상속한 Filter다.
기본적으로 아래와 같은 Form Login 기반을 사용할 때 username 과 password 확인하여 인증한다.
Form Login 기반은 인증이 필요한 URL 요청이 들어왔을 때 인증이 되지 않았다면 로그인페이지를 반환하는 형태이다.
1. AntPathRequestmatcher(/login)
→ 사용자가 요청한 요청정보를 확인하여 요청정보 Url이 /login으로 시작하는지 확인한다.
요청한다면 다음단계로(인증처리) 진행되고, 일치하지 않는다면 다음 필터로 진행된다.(chain.doFilter)
/login url은 .loginProcessingUrl()으로 변경 가능하다.
2. Authentication 에서 실제 인증처리를 하게 되는데, 로그인 페이지에서 입력한 Username과 Password를 인증객체(Authentication)에 저장해서 인증처리(AuthenticationManager)를 맡기는 역할을 한다.
→ 여기까지가 인증처리를 하기전에 필터가 하는 역할.
3. 인증관리자(AuthenticationManager)는 내부적으로 AuthenticationProvider 에게 인증처리를 위임하게 된다. 해당 Provider가 인증처리를 담당하는 클래스로써 인증에 성공/실패를 반환하는데 실패할 경우 AuthenticationException 예외를 반환하여 UsernamePasswordAuthenticationFilter로 돌아가서 예외처리를 수행하고, 인증에 성공하게 되면, Authentication 객체를 생성하여 User객체와 Authorities객체를 담아서 AuthenticationManager에게 반환한다.
4. AuthenticationManager는 Provider로부터 반환받은 인증객체(인증결과 유저(User), 유저권한정보(Authorities))를 SecurityContext객체에 저장한다.
5. SecurityContext는 Session에도 저장되어 전역적으로 SecurityContext를 참조할 수 있다.
6. 인증 성공 이후에는 SuccessHandler에서 인증 성공이후의 로직을 수행하게 된다.
5) SecurityContextHolder
- SecurityContextHolder 에는 스프링 시큐리티로 인증을 한 사용자의 상세 정보를 저장한다.
- SecurityContext 란? SecurityContextHolder 로 접근할 수 있으며 Authentication 객체를 가지고 있다.
// 예시코드
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication = new UsernamePasswordAuthenticationToken(principal, credentials, authorities);
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
6) Authentication
- 현재 인증된 사용자를 나타내며 SecurityContext 에서 가져올 수 있다.
- principal : 사용자를 식별한다. Username/Password 방식으로 인증할 때 보통 UserDetails 인스턴스다.
- credentials : 주로 비밀번호, 대부분 사용자 인증에 사용하고 다음 비운다.
- authorities : 사용자에게 부여한 권한을 GrantedAuthority 로 추상화하여 사용한다.
<UserDetails>
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
UserRoleEnum role = user.getRole();
String authority = role.getAuthority();
System.out.println("authority = " + authority);
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(authority);
Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(simpleGrantedAuthority);
return authorities;
}
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
UsernamePasswordAuthenticationToken는 Authentication을 implements한 AbstractAuthenticationToken의 하위 클래스로, 인증객체를 만드는데 사용된다.
7) UserDetailsService
UserDetailsService는 username/password 인증방식을 사용할 때 사용자를 조회하고 검증한 후 UserDetails를 반환한다. Custom하여 Bean으로 등록 후 사용 가능하다.
8) UserDetails
검증된 UserDetails는 UsernamePasswordAuthenticationToken 타입의 Authentication를 만들 때 사용되며 해당 인증객체는 SecurityContextHolder에 세팅된다. Custom하여 사용가능하다.
9) 예외처리 및 요청 캐시 필터
a. AuthenticationException (인증 예외 처리)
1. AuthenticationEntryPoint 호출
→ 로그인 페이지 이동, 401(Unauthorized) 오류 코드 전달 등
2. 인증 예외가 발생하기 전의 요청정보를 저장
→ RequestCache - 사용자의 이전 요청 정보를 세션에 저장하고 이를 꺼내 오는 캐시 메커니즘
→ SavedRequest - 사용자가 요청했던 request 파라미터 값들, 그 당시의 헤더값들 등이 저장
b. AccessDeniedException (인가 예외 처리)
AccessDeniedHandler 에서 예외 처리하도록 제공
1. 익명 사용자가 /user에 접근을 시도한다고 가정한다.
2. FilterSecurityInterceptor 권한 필터가 해당 요청(/user)을 받았지만, 해당 유저는 인증을 받지 않은 상태.
3. 해당 필터는 인증 예외를 발생한다.
→ 정확히는 인가 예외를 던진다. 왜냐하면 해당 사용자는 익명(anonymouse)사용자이기에 인증을 받지 않은 상태라서 인가 예외(AccessDeniedException)로 빠진다.
4. 인가 예외(AccessDeniedException)는 익명 사용자이거나 RememberMe사용자일 경우 AccessDeniedHandler를 호출하지 않고 AuthenticationException 에서 처리하는 로직으로 보내게 된다.
5. 인증 예외 (AuthenticationException) 두 가지 일을 한다.
a. AuthenticationEntryPoint 구현체 안에서 login페이지로 리다이렉트 한다. (인증 실패 이후)
→ Security Context를 null로 초기화해주는 작업도 해준다.
b. 예외 발생 이전에 유저가 가고자 했던 요청정보를 DefaultSavedRequest객체에 저장하고 해당 객체는 Session에 저장되고 Session 에 저장하는 역할을 HttpSessionRequestCache에서 해준다.
1. 인증절차를 밟은 일반 유저가 /user자원에 접근을 시도하는데 해당 자원에 설정된 허가 권한이 ADMIN일 경우
2. 권한이 없기 때문에 인가 예외 발생
3. AccessDeniedException이 발생한다.
4. AccessDeniedHandler 호출해서 후속작업을 처리한다.
→ 보통은 denied 페이지로 이동한다.
10. Security에서 JWT를 사용한 인증/인가 흐름 짚고 넘어가기
1. 사용자는 회원가입을 진행한다.
a . 해당 URI 요청은 permitAll 처리하고 사용자의 입력값으로 service에서 회원가입을 진행한다.
2. 사용자의 정보를 저장할 때 비밀번호를 암호화하여 저장한다.
a. PasswordEncoder를 사용하여 비밀번호를 암호화 한 후 저장한다.
3. 사용자는 로그인을 진행한다.
a. 해당 URI 요청은 permitAll 처리하고 사용자의 입력값으로 service에서 회원 인증을 진행한다. (비밀번호 일치여부 등)
4. 사용자 인증을 성공하면 사용자의 정보를 사용하여 JWT 토큰을 생성하고 Header에 추가하여 반환한다.
Client 는 이를 쿠키저장소에 저장한다.
5. 사용자는 게시글 작성과 같은 요청을 진행할 때 발급받은 JWT 토큰을 같이 보낸다.
6. 서버는 JWT 토큰을 사용하여 인증/인가를 처리한다.
서버는 JWT 토큰을 검증하고 토큰의 정보를 사용하여 사용자의 인증을 진행해주는 Spring Security 에 등록한 Custom Security Filter 를 사용하여 인증/인가를 처리한다.
7. Custom Security Filter에서 SecurityContextHolder에 인증을 완료한 사용자의 상세 정보를 저장한다.
Custom Security Filter에서 SecurityContextHolder에 인증을 완료한 사용자의 상세 정보를 저장하는데 이를 통해 Spring Security 에 인증이 완료 되었다는 것을 알려준다.
springsecurity 프로젝트 - 회원가입 / 로그인 기능만 (JWT사용X)
프로젝트 구조
WebSecurityConfig
UserController
TestController
Dto - SignupRequestDto / LoginRequestDto / SecurityExceptionDto
Entity - User / UserRoleEnum
UserRepository
UserDetailsServiceImpl
UserDetailsImpl
CustomSecurityFilter
CustomAccessDeniedHandle
CustomAuthenticationEntryPoint
https://siyoon210.tistory.com/32
https://velog.io/@seongwon97/Spring-Security-Filter%EB%9E%80
http://www.stechstar.com/user/zbxe/?mid=AlgorithmJava&document_srl=79253
https://ojt90902.tistory.com/843
https://velog.io/@csh0034/Spring-Security-Config-Refactoring
https://devlog-wjdrbs96.tistory.com/434
https://catsbi.oopy.io/c0a4f395-24b2-44e5-8eeb-275d19e2a536
https://velog.io/@modsiw/Spring-Spring-Security%EC%99%80-JWT-%EA%B0%9C%EB%85%90
'회고록 > 항해99' 카테고리의 다른 글
[항해99][과제] Spring 심화주차 - Spring Security를 적용한 나만의 항해 블로그 백엔드 서버 만들기 (0) | 2022.12.19 |
---|---|
[항해99] Spring 숙련주차 강의 - 회원가입 / 로그인 정리(JWT사용) (0) | 2022.12.06 |
[항해99][강의정리] Spring 숙련주차(W4) - JPA 심화 정리 (0) | 2022.12.05 |
[항해99][과제] Spring 숙련주차 - 스프링 부트로 회원가입, 로그인, 댓글 작성/조회/수정/삭제 기능이 추가된 블로그 백엔드 서버 만들기(JWT사용) (0) | 2022.12.05 |
[항해99][과제] Spring 입문주차 - 스프링 부트로 로그인 기능이 없는 블로그 백엔드 서버 만들기 (0) | 2022.12.04 |