Spring Security란?
어플리케이션의 보안(인증 및 권한)을 담당하는 프레임워크
Spring Security를 사용하지 않으면 자체적으로 세션을 체크해야 함.
redirect를 일일이 설정해줘야 함
Spring Security의 특징
Spring Security의 용어
Spring Security Architecture
1. Http Request 수신
Spring Security는 필터로 동작을 함
요청이 들어오면, 인증과 권한을 위한 필터들을 통하게 됨
유저가 인증을 요청할 때 필터는 인증 메커니즘과 모델을 기반으로 한 필터들을 통과한다
예)
-- HTTP 기본 인증을 요청하면 BasicAuthenticationFilter를 통과한다.
--HTTP Digest 인증을 요청하면 DigestAuthenticationFilter를 통과한다.
-- 로그인 폼에 의해 요청된 인증은 UserPasswordAuthenticationFilter를 통과한다.
--x509 인증을 요청하면 X509AuthenticationFilter를 통과한다.
2. 유저 자격을 기반으로 인증 토큰(AuthenticationToken) 만들기
username과 password를 요청으로 추출하여 유저 자격을 기반으로 인증 객체를 생성함
-- 대부분의 인증 메커니즘은 username과 password를 기반으로 함
username과 password는 UsernamePasswordAuthenticationToken을 만드는데 사용됨
3. Filter를 통해 AuthenticationToken을 AuthenticationManager에 위임함
UsernamePasswordAuthenticationToken 오브젝트가 생성된 후, AuthenticationManager의 인증 메소드를 호출함
AuthenticationManager는 인터페이스로 정의되어있음
-- 실제 구현은 ProviderManager에서 함
//AuthenticationManager.java
public interface AuthenticationManager{
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
실제 프로그래밍 구현에서 AuthenticationManager를 사용하기 위해 설정에 AuthenticationManagerBuilder를 이용해 아래에 정의된 UserServiceDetails를 매핑시켜줌
여기서는 LoginService가 UserDetailsService를 구현하였으므로, LoginService가 UserDetailsService의 역할을 함
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{ auth.userDetailsService(loginService); }
ProviderManager는 AuthenticationProvider를 구성하는 목록을 갖는다
ProviderManager는 AuthenticationProvider에 각각의 목록들을 제공하고, password Authentication 객체를 기반으로 만들어진 인증을 시도함
-- password Authentication 객체 = UsernamePasswordAuthenticationToken
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean { ... }
4. AuthenticatinProvider의 목록으로 인증을 시도함
AuthenticationProvider의 위치 : Core에 들어있음
AuthenticationProvider 내용
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
boolean supports(Class<?> authentication);
}
5. UserDetailsSerivce의 요구
public interface UserDetailsService
{
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
6. UserDetails를 이용해서 User객체에 대한 정보를 검색
public class LoginService implements UserDetailsService{}
- UserDetailsService는 UserDetails를 username을 기반으로 검색을 실행한다.
- UserDetails는 인터페이스로, 우리가 데이터베이스 생성하기 위한 객체(ex. AdminUser 또는 User 등)에서 정보를 가져올 때 이용된다.
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
...
}
```
7. User객체의 정보들을 UserDetails가 UserDetailsService(LoginService)에 전달
데이터베이스에서 User객체에 매핑된 정보를 가져와 UserDetailsService에 전달함
전달된 User 객체의 정보와 사용자가 요청한 인증 정보(username, password)를 확인하는 로직을 LoginService에 구현함
@Transactional
public Optional login(String adminId, String password) throws UsernameNotFoundException { //인증 로직 구현(아이디, 비밀번호를 확인하는 로직을 거친다.)}
UserDetailsService에서 username을 기반으로 검색을 실행한다고 위에 정의했다.
loadUsername은 UserDetailsService가 구현을 필수로 하기를 원하는 메소드이다.
반환 값으로 User에 id와 password, 권한 정보를 넣어서 반환한다.
User는 org.springframework.security.core.userdetails에서 기본적으로 제공해주는 User 객체이다.
개발자는 User를 그대로 사용해도 되고, User를 상속받아 확장하여 사용해도 되고, 아래와 같이 권한을 넘겨줄때만 이용할 수도 있다.
또는 User대신 UserDetails(인터페이스)를 구현하여 사용할 수도 있다.
중요한것은, UserDetailsService가 권한 정보를 알 수 있도록 결론적으로 User라는 객체를 통해 정보를 받을 수 있어야 한다는 것이다.
@Override
public UserDetails loadUserByUsername(String adminName) throws UsernameNotFoundException {
AdminUser adminUser = adminUserRepository.findByAdminName(adminName);
AdminUser admin = adminUser.get();
List<GrantedAuthority> authorities = new ArrayList<>();
if("ADMIN".equals(adminName)){
authorities.add(new SimpleGrantedAuthority(Role.ADMIN.getValue()));
} else {
authorities.add(new SimpleGrantedAuthority(Role.MEMBER.getValue()));
}
//User 객체에 아이디, 비밀번호, 권한정보를 넘겨준다.
return new User(
admin.getAdminId()
, admin.getPassword()
, authorities
);
}
public class User implements UserDetails, CredentialsContainer {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private static final Log logger = LogFactory.getLog(User.class);
private String password;
private final String username;
private final Set<GrantedAuthority> authorities;
private final boolean accountNonExpired;
private final boolean accountNonLocked;
private final boolean credentialsNonExpired;
private final boolean enabled;
...
}
유저의 인증이 성공하면, 전체 인증정보를 리턴한다.
-- 인증에 실패하면 AuthenticationException을 던진다.
인증에 성공한 객체의 정보
-- authenticated - true
-- grant authorities list : 권한 정보
-- user credentials(username only) : username으로 인증된 사항
SecurityContextHolder.getContext().setAuthentication(authentication);