From 028dc1125af2423ecf39d31194cf00224a6d29c2 Mon Sep 17 00:00:00 2001 From: JianChoi-Kor Date: Tue, 12 Jul 2022 00:18:09 +0900 Subject: [PATCH] Security --- .../basic/configure/WebMvcConfigure.java | 18 ++++ .../security/CustomAuthFailureHandler.java | 20 ++++ .../security/CustomAuthSuccessHandler.java | 20 ++++ .../security/CustomUserDetailsService.java | 60 +++++++++++ .../basic/security/MethodSecurity.java | 13 +++ .../basic/security/WebSecurityConfigure.java | 102 ++++++++++++++++++ 6 files changed, 233 insertions(+) create mode 100644 src/main/java/com/security/basic/configure/WebMvcConfigure.java create mode 100644 src/main/java/com/security/basic/security/CustomAuthFailureHandler.java create mode 100644 src/main/java/com/security/basic/security/CustomAuthSuccessHandler.java create mode 100644 src/main/java/com/security/basic/security/CustomUserDetailsService.java create mode 100644 src/main/java/com/security/basic/security/MethodSecurity.java create mode 100644 src/main/java/com/security/basic/security/WebSecurityConfigure.java diff --git a/src/main/java/com/security/basic/configure/WebMvcConfigure.java b/src/main/java/com/security/basic/configure/WebMvcConfigure.java new file mode 100644 index 0000000..8ec786e --- /dev/null +++ b/src/main/java/com/security/basic/configure/WebMvcConfigure.java @@ -0,0 +1,18 @@ +package com.security.basic.configure; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebMvcConfigure implements WebMvcConfigurer { + + private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { + "classpath:/static/", "classpath:/public/", "classpath:/", "classpath:/resources/", "classpath:/META-INF/resources/", "classpath:/META-INF/resources/webjars/" + }; + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("/**").addResourceLocations(CLASSPATH_RESOURCE_LOCATIONS); + } +} diff --git a/src/main/java/com/security/basic/security/CustomAuthFailureHandler.java b/src/main/java/com/security/basic/security/CustomAuthFailureHandler.java new file mode 100644 index 0000000..a30d81a --- /dev/null +++ b/src/main/java/com/security/basic/security/CustomAuthFailureHandler.java @@ -0,0 +1,20 @@ +package com.security.basic.security; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Slf4j +public class CustomAuthFailureHandler implements AuthenticationFailureHandler { + + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { + log.info("exception : " + exception.getMessage()); + response.sendRedirect("/login"); + } +} diff --git a/src/main/java/com/security/basic/security/CustomAuthSuccessHandler.java b/src/main/java/com/security/basic/security/CustomAuthSuccessHandler.java new file mode 100644 index 0000000..7c89993 --- /dev/null +++ b/src/main/java/com/security/basic/security/CustomAuthSuccessHandler.java @@ -0,0 +1,20 @@ +package com.security.basic.security; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Slf4j +public class CustomAuthSuccessHandler implements AuthenticationSuccessHandler { + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { + log.info("authentication : " + authentication.getName()); + response.sendRedirect("/home"); //인증이 성공한 후에는 home으로 이동 + } +} diff --git a/src/main/java/com/security/basic/security/CustomUserDetailsService.java b/src/main/java/com/security/basic/security/CustomUserDetailsService.java new file mode 100644 index 0000000..70954d2 --- /dev/null +++ b/src/main/java/com/security/basic/security/CustomUserDetailsService.java @@ -0,0 +1,60 @@ +package com.security.basic.security; + +import com.security.basic.persistence.dao.UserRepository; +import com.security.basic.persistence.model.Privilege; +import com.security.basic.persistence.model.Role; +import com.security.basic.persistence.model.User; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +@Slf4j +@Component +@RequiredArgsConstructor +public class CustomUserDetailsService implements UserDetailsService { + + private final UserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { + final User user = userRepository.findByEmail(email); + if (user == null) { + throw new UsernameNotFoundException("User Not Found"); + } + return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), getAuthorities(user.getRoles())); + } + + private Collection getAuthorities(final Collection roles) { + return getGrantedAuthorities(getPrivileges(roles)); + } + + private List getPrivileges(final Collection roles) { + final List privileges = new ArrayList<>(); + final List collection = new ArrayList<>(); + for (final Role role : roles) { + privileges.add(role.getName()); + collection.addAll(role.getPrivileges()); + } + for (final Privilege item : collection) { + privileges.add(item.getName()); + } + return privileges; + } + + private List getGrantedAuthorities(final List privileges) { + final List authorities = new ArrayList<>(); + for (final String privilege : privileges) { + authorities.add(new SimpleGrantedAuthority(privilege)); + } + return authorities; + } +} diff --git a/src/main/java/com/security/basic/security/MethodSecurity.java b/src/main/java/com/security/basic/security/MethodSecurity.java new file mode 100644 index 0000000..fddf48d --- /dev/null +++ b/src/main/java/com/security/basic/security/MethodSecurity.java @@ -0,0 +1,13 @@ +package com.security.basic.security; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; + +//securedEnabled @Secured 어노테이션을 사용하여 인가를 처리하고 싶을 때 사용하는 옵션 +//prePostEnabled @PreAuthorize, @PostAuthorize 어노테이션을 사용하여 인가를 처리하고 싶을 때 사용하는 옵션 +//jsr250Enabled @RolesAllowed 어노테이션을 사용하여 인가를 처리하고 싶을 때 사용하는 옵션 +@Configuration +@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true) +public class MethodSecurity { + +} diff --git a/src/main/java/com/security/basic/security/WebSecurityConfigure.java b/src/main/java/com/security/basic/security/WebSecurityConfigure.java new file mode 100644 index 0000000..3d90cc0 --- /dev/null +++ b/src/main/java/com/security/basic/security/WebSecurityConfigure.java @@ -0,0 +1,102 @@ +package com.security.basic.security; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.security.servlet.PathRequest; +import org.springframework.boot.web.servlet.ServletListenerRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.session.HttpSessionEventPublisher; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; + +@Slf4j +@RequiredArgsConstructor +@Configuration +public class WebSecurityConfigure { + + @Bean + public WebSecurityCustomizer webSecurityCustomizer() { + return (web -> web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations())); + } + + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .csrf().disable(); + + //요청에 대한 설정 + //permitAll시 해당 url에 대한 인증 정보를 요구하지 않는다. + //authenticated시 해당 url에는 인증 정보를 요구한다.(로그인 필요) + //hasAnyRole시 해당 url에는 특정 권한 정보를 요구한다. +// http +// .authorizeRequests() +// .antMatchers("/login", "/home").permitAll() +// .antMatchers("/user").hasAnyRole("USER", "ADMIN") +// .antMatchers("/admin").hasAnyRole("ADMIN") +// .anyRequest().authenticated(); + + http + .authorizeHttpRequests() + .antMatchers("/**").permitAll(); + + + //로그인폼 관련 설정 + //loginPage 로그인 페이지를 설정합니다. 해당 페이지를 설정하지 않으면 Spring Security에서 구현한 디폴트 화면이 노출됩니다. + //defaultSuccessUrl 인가된 사용자일 경우 로그인 성공 후 이동할 페이지를 설정합니다. + //failureUrl 로그인 검증이 실패한 경우 이동할 페이지를 설정합니다. + //successHandler defaultSuccessUrl에서 이동할 페이지만 설정하였다면 handler를 구현하고 이를 등록하면 좀 더 세부적인 작업을 수행할 수 있습니다. + //failureHandler successHandler와 마찬가지로 handler 구현을 통해 더 세부적인 작업을 수행할 수 있습니다. + http + .formLogin() + .defaultSuccessUrl("/home") + .failureUrl("/login?error=true") +// .successHandler(successHandler()) +// .failureHandler(failureHandler()) + .usernameParameter("email") + .passwordParameter("password"); + + http + .logout() + .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) + .logoutSuccessUrl("/login"); + + //동시 세션 관련 설정 + //maximumSessions 최대 허용 가능 세션 수를 설정 + //maxSessionPreventsLogin 위에서 설정한 최대 허용 세션의 수가 되었을 때 추가적인 인증이(세션 생성) 있을 경우 어떻게 처리할지 설정 + //true일 경우 현재 사용자 인증 실패, false인 경우 기존 세션 만료 + //expiredUrl 세션이 만료된 경우 이동할 페이지를 설정정 + http + .sessionManagement() + .maximumSessions(1) + .maxSessionsPreventsLogin(true) + .expiredUrl("/expired"); + + return http.build(); + } + + @Bean + public static ServletListenerRegistrationBean httpSessionEventPublisher() { + return new ServletListenerRegistrationBean(new HttpSessionEventPublisher()); + } + + @Bean + public AuthenticationSuccessHandler successHandler() { + return new CustomAuthSuccessHandler(); + } + + @Bean + public AuthenticationFailureHandler failureHandler() { + return new CustomAuthFailureHandler(); + } +}