spring.security.user.name=user
spring.security.user.password=1111
현재는 WebSecurityConfigurerAdater가 아닌 FilterChain 사용을 권장함
@Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http
.authorizeRequests() // 인가가 필요한 경우 설정
.anyRequest() // 어떠한 url 요청이던지
.authenticated(); // 인가 필요
.loginPage
아이디와 비밀번호를 입력하는 로그인 페이지 url주소를 설정할 수 있다. 사용자가 로그인 페이지를 직접 만들거나 로그인 주소를 변경할 때 사용된다. 기본 주소는 /login이다. 로그인하지 않은 상태에서 로그인이 필요한 페이지에 접근할 때 해당 url로 이동한다. (컨트롤러에서 @GetMapping의 url)
.defaultSuccessUrl
: 로그인 성공 후 자동으로 이동할 페이지이다.
로그인 성공 후 redirect될 페이지를 정하는 것은 이 메소드 뿐이 아니다. .successHandler에서 send.redirect해줄 수도 있고, requestCache에서 직전에 접속하려 했던 url을 get해서 그 페이지로 이동하기도 한다. 그 중 .defaultSuccessUrl은 최후순위를 가지게 된다. 만약 이걸로 설정한 url로 무조건 리다이렉트가게 하고 싶다면 .defaultSuccessUrl("/home", true)같이 뒤에 인자로 true를 주면 된다.
.failureUrl
: 로그인 실패 후 자동으로 이동할 페이지이다.
.usernameParameter
: 아이디(username) 파라미터를 커스텀으로 정할 수 있다. 기본값은 username이다.
ex) usernameParameter("email")로 설정하면 로그인에 필요한 아이디는 email로 세팅된다
.passwordParameter
: 비밀번호 파라미터를 커스텀으로 정할 수 있다. 기본값은 password이다.
.loginProcessingUrl
: 사용자 아이디와 비밀번호를 제출할 URL로 기본값은 /login이다. 해당 URl이 리턴하는 html파일에 파라미터 바인딩하여 post로 값이 넘어감
UserDetailService를 구현한 클래스내에 loadUserByUsername
메소드가 자동으로 실행되어 저장되어 있는 user를 가지고와서 입력받은 아이디와 비교한다(컨트롤러를 따로 만들 필요가 없다.)
loginProcessingUrl() -> loadUserByUsername -> 바인딩 된 username과 비교하여 저장된 user를 가지고옴
.successHandler
: 로그인 성공 이후 실행된다. 밑에서 자세히 알아보자.
.failureHandler: 로그인 실패 이후 실행된다. 밑에서 자세히 알아보자.
로그인 페이지와 로그인 정보를 전달할 action url은 .permitAll된다. 즉, 인증이 없이 접근이 가능하다. 로그인을 하기 위한 로그인은 필요없다.
.usernameParameter("userId")
.passwordParameter("passwd")
.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());
//로그인에 성공한 유저의 이름
response.sendRedirect("/");
}
})
.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");
}
})
UserDetailsService
를 사용하고 입력받은 값과 비교http.rememberMe()
.rememberMeParameter("remember") // 기본 파라미터명은 remember-me
.tokenValiditySeconds(3600) // 기본값은 14일
.alwaysRemember(true) // 기능이 활성화되지 않아도 항상 실행
.userDetailsService(userDetailsService) // 사용자의 정보를 가지고 옴
체크하고 로그인 시, JSESSIONID 말고도 remember-me 라는 쿠키가 날아온다. 이 쿠키에는 회원 아이디와 비밀번호 등이 인코딩 되어 들어있다.
따라서 JSESSIONID이 다른 요인에 의해 삭제되거나 request header에 보내지 않더라도서버에서는 회원 인증을 하고, 실제 회원과 일치한 정보가 있다면 로그인을 해주고 거기에 JSESSIONID 쿠키까지 새로 만들어서 보내준다.
실행 조건
RememberMeAuthenticationFilter
필터가 실행될 조건은 다음과 같다.
(1) 스프링 시큐리티에서 사용하는 인증객체(Authentication)가 Security Context에 없는 경우
세션 만료(time out), 브라우저 종료, 세션id 자체를 모르는 등의 요인이 있다.
인증객체가 있다라는 소리는 로그인이 정상적으로 되었고, 회원 정보도 정상적으로 세션에서 찾을 수 있다는 말이다. 따라서 이 필터가 실행될 필요가 없다.
(2) 사용자 request header에 remember-me 쿠키 토큰이 존재해야 한다.
구현체의 차이
위의 실행 조건이 부합하다면 RememberMeService(인터페이스)가 실행된다. 실제 구현체 2가지 있는데, 그 차이는 다음과 같다.
(1) TokenBasedRememberMeServices
: 메모리에 있는 쿠키와 사용자가 보내온 remember-me 쿠키를 비교(기본적으로 14일간 존재)
(2) PersistentTokenBasedRememberMeServices
: DB에 저장되어 있는 쿠키와 사용자가 보내온 remember-me 쿠키를 비교(이름 그대로 persistent)
로직
(1) Token Cookie를 추출했을 때, 사용자가 request한 토큰이 remember-me 토큰인지 확인
(2) Decode Token하여 토큰이 정상인지 판단
(3) 사용자가 들고온 토큰과 서버에 저장된 토큰이 서로 일치하는지 판단
(4) 토큰에 저장된 정보를 이용해 DB에 해당 User 계정이 존재하는지 판단
(5) 위 조건을 모두 통과하면 새로운 인증객체(Authentication)을 생성 후 AuthenticationManager에게 인증 처리를 넘긴다. (물론 Security Context에도 인증 객체를 저장한다.)
(6) 이후 response 될 때 JSESSIONID를 다시 보내준다.
remember-me 토큰 초기화 시점
(1) AuthenticationException
: 인증 예외가 발생하면 예외가 발생하기 전의 요청정보를 SavedRequestCache에 저장하고 AuthenticationEntryPoint를 호출해서 401오류코드를 전달
(2) AccessDeniedException
: AccessDeniedHandler에서 인가 예외를 처리하도록 제공
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint())
// 인증실패 시 처리
.accessDeniedHandler(accessDeniedHandler())
// 인가실패 시 처리
successHandler(new AuthenticationSuccessHandler()
RequestCache requestCache = new HttpSessionRequestCache();
SavedRequest savedRequest = requestCache.getRequest(request, response);
response.SendRedirect(savedRequest.getRedirectUrl())
// RequestCache에 정보를 저장해놓고
다시 인증, 인가를 받으면 원래 있던 페이지로 redirect
http.sessionManagement() // 세션 관리기능 작동
.maximumSessions(1) // 계정당 최대 허용가능 세션 수, -1일 경우 무제한
.maxSessionPreventsLogin(true/false)
// false : 기존세션만료 , true : 새로운 접근차단
.invalidSessionUrl("/invalid")
// 세션이 유효하지 않을 때 이동할 페이지 expiredUrl 보다 우선적용
.expireUrl("/expired") // 세션이 만료된 경우 이동할 페이지
http.sessionManagement()
.sessionFixation.changeSessionId // 코드를 작성하지 않아도 기본적용됨
// none, migrateSession(서블릿버전 3.1 이하에서 기본적용) , newSession
//changeSessionId , migrateSession은 이전 세션의 정보까지 가져옴
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.If_Required)
1. UsernamePasswordAuth 필터 : 아이디와 비밀번호 일치여부로 인증
2. ConcurrentSessionControlAuth : 동시 세션처리를 하는 클래스로 인증을 요청하는 사용자가 현재 사용하는 세션이 몇 개인지 체크
3. ChangeSessionIdAuth : 세션 고정보호처리, 새롭게 세션처리
4. RegisterSessionAuth : 사용자의 세션을 등록, 사용자의 count가 증가함
5. concurrentSessionFilter : 현재 세션의 만료여부 체크
authorizeRequests() : 요청Url에 대한 권한 지정. Security 처리에 HttpServletRequest를 이용한다는 것을 의미한다.
antMatchers() : 특정 경로를 지정해서 권한 설정. 보통 뒤에 permitAll(), hasRole() 등 다른 메서드가 붙습니다.
antMatcher와 mvcMatchers
일반적으로 mvcMatcher는 antMatcher보다 좀 더 포괄적이다.
antMatchers("/secured")는 정확한 /secured URL과만 일치하고,
mvcMatchers("/secured")는 /secured와 /secured/, /secured.html, /secured.xyz 등 과도 일치한다.
anyRequest() : 설정한 경로 외에 모든 경로를 뜻합니다.
authenticated() : 인증된 사용자만이 접근할 수 있습니다.
permitAll() : 어떤 사용자든지 접근할 수 있습니다.
hasRole() : 특정 ROLE을 가지고 있는 사람이 접근할 수 있습니다.
hasAuthority() : 특정 권한을 가지고 있는 사람만 접근할 수 있습니다. hasRole과 비슷하다고 볼 수 있습니다.
csrf() : CSRF 보안에 대한 설정입니다. 아무 설정도 하지 않으면 CSRF 보안을 하도록 설정됩니다. (http.csrf.disable())
disable() : 해당 기능을 해제 합니다. 여기서는 csrf()를 해제합니다.
unauthorizedEntryPoint는 AuthenticationEntryPoint가 리턴값입니다.
formLogin() : form 기반의 로그인을 할 수 있습니다.
loginPage() : 로그인 페이지의 URL을 설정합니다. 로그인이 필요한 페이지에 들어갔을 때 로그인 한 상태가 아니라면 해당 url로 redirect 해준다. 컨트롤러를 따로 만들 필요가 없다.
failureUrl() : 로그인에 실패했을 때에 해당 URL로 가게 합니다.
defaultSuccessUrl() : 로그인에 성공했을 때에 아무런 설정을 하지 않았을 시 넘어가는 페이지를 설정합니다.
successHandler() : defaultSuccessUrl과 비교해서 비슷하다고 생각할 수 있지만, 로그인에 성공했을 때 내가 원하는 대로 설정할 수 있습니다. 대신 () 안에 해당 객체를 넣어줘야 합니다.
failureHandler() : failureUrl과 비슷하지만, 로그인에 실패했을 때 내가 원하는 대로 설정합니다.
logout() : 로그아웃에 대해 설정할 수 있습니다.
logoutRequestMatcher() : 로그아웃을 실행할 주소를 나타냅니다. 새롭게 로그아웃 주소를 설정할 수 있습니다.
cf. logoutUrl() : 로그아웃을 실행할 주소를 나타낸다. 기본값으로 "/logout"이 적용된다고 합니다.
AntPathRequestMatcher() : HTTP 메서드와 일치하는 특정 패턴으로 Matcher를 작성합니다. AntPathRequestMatcher는 다른 곳에서도 종종 볼 수 있습니다.
logoutSuccessUrl() : 로그아웃을 성공했을 때 이동하는 페이지를 설정합니다.
sessionManagement() : 세션에 관한 설정을 한다.
sessionCreationPolicy() : 세션 create에 대해 설정한다.
SessionCreationPolicy.STATELESS : HTTPSession을 생성하지 않고 SecurityContext를 얻기 위해 HTTPSession을 사용하지 않는다.
exceptionHandling() : 예외사항을 설정한다.
authenticationEntryPoint() : 인증의 진입지점을 설정한다.
addFilter(filter, CLASS) : 지정된 필터 클래스 뒤에 필터를 추가한다.
UsernamePasswordAuthenticationfilter : Spring에서 기본적으로 제공하는 클래스이다. username과 password를 매개변수로 받는다.
usernameParameter() : 로그인에 사용될 파라미터 지정. 아이디 부분이 된다.
passwordParameter() : 로그인에 사용될 파라미터 지정. 비밀번호 부분이 된다.
invalidateHttpSession() : 로그아웃 시 인증정보를 지우고 설정된 세션을 무효화 시킨다는 설정
authenticationProvider() : AuthenticationProvider를 추가로 사용하게 허용한다.
-> AuthenticationProvider는 인터페이스로 화면에서 입력한 로그인 정보와 DB에서 가져온 사용자 정보를 비교해주는 인터페이스이다.
cors() : REST API를 개발할 때 백엔드와 프론트엔드를 연결하기 위하여 사용한다.
configurationSource() : cors 요청에 따라 어떤 방법으로 해결할 지 방법을 지정
httpBasic() : Basic Authentication을 정의할 때 사용한다.
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.antMatchers("/ws/**").permitAll()
.antMatchers("/h2-console", "/sign/**", "/error").permitAll()
.antMatchers("/api/system/**").hasRole(AppUserRole.SYSTEM.name())
.antMatchers("/api/manage/**").hasRole(AppUserRole.ADMIN.name())
.antMatchers("/api/openbabel/**").authenticated()
.antMatchers("/api/moleditor/**").authenticated()
.antMatchers(HttpMethod.GET, "/api/common/**").permitAll()
.anyRequest().authenticated()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER)
.and().exceptionHandling().authenticationEntryPoint(unauthorizedEntryPoint())
.and().httpBasic()
.and().csrf().disable();
http.headers().frameOptions().sameOrigin(); // h2-console
http.cors().configurationSource(corsConfigurationSource());
}