💻 Backend/스프링

[Spring Security] 스프링 시큐리티 용어 및 구조

미미누 2022. 4. 11. 19:37

스프링 시큐리티

- 어플리케이션의 보안(인증 및 권한)을 담당하는 프레임워크

- 스프링 시큐리티를 적용하면 redirect를 자동으로 설정 가능(로그인 완료시 다음 화면으로 넘어가기)

 


특징

- 필터 기반으로 동작

- bean으로 설정 가능


용어 

1. Principal(인증 주체): 유저

2. Authenticate(인증): 현재 유저가 누구인지 확인(로그인)

3. Authorize(인가): 현재 유저의 권한 검사

4. 권한: 인증 주체가 어플리케이션의 동작을 수행할 수 있도록 허락 되었는지 확인

 - 권한 승인이 필요한 부분으로 접근하려면 인증(로그인)을 통해 증명되어야 함

 

 

인증(로그인)을 거치게 되면 인증 주체에 대한 정보는 Principal에 담긴다.

(Principal은 UserDetailsService에서 리턴한 UserDetails 정보)

 

Authentication

  - 인증 정보를 담고 있는 객체

Principal

  - 내가 누구인지에 대한 정보를 담는 객체

  - 로그인 ID, username에 해당하는 값을 담음

Credntials

  - 인증자격에 대한 정보를 담은 객체

  - 비밀번호와 같은 암호 값

Authorities

  - 현재 유저의 권한(ROLE_USER, ROLE_ADMIN, ROLE_GUEST) 정보를 담은 객체

SecurityContextHolder

- 시큐리티가 최종적으로 제공하는 객체

- 인증에 대한 정보는 Authentication 객체에 있고, 최종적으로 SecurityContextHolder에 저장됨


왜 SecurityContextHolder에 저장할까? 

SecurityContext 객체를 Thread-local로 제공하기 때문(같은 스레드에서는 언제든지 인증정보에 접근 가능하기 때문)

public class SecurityUtil {

    private SecurityUtil() { }

    public static String getCurrentMemberId() {
        final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        if (authentication == null || authentication.getName() == null) {
            throw new RuntimeException("Security Context 에 인증 정보가 없습니다.");
        }

        return authentication.getName();
    }

}

 

final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

현재 유저의 인증정보(authentication)를 SecurityContextHolder로 부터 가져오고,

authentication.getName()을 통해 인증 객체의 이름을 반환한다. 

 

@Component
@RequiredArgsConstructor
public class UserUtil {

    private final UserRepository userRepository;

    public User findCurrentUser() {
        User user = userRepository.findByUserId(SecurityUtil.getCurrentMemberId())
                .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_MEMBER));

        return user;
    }

}

UserUtil 클래스는 SecurityUtil의 getCurrentMemberId()의 메서드를 불러와서 찾은 유저를 반환한다.


스프링 시큐리티의 구조

1. Http Request 수신

 - 스프링 시큐리티는 필터로 동작한다.

 - 요청이 들어오면, 인증과 권한을 위한 필터(Authentication Filter)를 통하게 된다.

 

2. 유저 자격을 기반으로 인증 토큰(UsernamePasswordAuthenticationToken) 만들기

 - username과 password를 요청으로 유저 자격 기반으로 UsernamePasswordAuthenticationToken 생성한다.

 

3. Authentication Filter를 통해 인증 토큰을 AuthenticationManager에 위임

 - AutheticationManager는 인증을 처리하는 방법을 정의한 API이다.

 - 인증 토큰이 생성된 후, AuthenticationManager의 인증 메서드 호출

 - AuthenticationManager는 인터페이스로 정의 (실제 구현: ProviderManager에서 시행)

 

public interface AuthenticationManager{
  Authentication authenticate(Authentication authentication) throws AuthenticationException;
}

AuthenticationManager를 사용하기 위해 Builder를 이용해 UserDetailsService를 매핑시켜준다.

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{   auth.userDetailsService(customUserDetailsService); }

ProviderManager는 AuthenticationProvider의 구성 목록을 가진다.

ProviderManager는 AuthenticationProvider에 각각의 목록을 제공하고, 인증 토큰을 기반으로 만들어진 인증을 시도한다.

 

각 AuthenticationProvider는 인증 성공, 실패, 결정할 수 없음을 나타낼 수 있음

public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean { ... }

 

 

4. AuthenticationProvider의 목록으로 인증 시도 

public interface AuthenticationProvider {
    Authentication authenticate(Authentication authentication) throws AuthenticationException;
    boolean supports(Class<?> authentication);
}

각각의 AuthenticationProvider는 특정 유형의 인증을 수행한다.

  • CasAuthenticationProvider
  • JaasAuthenticationProvider
  • DaoAuthenticationProvider (DB 기반 id/password 인증)
  • OpenIDAuthenticationProvider
  • RememberMeAuthenticationProvider
  • LdapAuthenticationProvider
  • id, password 기반 인증 경우 : username / password 가 유효한지 검사
  • id, password 외 인증 경우(saml 인증) 등..

 

5. UserDetailsService의 요구

 - UserDetailsService는 username 기반의 userDetails 검색

 - AuthenticationProvider의 DaoAuthenticationProvider를 사용

 - UserDetailsService는 인터페이스이기 때문에 UserDetails를 가져오는 CustomUserDetailService 생성

public interface UserDetailsService
{
  UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUserId(username)
                .orElseThrow(() -> new UsernameNotFoundException("Can not find username."));

        return CustomUserDetails.create(user);
    }

}

6. UserDetail를 이용해서 User 객체에 대한 정보 검색

- UserDetails는 인터페이스로, 우리가 직접 생성한 User 객체에서 정보를 가져올때 사용

 public interface UserDetails extends Serializable {
      Collection<? extends GrantedAuthority> getAuthorities();
      String getPassword();
        String getUsername();
      ...
    }
    ```

 

@Getter
@Setter
@AllArgsConstructor
@RequiredArgsConstructor
public class CustomUserDetails implements OAuth2User, UserDetails, OidcUser {

    private final String userId;
    private final ProviderType providerType;
    private final RoleType roleType;
    private final Collection<GrantedAuthority> authorities;
    private Map<String, Object> attributes;

    @Override
    public Map<String, Object> getAttributes() {
        return attributes;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return null;
    }

    @Override
    public String getUsername() {
        return userId;
    }

    @Override
    public String getName() {
        return userId;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    @Override
    public Map<String, Object> getClaims() {
        return null;
    }

    @Override
    public OidcUserInfo getUserInfo() {
        return null;
    }

    @Override
    public OidcIdToken getIdToken() {
        return null;
    }

    public static CustomUserDetails create(User user) {
        return new CustomUserDetails(
                user.getUserId(),
                user.getProviderType(),
                RoleType.USER,
                Collections.singletonList(new SimpleGrantedAuthority(RoleType.USER.getCode()))
        );
    }

    public static CustomUserDetails create(User user, Map<String, Object> attributes) {
        CustomUserDetails userPrincipal = create(user);
        userPrincipal.setAttributes(attributes);

        return userPrincipal;
    }

}

7. User객체의 정보를 UserDetails가 UserDetailsService에 전달

 - 데이터베이스에서 User 객체 정보를 가져와 UserDetailsService에 전달

 - UserDetailsService에서 username을 기반으로 검색 시행

 - loadUsername은 UserDetailsService가 구현을 필수로 하는 메서드

 - 반환 값으로 User(org.springframework.security.core.userdetails에서 기본적으로 제공해주는 User 객체)에 id, password, 권한 정보를 넣어서 반환

 - 혹은 UserDetails 인터페이스를 구현하여 반환

 

8. 인증 객체 또는 AuthenticationException

 1. 유저의 인증이 성공하면 인증 객체 반환

 - 인증 실패시 AuthenticationException 처리

 

2. 인증 객체(Authentication)의 정보

 - authenticated - true

 - grant authorities list : 권한 정보(ROLE_USER, ROLE_ADMIN 등)

 - user credentials : 인증자격에 대한 정보를 담은 객체 (비밀번호)

 

9. 인증이 끝나면 -> AuthenticationManager는 완전한 인증 객체를 Authentication Filter에 반환한다.

 

10. SecurityContext에 인증 객체를 설정한다.

  • AuthenticationFilter는 인증 객체를 SecurityContext에 저장한다.
SecurityContextHolder.getContext().setAuthentication(authentication);

 

[참조 목록]

https://twer.tistory.com/entry/Security-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0%EC%9D%98-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EA%B5%AC%EC%A1%B0-%EB%B0%8F-%ED%9D%90%EB%A6%84

 

[Security] 스프링 시큐리티의 아키텍처(구조) 및 흐름

Spring Security 스프링 시큐리티리란? 어플리케이션의 보안(인증 및 권한)을 담당하는 프레임워크 Spring Security를 사용하지 않으면 자체적으로 세션을 체크해야 한다. redirect를 일일이 설정해주어야

twer.tistory.com

https://gregor77.github.io/2021/05/18/spring-security-03/

 

Spring Security - 3. 인증 절차를 정의하는 AuthenticationProvider

Spring Security에서 어떻게 인증이 시작될까?Spring security는 내부에 인증 절차가 이미 구현되어 있다. spring security의 인증 절차를 이해하고 난다면, 구현체와 설정을 통해서 새로운 인증 절차를 추가

gregor77.github.io

https://to-dy.tistory.com/86

 

Spring Security - 인증 절차 인터페이스 구현 (1) UserDetailsService, UserDetails

UserDetailsService 인터페이스는 DB에서 유저 정보를 가져오는 역할을 한다. 해당 인터페이스의 메소드에서 DB의 유저 정보를 가져와서 AuthenticationProvider 인터페이스로 유저 정보를 리턴하면, 그 곳

to-dy.tistory.com