Spring Security
인증과 접근제어를 위해 세부적인 맞춤 구성이 가능한 강력한 필터 기반 프레임워크
Spring 기반 애플리케이션을 보호하기 위한 사실상 표준 프레임워크
Annotation 또는 간단한 코딩만으로 인증 및 접근제어가 가능
Spring Security 적용
Spring Security는 Dependency를 추가하는 것 만으로도 보안이 적용된다.
모든 URL 자원들은 Spring Security의 인증이 있어야 사용할 수 있다.
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
ID: user
Password: Console의 비밀번호
Spring Security 기본 구성
사용자 관리
/**
* <pre>
* Spring Security가 인증된 사용자의 정보를 보관할 클래스.
* UserDetails 인터페이스를 구현해야함.
* </pre>
*/
public class SecurityUser implements UserDetails {
/**
* <pre>
* UserDetails 인터페이스는
* 회원의 로그인아이디(이메일)와 비밀번호만 관리하기에
* 회원의 구체적인 정보는 알 수 없음.
*
* 사용자의 구체적인 정보를 알기 위해
* MemberVO를 멤버변수로 정의.
* </pre>
*/
private MemberVO memberVO;
public SecurityUser(MemberVO memberVO) {
this.memberVO = memberVO;
}
public MemberVO getMemberVO() {
return memberVO;
}
/**
* <pre>
* 로그인한 회원의 접근권한을 설정.
* 실무환경에서 아래 메소드를 만들기 위해
* URL별, C/R/U/D 권한을 별도로 관리.
*
* 예> /boards
* C: USER, ADMIN
* R: USER, ADMIN
* U: USER, ADMIN
* D: ADMIN
* 어떤 사용자가 /boards의 D를 수행하려면
* D에 대한 권한이 있는지 체크.
* </pre>
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
// authorities.add( new SimpleGrantedAuthority("CREATE"));
// authorities.add( new SimpleGrantedAuthority("READ"));
// authorities.add( new SimpleGrantedAuthority("UPDATE"));
// authorities.add( new SimpleGrantedAuthority("DELETE"));
/**
* memberVO.getAdminYN() == Y ==> ROLE_ADMIN
* memberVO.getAdminYN() == N ==> ROLE_USER
*/
String role = "ROLE_USER";
if(this.memberVO.getAdminYn().equals("Y")) {
role = "ROLE_ADMIN";
}
authorities.add(new SimpleGrantedAuthority(role));
return authorities;
}
/**
* 현재 사용자가 로그인에 사용한 비밀번호
*/
@Override
public String getPassword() {
return this.memberVO.getPassword();
}
/**
* 현재 사용자가 로그인에 사용한 이메일 정보
*/
@Override
public String getUsername() {
return this.memberVO.getEmail();
}
/**
* 로그인 사용자 계정이 만료되지 않았는지 여부
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* 로그인 사용자 계정이 잠금처리 되지 않았는지 여부
*/
@Override
public boolean isAccountNonLocked() {
return true;
}
/**
* 로그인 사용자 계정 비밀번호 변경일이 만료되지 않았는지 여부
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 로그인 사용자 계정이 유효한지 여부
*/
@Override
public boolean isEnabled() {
return true;
}
}
/**
* <pre>
* Spring Security에서 로그인(인증)처리를 할 때
* 사용자의 세부정보(상세정보)를 조회하는 기능.
*
* UserDetailsService 인터페이스 구현이 필요.
* </pre>
*/
public class SecurityUserDetailsService implements UserDetailsService {
//@Autowired
/**
* <pre>
* SecurityUserDetailsService 클래스는 Spring Bean 이 아니기 때문에
* @Autowired를 쓸 수 없음!!
* 따라서, 생성자나 Setter을 이용해서 MemberDao를 받아와야 한다.
* </pre>
*/
private MemberDao memberDao;
public SecurityUserDetailsService(MemberDao memberDao) {
this.memberDao = memberDao;
}
/**
* <pre>
* Spring Security 가 로그인 요청을 수행하면
* AuthenticationProvider(인증공급자)에서 로그인 아이디(이메일)로
* 회원의 정보를 조회하는 것을 요청한다.
* 이 때, loadUserByUserName 이 호출되며, 파라미터로는 로그인 아이디(이메일)가 전달된다.
*
* 만약, 로그인아이디(이메일)로 회원의 정보를 조회했을 때, 조회한 결과가 없을 경우
* UsernameNotFoundException이 던져져야 한다.
* </pre>
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
MemberVO memberVO = this.memberDao.getMemberByEmail(username);
if(memberVO == null) {
throw new UsernameNotFoundException("아이디 또는 비밀번호가 일치하지 않습니다.");
}
return new SecurityUser(memberVO);
}
}
/**
* <pre>
* Spring Security 의 전반적인 설정이 이루어지는 곳.
*
* Spring Security에 필요한 Bean의 생성.
* Spring Security의 보안 정책을 설정.
* </pre>
*/
@Configuration
@EnableWebSecurity // Spring Security Filter 정책 설정을 위한 Annotation
public class SecurityConfig {
@Autowired
private MemberDao memberDao;
/**
* 사용자 세부정보 서비스에 대한 Spring Bean 생성.
*
* @return SecurityUserDetailsService 의 bean
*/
@Bean
UserDetailsService userDetailsService() {
return new SecurityUserDetailsService(this.memberDao);
}
/**
* 암호 인코더에 대한 Spring Bean 생성
*
* @return SecuritySHA 의 bean
*/
@Bean
PasswordEncoder passwordEncoder() {
return new SecuritySHA();
}
/**
* Spring Security가 절대 개입하지 말아야하는 URL들을 정의.
* 아래 URL에서 보여지는 페이지 내부에서는 Security Taglib을 사용할 수 없다.
* @return
*/
@Bean
WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring()
.requestMatchers(AntPathRequestMatcher.antMatcher("/WEB-INF/Views/**"))
.requestMatchers(AntPathRequestMatcher.antMatcher("/member/login"))
.requestMatchers(AntPathRequestMatcher.antMatcher("/member/regist/**"))
.requestMatchers(AntPathRequestMatcher.antMatcher("/error/**"))
.requestMatchers(AntPathRequestMatcher.antMatcher("/favicon.ico"))
.requestMatchers(AntPathRequestMatcher.antMatcher("/member/**-delete-me"))
.requestMatchers(AntPathRequestMatcher.antMatcher("/js/**"))
.requestMatchers(AntPathRequestMatcher.antMatcher("/css/**"));
}
/**
* Spring Security Filter가 동작해야할 방식(순서)를 정의.
*
* @param http : HttpSecurity 필터 전략
* @return SpringSecurityFilterChain : Spring Security가 동작해야할 순서
* @throws Exception
*/
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(httpRequest -> httpRequest
// /board/list 는 Security 인증 여부와 관계없이 모두 접근이 가능하다.
.requestMatchers(AntPathRequestMatcher.antMatcher("/board/search"))
.permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/ajax/menu/list"))
.permitAll() // /ajax/menu/list 는 Security 인증 여부와 관계없이 모두 접근이 가능하다.
.requestMatchers(AntPathRequestMatcher.antMatcher("/board/excel/download2"))
.hasRole("ADMIN")
.requestMatchers(AntPathRequestMatcher.antMatcher("/ajax/board/delete/massive"))
.hasRole("ADMIN")
.requestMatchers(AntPathRequestMatcher.antMatcher("/ajax/board/excel/write"))
.hasRole("ADMIN")
// 그 외 모든 URL은 인증이 필요하다.
.anyRequest().authenticated());
// CSRF 기능을 일시적으로 해제한다.
// 현재 시점에서 아래 코드가 없을 경우,
// HTTP POST, PUT, DELETE 등은 정상 동작하지 않는다.
http.csrf(csrf -> csrf.disable());
// 로그인(필터) 정책 설정.
// 우리 애플리케이션은 Form 기반으로 로그인을 하여
// 로그인이 완료되면, /board/search로 이동을 해야한다.
// 로그인 페이지 변경.
http.formLogin(formLogin -> formLogin
// Spring Security 인증이 성공할 경우, LoginSuccessHandler가 동작되도록 설정.
.successHandler(new LoginSuccessHandler())
// Spring Security 인증이 실패할 경우, LoginFailureHandler가 동작되도록 설정.
.failureHandler(new LoginFailureHandler())
// Spring Security Login URL 변경
.loginPage("/member/login")
// Spring Security Login 처리 URL 변경
// SecurityAuthenticationProvider 실행 경로 지정
.loginProcessingUrl("/member/login-proc")
// 로그인ID가 전달될 파라미터 이름
.usernameParameter("email")
// 로그인PW가 전달될 파라미터 이름
.passwordParameter("password"));
// CSRF 방어로직 무효화.
http.csrf(csrf -> csrf.disable());
return http.build();
}
}