Back to the Spring

[Spring Security] SecurityConfig, UserDetailsService, BCrypt, CSRF

Backcoder 2022. 12. 23. 21:55

1. Spring Security

:  Application 의 보안( Authentication, Authroization )을 담당하는 스프링 하위 프레임워크

 

- Authentication ( 인증 ) : 해당 사용자의 로그인 정보가 일치하는 지 확인 ( ID / PW ) 

- Authorization ( 권한 ) : '인증' 된 사용자가 요청한 페이지에 접근 가능한지 권한을 확인하고 부여 


[ SecurityConfig ] 
: 시큐리티의 Authentication / Authorization 기능을 설정

(1) old 버전 

@Configuration
@EnableWebSecurity 
public class SecurityConfig (extends WebSecurityConfigurerAdapter) {

 

 @Override
    protected void configure(HttpSecurity http) throws Exception{
        http.csrf().disable(); // Spring Security - Post - 403 forbidden 방지
        http
            .authorizeRequests()
                // admin 페이지는 ROLE_ADMIN 일때만 match
                .antMatchers("/admin**/**").hasAuthority("ROLE_ADMIN")
                // 나머지 request는 허용 // .authenticated() 로 주면 로그인 했을시 허용  // authorization : 권한처리
                .anyRequest().permitAll()
                .and()

        // 로그인페이지 설정
            .formLogin()
                .loginPage("/login")
                .permitAll() // 로그인 페이지 접근 허용
                //.loginProcessingUrl("/loginProcess")
                .usernameParameter("email")
                .passwordParameter("pw")
                .loginProcessingUrl("/loginProcess")
                .defaultSuccessUrl("/allboard")
                .failureHandler(authenticationFailureHandler)
//                .failureUrl("/loginFail")
                .and()

            // 로그아웃
            .logout()
                .logoutSuccessUrl("/login")

- 관리자 페이지와 같은 권한에 따른 페이지 접근 권한을 부여하고자 하면 

.authorizeRequests().anyMatchers("url꼴").hasAuthority("ROLE_권한").anyRequest().permitAll() 

=> 특정 url 에 대한 접근을 특정 권한이 있을 시에만 permitAll 시켜준다. 

=> 권한은 Enum 을 활용해서 USER / ADMIN 등의 역할을 부여하는 과정을 거친 뒤 Set, DB 에 insert 해준다. 

 


(2) New 버전

: SecurityFilterChain을 리턴하는 메소드를 빈에 등록하는 방식 (컴포넌트 방식으로 컨테이너가 관리)

 @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.authorizeRequests()
                .antMatchers("/user/**").authenticated()
                .antMatchers("/manager/**").access("hasAnyRole('ROLE_MANAGER','ROLE_ADMIN')")
                .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
                .anyRequest().permitAll();
        return http.build();
    }
}

 

=> SecurityConfig 에서는 Authentication 에 성공했을 시, 실패했을 시 특정 URL로 보내는 설정을 해주고, 

Authentication, 즉 로그인 정보가 일치하는지에 대해

UserDetailService 인터페이스를 상속해서 사용하는 UserDetails 메소드를 이용합니다.

 

package ask;

import java.util.Optional;
import javax.servlet.http.HttpSession;
import ask.Member.MemberDTO;
import ask.Member.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
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.Service;

@Service
public class CustomUserDetailService implements UserDetailsService {
    @Autowired
    private MemberRepository memberRepository;
    @Autowired
   HttpSession session;
    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        Optional<MemberDTO> user = memberRepository.findByEmail(email);

        if (user == null) {
            return null;
        }

        // session 에 set만 해두면 자동으로 successURL 로 넘어감
        session.setAttribute("sessiondto", user.get());

        return User.builder()
                .username(email)
                .password(user.get().getPw())
                .roles(user.get().getRole())
                .build();
    }
}

=> UserDetails 에서는 사용자가 로그인 시도 시 입력한 id (email) 값을 받아와서 

DB에서 해당 id에 해당하는 사용자 객체를 가져온다. 

=> 사용자 객체를 User.builder() 를 사용해 빌드하고 리턴한다. 

=> 리턴값을 가지고 자동으로 로그인 정보가 일치하는 지 검증 후 success / fail 을 판단, 

=> SecurityConfig 에서 성공 실패에 대한 URL, 혹은 Handle 처리를 받게 된다. 

 

 

 


2. Bcrypt 암호화 처리 

 

SecurityConfig 에서 @Bean 등록해두고,

회원가입 process를 처리할 때

@Autowired Bcrypt 객체를 가져와서

.encode() 

암호화 후 객체에 set, DB에 저장한다.  

 

- Security Config

@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
    return new BCryptPasswordEncoder();
}

 

- 회원가입 Process

@Autowired
BCryptPasswordEncoder bCryptPasswordEncoder;
@PostMapping("/joinProcess")
public String joinProcess(MemberDTO memberDTO) throws Exception {
    // PW 암호화
    memberDTO.setPw(bCryptPasswordEncoder.encode(memberDTO.getPw()));
    memberRepository.registerMember(memberDTO);
    return "redirect:/login";
}

 



 

- Spring Security 를 사용해 관리자, 일반 사용자 등에 따른 권한을 부여하고, 

권한을 가진 사용자만 접근할 수 있는 페이지를 만들 수 있고 ( Authorization ) 

 

-  UserDetailService 인터페이스를 활용해 로그인 정보 검증을 진행 ( Authentication ), 

검증 통과 / 실패에 따라 Security Config 파일에서 처리하는 방법을 정해둘 수 있습니다.  

 

 

 

3. Security -  CSRF
: Cross-site request forgery 403 forbidden  

(1) Spring Security 적용 시, 요청에서 csrf => forbidden 403
=> Spring Config 파일 생성, 설정이 선행되어야 함 

(2) Spring Boot 에서 Spring Security 를 사용하고, Post 방식 요청을 하고자 할 때 
CSRF 로 인해 403 발생, Spring config 설정 - configure 세팅에서 
http.csrf().disable();  // Spring Security - Post - 403 forbidden 방지 
설정 추가 필요 

(3) AJAX 통신을 사용하고자할 때, CSRF 로 인한 403 에러 발생 
<head> 안에 토큰, 헤더 name 으로 지정해두고 
<meta name="_csrf_parameter" content="${_csrf.parameterName}" />
<meta name="_csrf_header" content="${_csrf.headerName}" />
<meta name="_csrf" content="${_csrf.token}" />

- ajax 에서 데이터를 보내기 전에, 헤더에 토큰으로 담아서 보내는 설정을 해준다. 
- if 문으로 토큰과 헤더가 존재할시만 실행하게 해서 null 값일 때 발생하는 에러를 잡아줘야한다. 

 beforeSend: function(xhr){
          if(token && header) {
          xhr.setRequestHeader(header, token);}
},
data : ...