빙응의 공부 블로그

[Spring]스프링 시큐리티 - 구조 이해하기 본문

Spring/개인공부_이론

[Spring]스프링 시큐리티 - 구조 이해하기

빙응이 2024. 3. 22. 19:09

1편 안봤으면 보고오자, 간단한 개념을 설명하였다.

[Spring]스프링 시큐리티 - 개요 (tistory.com)

 

[Spring]스프링 시큐리티 - 개요

📝Spring Security Spring Security란 일반적인 공격에 대한 인증, 권한 부여 및 보호를 제공하는 프레임워크이다. 스프링 시큐리티의 기능은 크게 4가지로 나눈다. 인증(Authentication) 권한 부여(인가 : Auth

quddnd.tistory.com

 

📝Spring Security의 구조 

스프링 시큐리티의 구조(아키택처)에 대해 알아보자 

전에도 말했듯 스프링 시큐리티는 필터 기반의 기술이다. 

 

필터 흐름

HTTP 요청 - WAS - 필터 - 서블릿 - 컨트롤러 

 

 

🚩Spring Security 구조 

1. 유저 자격을 기반으로 인증토큰(AuthenticationToken)만들기 
  • username과 passwerd를 요청(request)으로 추출하여 유저 자격을 기반으로 인증 객체를 생성한다. 
  • 이것을 UsernamePasswordAuthenticationToken이라 한다.
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
    // 주로 사용자의 ID에 해당함
    private final Object principal;
    // 주로 사용자의 PW에 해당함
    private Object credentials;
    //-------------------------//
    }

 

2. Fillter를 통해 AuthenticationToken을 AuthenticationManger에 위임한다.
  • UsernamePasswordAuthenticationToken 오브젝트가 생성된 후, AuthenticationMangager의 인증 메소드를 호출한다.
  • AuthenticationManager는 인터페이스로 정의되어있다.
    • 실제 구현은 ProviderManager에서 한다. 
    • 너무 어렵게 생각하지 말자, 그냥 사용자를 검증하고, 인증을 성공하면 "Authentication" 객체를 반환한다.
//AuthenticationManager.java
public interface AuthenticationManager{
  Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
3. AuthenticationManager 가 인증을 수행한다. 
  • AuthenticationManager에 주어진 Authentication 객체를 기반으로 사용자를 검증하고 인증한다.
    • AuthenticationManager의 구현체인 ProviderManager는 여러개의 AuthenticationManager를 관리하고 각각의 AuthenticationProvider에게 인증을 위임한다.
    • 따라서 실제 인증과정은  AuthenticationProvider에서 수행하며, ProviderManager는 사실상 여러개의 인증을 관리하는 관리자 개념이다.
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
    public List<AuthenticationProvider> getProviders() {
		return providers;
	}
    public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
		Class<? extends Authentication> toTest = authentication.getClass();
		AuthenticationException lastException = null;
		Authentication result = null;
		boolean debug = logger.isDebugEnabled();
        //for문으로 모든 provider를 순회하여 처리하고 result가 나올 때까지 반복한다.
		for (AuthenticationProvider provider : getProviders()) {
            ....
			try {
				result = provider.authenticate(authentication);

				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException e) {
				prepareException(e, authentication);
				// SEC-546: Avoid polling additional providers if auth failure is due to
				// invalid account status
				throw e;
			}
            ....
		}
		throw lastException;
	}
}

 

4. UserDetailsService를 통한 UserDetails 객체 생성 
  • 인증에 성공하면 UserDetailsService는 UserDetails 객체를 생성한다. 
    •  이 객체는 Authentication 객체를 구현한 UsernamePasswordAuthenticationToken를 생성하기 위해 사용된다.
    • 해당 객체는 사용자의 ID, 비밀번호 및 권한 등의 정보가 포함된다. 
public interface UserDetails extends Serializable {

    Collection<? extends GrantedAuthority> getAuthorities();

    String getPassword();

    String getUsername();

    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();
    
}

 

5. 인증 성공 후 인증된 사용자에 대한 보안 처리 
  • 사용자의 권한을 확인하고 접근 권한을 부여하는 단계이며 권한에 따라 보호된 리소스에 대한 접근을 제어한다.
참고! UserDetailsService 사용해보기 
  • UserDetailsService는 현재 로그인 사용자 정보를 불러올 수 있다.
  • 밑에는 UserDetailsService의 원문이다. 
public interface UserDetailsService {

    UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;

}

 

이걸 이용해서 밑에 처럼 현재 로그인한 사용자의 정보를 쉽게 불러올 수 있다. 

@Component
public class MemberComponent implements UserDetailsService {
    private final MemberService memberService;

    public MemberComponent(MemberService memberService) {
        this.memberService = memberService;
    }

    @Override
    public UserDetails loadUserByUsername(String id) throws UsernameNotFoundException {
        Member member = memberService.findMemberById(id);

        if (member == null) {
            throw new UsernameNotFoundException("없는 회원입니다");
        }

        return User.builder()
                .username(member.getId())
                .password(member.getPw())
                .build();
    }
}

 

또한 컨트롤러에서는 이러한 방식으로 불러오면 된다.

        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        String memberId = authentication.getName();