웹 개발에서 백엔드 업무를 하시거나, 공부 해보신 분이라면 유저 인증, 유저 인가에 대해서 들어보신 적이 있으실 겁니다. 도대체 그게 뭔지, 그리고 Security에서 어떻게 지원해주는지 기본적인 부분들을 살펴보겠습니다.
Spring Security의 인증, 인가 지원하는 방식에 대해 본격적으로 배우는 단계는 아니며, 정말 "간단히 이런 방식이구나" 만 파악해보면 됩니다. 내부적으로는 앞으로 천천히 파헤쳐보도록 하겠습니다.
인증과 인가
인증이란 사용자의 신원을 검증하는 행위이고, 인가란 그 사용자가 특정 리소스나 기능에 엑세스 할 수 있도록 권한을 부여하는 행위를 말합니다. 쉽게 말해서 인증이란 로그인하는거고, 인가란 관리자, 일반유저 등의 접근 가능 범위에 따른 권한이 나뉘는 것입니다.
Spring Security 의 인증
인증 방식에는 대표적으로 Form Login 방식이 있고, Security 역시 이를 지원합니다. ID와 PW를 form 태그로 서버에 전달하면, 유저는 그 정보들을 토대로 인증을 하고 인증 객체를 보관합니다. Spring Security는 또한 세션 방식을 지원하기 때문에, 한번 인증한 객체를 유효할 때까지 인증을 보관합니다. 세션과 세션 유지 전략에 대해서는 나~중에 나옵니다.
지난번에 짧게 등장했던 MySecurityConfig.class 에서 HttpSecurity 객체에 설정을 이어나가 보겠습니다. 해당 설정을 통해 WebSecurityConfigurerAdapter에서 보안 초기화를 진행해준다고 했습니다.
1. loginPage("/login.html") : 미인증시 로그인 페이지 지정
2. defaultSuccessUrl("/home") : 로그인 성공시 이동 페이지
3. failureUrl("/login.html?error=true") : 실패시 이동 페이지
4. usernameParameter("username") : Form 방식에서 ID 받을 파라미터 명
5. passwordParameter("password") : PW 받을 파라미터 명
6. loginProcessingUrl("/login") : 로그인 Form Action Url
7. successHandler(loginSuccessHandler()) : 로그인 성공 후 핸들러
8. failureHandler(loginFailureHandler()) : 로그인 실패 후 핸들러
9. permitAll() : authorization 설정시, login 시도 url 도 경로에 포함되므로, 로그인 경로는 인가 로직에서 예외처리를 함
이에 따라 위의 예제 인증 부분을 다음과 같이 바꿔볼 수 있습니다.
{ ...
// 인증
http
.formLogin() // 로그인 방식 채택 후 설정들을 build 해나간다
.loginPage("/loginPage")
.defaultSuccessUrl("/")
.failureUrl("/login")
.usernameParameter("useruser")
.passwordParameter("pwpw")
.loginProcessingUrl("/login_proc")
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
System.out.println("Authentication:: " + authentication.getName()); // 인증한 객체 정보가 Authentication에 저장됨
}
})
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
System.out.println("Exception:: " + exception.getMessage());
response.sendRedirect("/login");
}
})
.permitAll(); // 로그인에 대한 인가 예외 처리
}
위와 같이 설정하고 test를 진행해보면, 인증되지 않은 상태로 특정 페이지로 접근을 시도하면 바로 로그인 페이지로 가게되며, 인증된 후에 그 페이지로 이동할 수 있는 것을 확인할 수 있습니다.
요청이 이루어질 때
=====
처리 그림
=====
위와 같은 순서를 토대로 인증이 진행되며, 최종적으로 SecurityContext에 인증 정보를 저장하게 되는데, 이는 Session 방식으로 앞으로 통신에서 해당 유저를 기억하게 된다. 이 SecurityContext의 역할과 구현에 대해서는 나중에 알아보겠다. 그리고 SuccessHandler를 통해 다음으로 넘어가게 된다.
참고로, Authentication, AuthenticationManager, AuthenticationProvider 등 모두 역할에 대한 객체이며, 사용하는 인증 방식, API 버전에 따라 사용하는 구현 객체가 다릅니다. 역할과 구현의 분리가 잘 이루어지고 있는 모습도 확인할 수 있습니다.
=====
// 1-5 강
클라이언트가 로그아웃을 요청할 시 위에서 한 행위를 다 삭제해주는 것으로, 일어나는 일들은 다음과 같다.
1. 세션 무효화
2. SecurityContext 객체에 저장되어 있는인증토큰 삭제
3. 쿠키정보 삭제
4. 지정된 login page 로 리다이렉팅
로그아웃도 formLogin 처럼 어떤 처리를 해줄지 설정들이 필요하므로, 다음과 같은 항목이 있다.
1. logoutUrl(): 로그아웃을 처리하는 URL 지정
2. logoutSuccessUrl(): 로그아웃 성공 후 이동 URL 지정
3. deleteCookies("JSESSIONID", "remember-me"): 삭제할 쿠키 지정
4. addLogoutHandler(logoutHandler()) : 로그아웃 후 핸들링 (response.sendRedirect 와 2번의 차이?) > 핸들러 자체는 어쨌든 custom 화 하기 위함. 내가 원하는 특정 행동들을 등록할 수 있음.
5. logoutSuccessHandler(logoutSuccessHandler()) : 로그아웃 후 성공 핸들러 (4번과 먼차이?) > 로그아웃이 '완료된 후' postLogout 같은 느낌이라고 보면 됨.
이에 따라 위에서 설정하던 코드에 이어서 logout 설정을 붙일 수 있따.
{
....
http
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.logoutSuccessUrl("/login_page")
.addLogoutHandler(new LogoutHandler() {
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
// 세션을 무효화 시키는 작업을 보통 함. (자신의 인증 토큰 등)
// 이 세션으로 오는 친구는 더 이상 인증된 상태가 아님. 재인증 필요.
// 굳이 없어도 SecurityContextLogoutHandler가 수행함
HttpSession session = request.getSession();
session.invalidate();
}
})
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.sendRedirect("/login_page");
}
})
.deleteCookies("remember-me")
}
// 로그아웃 에서 일어나는 일
== 로그아웃 그림
/// 자동 로그인, Remember Me 인증
Remember Me 코딩
앞서 자동 로그인의 일반적인 방식에 대한 이해가 필요.
>(쿠키와 세션에 대한 설명 포스트 ㄱㄱ)
지금까지 살펴봤을 때 인증에 대해서 대충 정리를 해보자면 이렇다.
- Login 진행시 AuthenticationManager에서 AuthenticationProvider를 통해서 인증을 진행함. 그 결과로 Authentication 객체는 User 정보와, 권한 정보를 함유한채 생성되어 [인증]을 마친다.
- 그 Authentication 객체는 Security Context 에 저장되고, Security Context는 SecurityContextHolder에 저장되며, Session 에도 저장된다. (??? 이부분 이해 필요)
- 시큐리티는 어떤 요청에 대해 자신의 Security Context 안에
>> Remember ME 에 대한 설명 (이건 걍 쿠키 & 세션에 대한 설명) (그니까 SEcurity Context 랑 헷갈리지 말아라)
>> 인증이 되었다는 말은 시큐리티에서 그 사용자에 대한 세션이 생성이 되었고, 그 세션이 인증 객체를 담고 있음. SEcurity Context. 인증 성공한 그 사용자에게 그 세션을 생성할 때 가지고 있는 SESION ID 를 응답헤더에 실어서 보내는 것. 클라이언트는 SESSION ID 를 가지고 있음. 그 세션 아이디를 그대로 가지고 가서, 서버가 그 세션 아이디에 대한 것을 그대로 꺼내는 것.
===== 이거 질문 답변 받고 더 작성하기
======================
AnonymousAuthenticationFilter
> 이 필터가 존재하는 이유 : 인증 받으면 세션에 Authentication 객체를 저장한다.
> 인증 후 사용자 접근 시도시, 그 유저 객체의 여부를 먼저 판단.
객체가 없으면 자원에 접근 하지 못함.
있으면 자원에 접근 가능.
어노니먼스는 그렇지 않음.
> 그냥 쉽게 말하면, 그냥 Block 시키는 것보단, 익명 사용자를 구분해서 처리 하는 것.
> isAuthenticated 이 아니라는 경우보다 더 구분하고 싶을 때 쓰는 듯.
> 좀 ... 뭔지 모르겠고.. 별로인듯!
기본 API 필터는 내가 기능을 쓰겠다고 안하면 그냥 뭐 안하는거인가?
==================================================================
인가API (1-11강)
인가에 대한 권한을 설정할 수 있또록 도와준다.
1. 선언적 방식
1-1 URL 방식
ex) http.antMatchers("/users/**").hasRole("USER")
1-2 Method 방식
ex) @PreAuthorize("hasRole("USER")")
public void hello(){~~}
2. 동적 방식 - DB 연동 프로그래밍
URL, MEthod 존재.
1-1 URL 방식
configure 안에서 계속 진행
http.antMatcher("/shop/**") >> 사용자가 하는 요청이 이 경로에 걸릴 때는, 이 매쳐에 걸린 기능들이 작동하게 된다!
.authorizeRequests() // 위 경로일 경우 하기의 설정들을 적용한다.
.antMathcers("/shop/login", "/shop/users/**").permitAll() //login 과 users 하위 경로에 대해서는 권한 예외처리
.antMatchers("/shop/mypage").hasRole("USER") // 유저의 권한이 있을 경우에 mypage 로 이동가능.
.antMatchers("/shop/admin/pay").access("hasRole("ADMIN")") // admin/pay 에 대해서는 ADMIN 권한
.antMatchers("/shop/admin/**").access("hasRole("ADMIN")" or has Roile~~)
.....
.anyRequest().authenticated(); // 특정 경로를 제외한 모든 요청에는 "인증"을 받았으면 그냥 다 접근 가능.
// access 를 사용하면 내부를 조금더 구체적으로 설정 가능 (SpEL 표현식이라고 함) . hasRole 로 바로 하면 그냥 하나씩만 가능함. 그냥 hasAnyRole 사용해도 됨..
// admin 처럼, 스프링은 위에서 부터 인가처리를 하게 되므로, 구체적인 경로가 먼저 오고 그것보다 큰 범위 경로가 뒤에 오도록 해야 함
// hasIpAddress 로 지정해서 사무실 내 설정 이런것도 가능할듯?
표현식 Table 도 확인해본다. 10분대
============================================