Authentication
스마일게이트 윈터 데브 캠프 개인 프로젝트가 시작되었다. 인증 시스템을 구축하는 게 과제이다.
정말 다 뜯어볼 생각이다. 그래서 Spring Docs와 여러 블로그를 보며 내가 이해한 토대로 작성해보겠다.
구조
1. Authentication Filter
AuthenticationFilter(사용할 구현체 UsernamePasswordAuthenticationFilter)가 HttpServletRequest에서 사용자가 보낸 아이디와 패스워드를 인터셉트한다.
Request안에서 username 과 password를 기반으로 Token을 생성해준다. 실제 코드를 보면 다음과 같다. Token이 과연 무엇인가?
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
//POST요청인지?
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
username = (username != null) ? username.trim() : "";
String password = obtainPassword(request);
password = (password != null) ? password : "";
//처음 요청일 때는 authenticated되지 않은 Token을 만드는 것
//생성자에 authenticate 는 false로 설정되어있다.
UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
2. Authentication
Spring Security는 Authenticated된 사용자를 Authentication라는 인터페이스를 주어 관리를 한다.
총 두가지 목적을 가지고 있는데
1. AuthenticationManager의 input으로서 역할하는 Authentication은 user가 인증되어야 한다는 것을 증명해준다.
2. SecurityContext에서 얻을 수 있으며, 현재 인증된 사용자를 대표할 수 있다.
Authentication안에는
Principal : UserDetails의 객체
Credentials : 보통 패스워드를 뜻하지만 인증된 후 보통 초기화된다.
Authority : 권한 , user가 갖고 있는 권한을 말한다 보통 ROLE의 형태로 이루어져 있다.
대표적인 구현체를 살펴보니 2.UserNamePasswordAuthenticationToken이 있다.
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
}
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
UserNamePasswordAuthenticationToken은 AbstractAuthenticationToken을 상속받고 이는 Authentication을 상속받고 있다.
public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer {
3. Authentication Manager
public interface AuthenticationManager {
/**
* Attempts to authenticate the passed {@link Authentication} object, returning a
* fully populated <code>Authentication</code> object (including granted authorities)
* if successful.
* <p>
* An <code>AuthenticationManager</code> must honour the following contract concerning
* exceptions:
* <ul>
* <li>A {@link DisabledException} must be thrown if an account is disabled and the
* <code>AuthenticationManager</code> can test for this state.</li>
* <li>A {@link LockedException} must be thrown if an account is locked and the
* <code>AuthenticationManager</code> can test for account locking.</li>
* <li>A {@link BadCredentialsException} must be thrown if incorrect credentials are
* presented. Whilst the above exceptions are optional, an
* <code>AuthenticationManager</code> must <B>always</B> test credentials.</li>
* </ul>
* Exceptions should be tested for and if applicable thrown in the order expressed
* above (i.e. if an account is disabled or locked, the authentication request is
* immediately rejected and the credentials testing process is not performed). This
* prevents credentials being tested against disabled or locked accounts.
* @param authentication the authentication request object
* @return a fully authenticated object including credentials
* @throws AuthenticationException if authentication fails
*/
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
1. 유효한 사용자라면 credentials를 포함한 Authentication 객체를 반환
2. 유효하지 않다면 Authentication Exception을 던진다.
+ Authentication Exception은 RuntimeException이라 굳이 예외처리를 안 해도 된다.
3. Authentication Manager
Authentication Manager의 대표적인 구현체는 Provider Manager이다.
Provider Manager는 AuthenticationProvider의 객체 리스트를 위임한다.
4. Authentication Provider
가장 핵심 로직일 수 있다. authenticate를 판별하여 Authentication객체를 반환해준다.
각각의 Authentication Provider는 특정 authentication에 대하여 어떤 행동을 취할지 알려져 있다.
어떤 AuthenticationProvider는 사용자 이름/비밀번호를 , 어떤 Provider는 SAML 이 맞는지 확인할 것이다.
이러한 각각 다른 Provider를 하나의 대표적인 provider manager만 노출하게끔 할 수 있다.
Authentication Provider 구현체 예시(추상화 클래스) 중 대표적인 Provider이다. UserDetails를 통해 authenticate를 판별한다.
public abstract class AbstractUserDetailsAuthenticationProvider
implements AuthenticationProvider, InitializingBean, MessageSourceAware {
authenticate() 안에 있는 함수를 보면 UserNamePasswordAuthenticationToken(Authentication 구현체) 객체 안에서 이름을 꺼내서 이름을 가지고 캐싱을 하고 찾는 걸 볼 수 있다. 가장 중요한 로직일 것이다.
DB에서 꺼내서 , 인증을 한 Token을 반환하는 로직이 들어있다. 글로 정리하기에는 헷갈려서 주석으로 달아보았다.
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//Authentication 타입 검사 (다양한 구현체 중 특정 UserNamePasswordAuthenticationToken만 가능)
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
() -> this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
"Only UsernamePasswordAuthenticationToken is supported"));
//Authenticaiton 객체 안에서 이름을 꺼낸다.
String username = determineUsername(authentication);
boolean cacheWasUsed = true;
//위의 userCache변수에 userName으로 UserDetails가 있는지 검증한다
UserDetails user = this.userCache.getUserFromCache(username);
//없으면
if (user == null) {
//사용하지 않음으로 변경
cacheWasUsed = false;
//rerieveUSer는 DaoAuthentication Provider에 구현되어있다.
//--> 간단히 말하면 사용자의 이름과 Authentication객체를 받아서
//DB에서 검색하여 UserDetails를 반환 한다.
try {
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
}
catch (UsernameNotFoundException ex) {
this.logger.debug("Failed to find user '" + username + "'");
if (!this.hideUserNotFoundExceptions) {
throw ex;
}
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
}
//DB에 존재했다면? 기존 사용했는지 검사를 하고 최종적으로
//Authentication검사를 성공하여 Authentication에 요청한 authority를 넣어서 반환해준다.
try {
this.preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
}
catch (AuthenticationException ex) {
if (!cacheWasUsed) {
throw ex;
}
// There was a problem, so try again after checking
// we're using latest data (i.e. not from the cache)
cacheWasUsed = false;
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
this.preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
}
this.postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (this.forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
return createSuccessAuthentication(principalToReturn, authentication, user);
}
마지막으로 이렇게 생성된 Authentication들을 SecurityContext에서 관리해준다.
5. Security Context Holder
스프링 시큐리티의 심장이라고 한다(실제 문서에 이렇게 적혀있음.. 귀엽다)
간단히 말해 , Authentication을 모아둔 저장소라고 생각하면 좋을 것 같다.
Thread Local 환경을 제공한다.. FilterChainProxy덕분이라고 한다.
6. 인증 성공? 실패?
Authentication 실패하면?
1. Secutiy Context Holder는 초기화된다.
2.RememberMeService.loginFail()가 가동된다. 설정이 없으면 가동 X
3. AuhtneticationFailureHandler가 가동된다
Authentication이 성공적이라면?
1.SessionAuthenticationStrategy가 새로운 로그인을 인식한다.
2. SecurityContextHolder에 Authentication을 저장한다.
3.2.RememberMeService.loginSuccess()가 가동된다. 설정이 없으면 가동 X
4.InteractiveAuthenticationSuccessEvent가 가동된다.
5. AuthenticationSuccessHandler가 가동된다. -> 단순한 UrlAuthenticationJHandler이고 login page로 이동시켜준다
정리를 해보자
1. 유저의 요청을 AuthenticationFilter에서 Authentication 객체로 변환(보통 UserPasswordToken)
2. AuthenticationManager(ProviderManager)에게 넘겨주고
3. AuthenticationProvider(DaoAuthenticationProvider)가 실제 인증을 한 이후에 ( DaoAuthenticationProvider안에 loadUser() 함수에서 DB 접근)
4. 인증이 완료되면 Authentication객체를 반환해준다.
- AbstractAuthenticationProcessingFilter : 웹 기반 인증 요청에서 사용되는 컴포넌트로 POST 폼 데이터를 포함하는 요청을 처리한다. 사용자 비밀번호를 다른 필터로 전달하기 위해서 Authentication 객체를 생성하고 일부 프로퍼티를 설정한다.
- AuthenticationManager : 인증 요청을 받고 Authentication을 채워준다.
- AuthenticationProvider : 실제 인증이 일어나고 만약 인증 성공 시 Authentication 객체의 authenticated = true로 설정해준다.
Spirng Security + Jwt 로그인 적용하기 (velog.io)
Spirng Security + Jwt 로그인 적용하기
프로젝트를 진행하면서 Spring Security + Jwt를 이용한 로그인을 구현하게 되었다. 목차 Spring Security JWT Spring SEcurity + JWT Spring Security > 가장먼저 스프링 시큐리티에 대해서 알아보자. Sprin
velog.io
Servlet Authentication Architecture :: Spring Security
Servlet Authentication Architecture :: Spring Security
ProviderManager is the most commonly used implementation of AuthenticationManager. ProviderManager delegates to a List of AuthenticationProvider instances. Each AuthenticationProvider has an opportunity to indicate that authentication should be successful,
docs.spring.io
Spring Security - JWT (tistory.com)
Spring Security - JWT
이번에는 지난번 세션 인증을 적용한 포스트에 이어서 JWT를 이용한 로그인을 구현해보도록 하겠다. 지난 포스트 https://hou27.tistory.com/entry/Spring-Security-%EC%84%B8%EC%85%98-%EC%9D%B8%EC%A6%9D Spring Security -
hou27.tistory.com
스프링 부트 Spring Security를 활용한 인증 및 권한 부여 | Bottlehs Tech Blog
스프링 부트 Spring Security를 활용한 인증 및 권한부여
스프링 시큐리티는 스프링 기반의 애플리케이션의 보안(인증과 권한,인가 등)을 담당하는 스프링 하위 프레임워크이다. 주로 서블릿 필터와 이들로 구성된 필터체인으로의 위임모델을 사용한
www.bottlehs.com
'2023스마일게이트윈터데브캠프' 카테고리의 다른 글
2023 스마일게이트 윈터 데브 캠프 - 개인프로젝트(합격부터) (3) | 2023.03.31 |
---|---|
[스마일게이트 캠프] 실시간 채팅 구현하기 - Web Socket (2) | 2023.02.17 |
어떤 비밀번호 알고리즘을 써야 할까? (2) | 2022.12.11 |