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

soosoorim·2024년 5월 8일
0

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개의 댓글

관련 채용 정보