[WIL] 23.1.2 9주차

hyewon jeong·2023년 1월 2일
0

TIL

목록 보기
58/138
post-thumbnail

1) 이번 주 알게 된 점

1. Spring Security

Spring Security 기본 설정

스프링 시큐리티 프레임 워크 추가

// 스프링 시큐리티
implementation 'org.springframework.boot:spring-boot-starter-security'

WebSecurityConfig

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity // 스프링 Security 지원을 가능하게 함
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests()
                // image 폴더를 login 없이 허용
                .antMatchers("/images/**").permitAll()
                // css 폴더를 login 없이 허용
                .antMatchers("/css/**").permitAll()
                // 어떤 요청이든 '인증'
                .anyRequest().authenticated()
                .and()
                    // 로그인 기능 허용
                    .formLogin()
                    .loginPage("/user/login")
                    .defaultSuccessUrl("/")
                    .failureUrl("/user/login?error")
                    .permitAll()
                .and()
                    // 로그아웃 기능 허용
                    .logout()
                    .permitAll();
    }
}

2. Spring Security 주요 컴포넌트

Spring Security와 Filter

스프링 시큐리티는 요청이 들어오면 Servlet Filter Chain을 자동으로 구성한 후 거치게 된다.
Filter는 Client요청이 전달되기 전후의 URL패턴에 맞는 모든 요청에 필터링 역할을 한다.
예) CSRF, XSS 등 보안 검사를 통해 올바른 요청이 아닐 경우 이를 차단 한다.
이런 기능을 활용하여 스프링시큐리티에서 filter를 사용하여 인증/인가를 구현한다.

SecurityFilterChain

Spring 의 보안 Filter를 결정하는데 사용되는 Filter
session, jwt 등의 인증방식들을 사용하는데에 필요한 설정을 완전히 분리할 수 있는 환경을 제공한다.
그 중
- AbstractAuthenticationProcessingFilter
: 사용자의 credential을 인증하기 위한 베이스 Filter

  • UsernamePasswordAuthenticationFilter
    : UsernamePasswordAuthenticationFilter 는 AbstractAuthenticationProcessingFilter를 상속한 Filter다. 기본적으로 아래와 같은 Form Login 기반을 사용할 때 username과 password확인하여 인증한다. Form Login기반은 인증이 필요한 URL요청이 들어왔을때 인증이 되지 않았다면 로그인 페이지를 반환하는 형태이다.


- SecurityContextHolder에는 스프링 시큐리티로 인증한 사용자의 상세정보를 저장한 Authentication객체를 저장한 SecurityContext를 담고 있다.

  • SecurityContext ? SecurityContextHolder로 접근 가능 ,Authentication객체 가지고 있음

  • Authentication : 현재 인증된 사용자를 나타내며 , SecurityContext에서 가져올 수 있다.

    • principal : 사용자를 식별한다. Username/password방식으로 인증할 때 보통 UserDetails 인스턴스다.
    • credential : 주로 비밀번호, 대부분 사용자 인증에 사용되고 다음에 비운다.
    • authorities : 사용자에게 부여한 권한을 GrantedAuthority로 추상화하여 사용한다.
		<UserDetails>
		@Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        UserRoleEnum role = user.getRole();
        String authority = role.getAuthority();
        System.out.println("authority = " + authority);

        SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(authority);
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(simpleGrantedAuthority);

        return authorities;
    }

Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
  • UsernamePasswordAuthenticationToken는 Authentication을 implements한 AbstractAuthenticationToken의 하위 클래스로 , 인증객체를 만드는데 사용된다. \

-UserDetailsService
: username/password 인증방식을 사용할 때 사용자를 조회하고 검증한 후 UserDetails를 반환한다. Custom하여 Bean으로 등록 후 사용 가능하다.

비밀번호 암호화

비밀번호 암호화

🔥 회원 등록 시 '비밀번호'는 사용자가 입력한 문자 그대로 DB 에 등록하면  안 됩니다.
'정보통신망법, 개인정보보호법' 에 의해 비밀번호 암호화(Encryption)가 의무입니다.
  • 단방향 암호 알고리즘 : 암호화는 가능하지만 복호화 불가능
  • Password Matching
// 비밀번호 확인
if(!passwordEncoder.matches("사용자가 입력한 비밀번호","저장된 비밀번호")){
  throw new IllegalAccessError("비밀번호가 일치하지 않습니다.");
  }
  • boolean matches(CharSequence rawPassword, String encodedPassword);
    • rawPassword : 사용자가 입력한 비밀번호
    • encodedPassword : 암호화되어 DB 에 저장된 비밀번호

    암호화 기능 추가하는 방법

    WebSecurityConfig
 @Configuration 
 @EnableWebSecurity// 스프링 시큐리티 지원하능하게 함
 public class WebSecurityConfig{
 	
    @Bean//비밀번호 암호화 기능 등록
    public PasswordEncoder passwordEncoder(){
    return new BcryptPasswordEncoer();
    }
    }

@AuthenticationPricipal:

세션을 사용하는 방식과 jwt 같은 토큰을 사용하는 방식에 따라서 시큐리티 설정과 처리방식이 매우 다양한 로직을 띄는데..

간단히 살펴보면 세션을 사용한다면 브라우저의 JSESSIONID를 활용하여 사용자 세션정보를 구분할 수 있으므로 특별히 요청 전에 세션정보만 잘 가져온다면 추가적인 처리가 덜하겠지만 JWT 같은 토큰을 활용한다면 요청된 요청마다의 토큰 정보(곧 세션정보가 될)를 읽어 매번 인증을 진행할 것이다. 따라서 매 요청마다 Filter를 활용하여 SecurityContext에 요청마다 인증되는 Authentication 객체를 set 시킬 것이고 이후에 Controller 에서 @AuthenticationPrincipal을 활용하여 가져올 수 있는 부분이다.

@AuthenticationPricipal: authentication 즉 인증 객체의 principal부분의 값을 가지고 오는데,
Filter를 통해 인증객체를 만들 때 principal부분에 UserDetails를 넣어줬기 때문에 이것을 파라미터로 받아 올 수 있다.
그렇기 때문에 user,username,password 도 안에 들어 있어 유저의 정보를 가져올 수 있다.

    @PostMapping("/login")
    public String login(@AuthenticationPrincipal UserDetails userDetails) {
        System.out.println("*********************************************************");
        System.out.println("UserController.login");
        System.out.println("userDetails.getUsername() = " + userDetails.getUsername());
        System.out.println("*********************************************************");

        return "redirect:/api/user/login-page";
    }

@Secured

스프링 시큐리티를 이용한 API 별 권한 제어 방법

- Controller 에 "**@Secured"** 어노테이션으로 권한 설정 가능
    - **@Secured("권한 이름")** 선언
        - 권한 1개 이상 설정 가능
// (관리자용) 등록된 모든 상품 목록 조회
    @Secured("ROLE_ADMIN")
    @GetMapping("/api/admin/products")
    public List<Product> getAllProducts() {
        return productService.getAllProducts();
    }
  • @Secured" 어노테이션 활성화 방법
<@Configuration
@EnableWebSecurity // 스프링 Security 지원을 가능하게 함
**@EnableGlobalMethodSecurity(securedEnabled = true)** // @Secured 어노테이션 활성화
public class WebSecurityConfig {

401, 403 Error ExceptionHandling 해보기

401,403이 들어오면 commence랑 handle함수가 작동하여 결과를 objectMapperdmfh
스트링값으로 반환하여 Client에게 값을 보낸다.

WebSecurityConfig

public class WebSecurityConfig {
		
		private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
  private final CustomAccessDeniedHandler customAccessDeniedHandler;
		private final UserDetailsServiceImpl userDetailsService;

				// 접근 제한 페이지 이동 설정
				// http.exceptionHandling().accessDeniedPage("/api/user/forbidden");        
		
				 // 401 Error 처리, Authorization 즉, 인증과정에서 실패할 시 처리
				http.exceptionHandling().authenticationEntryPoint(customAuthenticationEntryPoint);
      
				// 403 Error 처리, 인증과는 별개로 추가적인 권한이 충족되지 않는 경우
				http.exceptionHandling().accessDeniedHandler(customAccessDeniedHandler); 
      
				return http.build();
  }

}

CustomAccessDeniedHandle.java

@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
    private static final SecurityExceptionDto exceptionDto =
            new SecurityExceptionDto(HttpStatus.FORBIDDEN.value(), HttpStatus.FORBIDDEN.getReasonPhrase());

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
                       AccessDeniedException accessDeniedException) throws IOException{

        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setStatus(HttpStatus.FORBIDDEN.value());

        try (OutputStream os = response.getOutputStream()) {
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.writeValue(os, exceptionDto);
            os.flush();
        }
    }
}

CustomAuthenticationEntryPoint.java

@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
    private static final SecurityExceptionDto exceptionDto =
            new SecurityExceptionDto(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());

    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authenticationException) throws IOException {

        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setStatus(HttpStatus.UNAUTHORIZED.value());

        try (OutputStream os = response.getOutputStream()) {
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.writeValue(os, exceptionDto);
            os.flush();
        }
    }
}
        

2) 이번 주 목표 달성 여부

  1. 스프링 시큐리티 심화 다 듣기 ( 7빼고 다 들음) - x
  2. 알고리즘 문제 ( 5일 중 3일 품) - x
  3. Stream 공부하기 - o

ㅠ.ㅠ 정신차리자!!!!!!

3) 다음 주 목표 세우기

  1. 스프링 시큐리티 심화 다 듣고 개인 프로젝트 응용해보기
  2. 팀 프로젝트에 도움 되기
  3. 알고리즘 문제 매일 1문제이상 풀기
  4. post-man 잘 활용하기
  5. 테스트 코드 작성해보기
profile
개발자꿈나무

0개의 댓글