(Spring Security) 기본 구성, 사용자 관리

soosoorim·2024년 5월 8일

Spring Security
인증과 접근제어를 위해 세부적인 맞춤 구성이 가능한 강력한 필터 기반 프레임워크
Spring 기반 애플리케이션을 보호하기 위한 사실상 표준 프레임워크
Annotation 또는 간단한 코딩만으로 인증 및 접근제어가 가능

Spring Security 적용
Spring Security는 Dependency를 추가하는 것 만으로도 보안이 적용된다.
모든 URL 자원들은 Spring Security의 인증이 있어야 사용할 수 있다.

  • pom.xml
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>
  • Dependency를 추가한 뒤, Spring Security의 기본 Login Page 에 접근

ID: user
Password: Console의 비밀번호

  • Spring Security의 로그인은 Session 기반이 아니다.
    Spring Security 로그인 후 Session Login 한번 더 수행해야 한다.

Spring Security 기본 구성

사용자 관리

  • SecurityUser
/**
 * <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;
	}

}
  • SecurityUserDetailsService
/**
 * <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);
	}

}
  • SecurityConfig
/**
 * <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();
	}
}

0개의 댓글