티스토리 뷰
이해 목적
실무에서 사용하다가 요구사항으로 인한 변경, 갑자기 터지는 이슈를 내부 구조를 알아야 대응하기 쉬워집니다.
그래서 옵셔널 한 이 시큐리티를 깊게를 할 필요는 없지만 내부 구조 정도는 알아야 한다고 생각합니다.
이 글을 외우지 말고 간단한 이해만 시켜보도록 하겠습니다.
아키텍처 크게 본 구조
공식문서에 있는 그대로 설명할 것입니다.
1. 서버에 오기전에 필터들이 동작된다.
클라이언트(사용자)가 요청을 하면 서버에 그 해당 요청이 들어오기 전에 필터가 동작합니다.
필터들이 여러 개로 묶여 동작한다면, 그것이 필터 체인이라고 부릅니다.
2. 그 많은 필터 중에 FilterChainProxy가 등록된다.
간단히 말하여 스프링 시큐리티는 FilterChainProxy를 지원하여, 한 Filter 영역을 잡게 됩니다.
3. 시큐리티 관련 SecurityFilterChain에는 많은 필터를 가지고 있다.
그냥 어떠한 필터가 있는지 print 해봤을 때는 아래와 같이 많이 나오지만 다 외울필요는 없고 아 이 정도 있구나라고 생각하시면 됩니다.
2023-06-14T08:55:22.321-03:00 INFO 76975 --- [main] o.s.s.web.DefaultSecurityFilterChain :
Will secure any request with [
org.springframework.security.web.session.DisableEncodeUrlFilter@404db674,
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@50f097b5,
org.springframework.security.web.context.SecurityContextHolderFilter@6fc6deb7,
org.springframework.security.web.header.HeaderWriterFilter@6f76c2cc,
org.springframework.security.web.csrf.CsrfFilter@c29fe36,
org.springframework.security.web.authentication.logout.LogoutFilter@ef60710,
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@7c2dfa2,
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@4397a639,
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@7add838c,
org.springframework.security.web.authentication.www.BasicAuthenticationFilter@5cc9d3d0,
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@7da39774,
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@32b0876c,
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@3662bdff,
org.springframework.security.web.access.ExceptionTranslationFilter@77681ce4,
org.springframework.security.web.access.intercept.AuthorizationFilter@169268a7]
4. 각 SecurityFilterChain는 각각 로직을 가지고 있다.
많은 필터를 가지고 있는 것을 위에서 확인하였고, 그 필터마다 로직을 가지고 있습니다.
많은 것을 확인 했으니 전체적 그림을 보고 제일 중요한 인증 필터를 구현한 아키텍처 흐름도를 보겠습니다.
크게 보자면 이런 형태가 되겠군요
중요한 로그인 또는 JWT 인증 아키텍처
다들 아래와 같은 사진을 가져오길래 나도 가져왔다...
그림에 나와 있는 숫자 순서대로 설명하도록 하겠습니다.
https://chathurangat.wordpress.com/2017/08/23/spring-security-authentication-architecture/#more-54
1. 사용자가 Http 요청
- 사용자가 로그인 요청을 넣은 것입니다.
2. 유저 자격을 기반으로 인증토큰 생성
- AuthenticationFilter(인증필터)가 요청을 가로채고 그 요청에서 정보( username, password )를 추출합니다.
- 이 추출된 정보로 UsernamePasswodAuthenticationToken의 인증토큰을 생성합니다.
3. Filter를 통해 인증토큰을 인증관리자(AuthenticationManager)로 위임
- AuthenticationFilter(인증필터)는 UsernamePasswordAuthenticationToken(인증토큰)을 인증을 관리하는 인터페이스인 AuthenticationManager로 전달합니다.
4. 인증공급자(AuthenticationProvider)의 목록으로 인증을 시도
- AuthenticationManager(인증 관리자)는 하나 이상의 AuthenticationProvider(인증 공급자)에게 인증 요청을 위임합니다.
- 실제 인증 논리, 로직을 구현하여 인증을 시도합니다.
5. UserDetailsService의 요구
- AuthenticationProvider(인증 공급자)는 UserDetailsService로 사용자를 찾고 암호 인코더로 암호를 검증합니다.
- 실제 DB에서 사용자 인증정보를 가져오는 로직이 UserDetailsService에 구현합니다.
6. UserDetails를 이용해 User객체 만들기
- DB에서 찾은 사용자의 권한 및 기타 인증 정보를 포함한 정보들로 UserDetails 객체를 만듭니다.
7. 인증공급자(AuthenticationProvider)가 검증
- AuthenticationProvider(인증 공급자)는 UserDetails 객체를 사용하여 사용자가 제공한 인증 정보를 검증합니다.
8, 9. 인증 객체 또는 인증 에러
- 인증이 검증되어 완료가 되면 사용자 정보가 담긴 Authentication(인증) 객체를 반환합니다.
10. SecurityContext에 인증 객체를 설정
- AuthenticationFilter(인증필터)는 SecurityContextHolder(보안 컨텍스트)에 인증 정보를 저장합니다.
- SecurityContextHolder(보안 컨텍스트)는 현재 활성화된 보완 컨텍스트를 저장하고 있는 싱글톤 클래스입니다.
Spring Security 주요 역할 객체들
1. SecurityContextHolder, SecurityContext, Authentication
SecurityContextHolder 안에!! SecurityContext가 있고!! 그안에 인증객체가 있다!!
Authentication 객체에는
- principal : 아이디 (username)
- credential : 비밀번호 (password)
정보를 담고 있습니다.
그래서 Security에 로그인한 사용자의 정보를 얻으려면
SecurityContextHolder.getContext().getAuthentication().getPrincipal();
이렇게 접근하시면 됩니다.
2. AbstractAuthenticationProcessingFilter.java
추상클래스인 인증필터 입니다.
평균적으로 더보기에 있는 3가지 메서드만 구현하시면 됩니다.
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware {
// ~~ 필드 생략~
public abstract Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException;
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
// 내용 생략
}
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
// 내용 생략
}
이 클래스를 구현하여 자주 사용하고 제공되는 필터로는 UsernamePasswordAuthenticationFilter가 있습니다.
그래서 실무에서 사용할 때는 이 UsernamePasswordAuthenticationFilter를 구현(오버라이딩)해서 사용하시면 됩니다.
기본적으로 제공되는 필터는 아니지만 JWT를 많이 사용하므로 JwtAuthenticationFilter로 새롭게 만들어서 정의(오버라이딩)하여 사용하시면 됩니다!!
3. AbstractAuthenticationToken
이 인증토큰은 Authentication(인증)을 구현한 클래스이고 Authentication 객체에는
- principal : 아이디 (username)
- credential : 비밀번호 (password)
가 존재 합니다.
Authentication(인증 객체)을 구현한 AbstractAuthenticationToken 추상클래스가 있고
AbstractAuthenticationToken을 상속한 UsernamePasswordAuthenticationToken 가 존재합니다.
JwtAuthenticationToken 는 제가 Spring Security + JWT를 구현하기 위해 존재하지 않는 것을 상속받아 구현한 것입니다.
이 AuthenticationManager(인증 관리자), AuthenticationProvider(인증 공급자)에게 실제 인증을 하기 위해서 인증객체(인증토큰)로전달하거나 인증을 끝내고 인증객체(인증토큰)로 반환할 때 사용합니다.
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 610L;
private final Object principal;
private Object credentials;
// 인증 완료 전에 객체 생성
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super((Collection)null);
this.principal = principal;
this.credentials = credentials;
this.setAuthenticated(false);
}
// 인증 완료 후의 객체 생성
public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);
}
// 중간 생략
}
4. AuthenticationManager
이 클래스는 여러 AuthenticationProvider(인증 공급자)를 관리하고, 인증 요청을 처리하는 인터페이스입니다.
인증객체토큰을 Provider에게 받아서 AuthenticationFilter(인증필터)에게 return 합니다.
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
// 예시로 구현했을 때
@RequiredArgsConstructor
public class CustomAuthenticationManager implements AuthenticationManager {
private final List<AuthenticationProvider> providers;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
for (AuthenticationProvider provider : providers) {
if (provider.supports(authentication.getClass())) {
return provider.authenticate(authentication);
}
}
throw new AuthenticationException("No provider found for " + authentication.getClass().getName()) {};
}
}
5. AuthenticationProvider
스프링 시큐리티의 실제 인증 프로세스를 담당하는 중요한 필수 인터페이스 입니다.
이 로직을 실행하고 인증객체토큰을 AuthenticationManager(인증 관리자)에게 return 합니다.
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
boolean supports(Class<?> authentication);
}
// 예시로 구현했을 때
@RequiredArgsConstructor
public class CustomAuthenticationProvider implements AuthenticationProvider {
private final UserDetailsService userDetailsService;
private final PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = (String) authentication.getCredentials();
// 사용자 이름과 비밀번호 검증 로직
UserDetails user = userDetailsService.loadUserByUsername(username);
if(passwordEncoder.matches(password, user.getPassword())){
return UsernamePasswordAuthenticationToken(username, password, user.getAuthorities());
} else {
throw new AuthenticationException("Invalid password");
}
}
@Override
public boolean supports(Class<?> authentication) {
// isAssignableFrom : 특정 class, Interface를 상속하거나 구현했는지 체크 return Boolean;
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
6. UserDetailsService
사용자 이름으로 사용자 세부 정보(UserDetails)를 검색하는 객체입니다.
이 메서드를 구현하여 나의 DB에 있는 User와 연결하시면 됩니다.
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
// 구현했을 때
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User findUser = userRepository.findByEmail(username);
if(findUser == null){
throw new UsernameNotFoundException("User not found");
}
return new CustomUserDetails(findUser);
}
7. UserDetails
스프링 시큐리티가 관리하는 사용자 객체입니다.
public interface UserDetails extends Serializable {
String getPassword();
String getUsername();
Collection<? extends GrantedAuthority> getAuthorities();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
// 구현했을 때
@RequiredArgsConstructor
public class CustomUserDetails implements UserDetails {
private final User user;
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getEmail();
}
// 이하 생략
}
8. GrantedAuthority
애플리케이션의 목적 내에서 사용자가 가지고 있는 권한을 의미하며 사용자는 여러 권한을 가지게 할 수 있습니다.
이것으로 인해 접근 허용여부를 결정합니다.
public interface GrantedAuthority extends Serializable {
String getAuthority();
}
감사합니다.
'백엔드 > 🛡️Spring Security' 카테고리의 다른 글
[Spring Security] 스프링 시큐리티 JWT (3/3) : JWT 검증 및 Redis를 활용한 Refresh Token 재발급 (1) | 2024.09.04 |
---|---|
[Spring Security] 스프링 시큐리티 JWT (2/3) : 로그인 및 JWT 쿠키로 발급 (0) | 2024.08.16 |
[Spring Security] 스프링 시큐리티 JWT (1/3) : 기본 설정 및 회원 가입 (0) | 2024.08.07 |
- Total
- Today
- Yesterday
- Fetch
- 템플릿
- 디자인패턴
- Spring Security
- aws
- AJAX
- 코딩테스트
- 데이터 베이스
- 프로세스
- DBeaver
- 개발
- 비동기
- Front
- 네트워크
- 프론트
- Cors
- Mac
- 개발환경
- spring
- 자바스크립트
- 깃허브 액션
- JavaScript
- 개발자
- java
- git
- 개발블로그
- 오라클
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |