주노 님의 블로그

20240829 본캠프 34일차 TIL 본문

TIL

20240829 본캠프 34일차 TIL

juno0432 2024. 8. 29. 22:45

본캠프 34일차 내용 간단요약

  • 09:00 ~ 10:00 : 코드카타
  • 10:00 ~ 11:30 : 팀 회의
  • 11:30 ~ 12:00 : 개인과제 마무리
  • 12:00 ~ 13:00 : 점심시간
  • 13:00 ~ 14:00 : cs공부
  • 14:00 ~ 17:00 : 심화 1주차
  • 17:00 ~ 18:00 : 튜터님 인터뷰
  • 18:00 ~ 19:00 : 저녁시간
  • 19:00 ~ 20:00 : 개인과제 피드백 수정
  • 20:00 ~ 21:00 : 튜터님 인터뷰 및 TIL 작성

오늘 할일

 

 

완존 러키비키자낭?


개인과제 마무리

더보기

사용하지 않는 생성자 제거와

리드미를 수정하고

tistory 정리를 하였다

 


강의 심화 1주차

더보기

스프링 시큐리티

  • spring 서버에 필요한 인증 및 인가를 위한 기능을 제공해줌으로 개발의 수고를 덜어준다
  • 적용과정
    인증 인가에 대한 spring security 설정을 하기위해 @EnableWebSecurity설정을 하기위해 전달한다.
    jpaAuditing과 비슷하다고 생각하면된다
    config파일을 등록하기위해 @Configuration 어노테이션을 달아준다.
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // CSRF 설정
        http.csrf((csrf) -> csrf.disable());
    
        http.authorizeHttpRequests((authorizeHttpRequests) ->
                authorizeHttpRequests
                //resources 접근을 설정함.
                        .requestMatchers(PathRequest.toStaticResources()
                        .atCommonLocations()).permitAll() // resources 접근 허용 설정
                        //위 경로를 접근 허용 permitAll()한다.
                        .requestMatchers("/api/user/**").permitAll()
                        //특정 경로를 접근 허용할 수 있다 user의 하위 폴더를 선택하려면 **
                        .anyRequest().authenticated() // 그 외 모든 요청 인증처리
        );
    
        // 디폴트 로그인 페이지를 제공해줌
        http.formLogin(Customizer.withDefaults());
    
        return http.build();
    }

    csrf란 사이트간 요청 위조를 방지하기위해 설정하는것이다.
    쿠키 취약점을 이용한 공격이기때문에 rest 방식의 api에서는 disable 해도된다.

    디폴트 로그인 페이지는 이렇다.

    username : user
    Using generated security password: 155bd684-bc97-46ba-8563-e174ea1bac66
    기본 로그인할수 있는 정보를 제공해주며. 패스워드는 서버가 재 시작될때마다 바뀐다.

  • 개념
    spring에서는 모든 호출을 DispatcherServlet을 통과하게되고 controller로 분배되게 되는데
    각 요청에 대해 공통적으로 처리해야할 필요가 있다면
    DispatcherServlet이전에 filter를 두어 공통적으로 관리한다
    spring security도 인증 인가를 처리하기 위해서 filter를 사용하는데

    spring security는 filterChineProxy를 통해서 상세 로직을 구현한다

  • form login 기반 인증
    인증이 필요한 url위치에 요청이 들어왔을때 로그인 페이지로 보내버린다.
    내일배움캠프 스프링 강의 심화 1주차 10강

    로직
    사용자가 username과 password를 제출하면 usernamePasswordAuthentificationFilter는 사용자 인증정보가 담긴 인증 객체인 토큰을 만들어 authentificationManager에게 넘겨서 인증을 시도한다

    실패하면 SecurityContextHolder를 비운다
    성공하면 SecurityCOntextHolder에 authentication을 세팅한다.
내일배움캠프 스프링 강의 심화 1주차 10강
  • SecurityContextHolder
    인증이 완료된 사용자의 상세 정보를 저장한다.

  • Authentication
    현재 인증된 사용자를 나타내며 security Context에서 가져올 수 있다.
    principal : 사용자를 식별한다
    credentials : 비밀번호이고, 사용자 인증 후 비운다
    authorities :  권한을 추상화하여 사용한다.

  • userDetailsService
    사용자를 검증 및 조회하여 userDetails를 반환한다.

로그인

  • 기존 로그인에는 controller에서 작업을 처리햇지만
    스프링 시큐리티는 controller 앞에 spring security가 위치한다로그인 과정
    1. 로그인 할 정보를 HTTP BODY로 POST요청
    WebSecuritConfig를 통해 회원가입 요청 페이지 제어

    2. 인증 관리자 : UserDetailsService에게 username을 전달하고 회원 상세정보 요청
    UserDetailsServiceImpl 및 UserDtailsImpl 구현함으로, security의 디폴트 로그인 기능을 사용하지 않겠다.
    라고 선언하여 기존 로그인페이지가 뜨게된다.
    @AuthenticationPrincipal을 이용하면 로그인 한 유저의 정보를 들고올 수 있다.
    @GetMapping("/products")
    public String getProducts(@AuthenticationPrincipal UserDetailsImpl userDetails)
    {
        //Authentication의 principle 부분에 들어가는 userDetails를 가져오는 부분임.
        User user = userDetails.getUser();
        System.out.println("user.getUsername() = " + user.getUsername());
        return "redirect:/";
    }

    3. UserDetailsService는 db에서 회원 조회를 함
    4. 회원정보가 존재하지 않을시 예외처리, 회원정보가 존재할시 조회된 회원 정보를 UserDetails로 변환
    5. UserDetails를 인증관리자에게 전달
    6. 인증관리자가 인증 처리를함, 클라이언트가 입력한 정보와, UserDetails와 매치 시켜봄
    7. 인증 실패시 예외처리, 인증 성공시 세션에 로그인 정보를 저장함.
  • jwt로 로그인 과정을 변환해보자
    위 spring security방법은 세션 방법이다. jwt로 변환해보자
    @PostMapping("/user/login")
    public String login(LoginRequestDto requestDto, HttpServletResponse res)
    {
        try {
            userService.login(requestDto, res);
        } catch (Exception e) {
            return "redirect:/api/user/login-page?error=";
        }
        return "redirect:/";
    }

    기존의 컨트롤러에서 로그인 하던 방식을 바꿔서 filter에서 로그인 하는 방식으로 바꾸자

    jwt 인증
    @Slf4j(topic = "로그인 및 JWT 생성")
    public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
        private final JwtUtil jwtUtil;

    authenticationFilter를 상속받아서 사용한다

    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException
    // 로그인을 시도하는 메서드
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
    //로그인을 성공했을때 수행하는 메서드
    //토큰을 생성하고, 토큰을 쿠키에 넣는다.
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
    //로그인을 실패했을때 실행하는 메서드

    다들 overriding 하기때문에 많은 메서드를 구현하지않아도되지만
    로그인이 성공하였을경우 jwt토큰을 만드는 과정이 필요하다

    JWT인가
    public class JwtAuthorizationFilter extends OncePerRequestFilter {
    //OncePerRequestFilter을 상속받으면 httpServletRequest를 상속받을수 있다
    
    
    private final JwtUtil jwtUtil;
    private final UserDetailsServiceImpl userDetailsService;
    //JWT와 USER정보를 불러오기 위해서 위 두개를 주입해준다
    
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain filterChain) throws ServletException, IOException 
    //jwt검증 및 인가 과정을 거친다
    //1. 쿠키에서 jwt를 가져온다
    //2. 토큰이 있는지 확인한다
    //3. barrer을 자르는 토큰 자르기 메서드를 호출한다
    //4. 토큰에서 유저 정보를 가져온다
    //5. setAthentication을 호출한다
    
    public void setAuthentication(String username) {
    //인증처리를 위한 메서드
    //유저 정보를 통해
    //authenticatioon 을 createAutenticatioon메서드를 통해 구현체를 만든다.
    //context에 넣어 인증을 완료해준다.
    
    private Authentication createAuthentication(String username) {
    //인증 객체를 생성한다
    //userDetails를 가져온다
    //principal에 userdetails와, null(비밀번호), 권한을 넣어준다
     
    websecurityconfig
    @EnableWebSecurity
    // Spring Security 지원을 가능하게 함
    
    private final JwtUtil jwtUtil;
    private final UserDetailsServiceImpl userDetailsService;
    private final AuthenticationConfiguration authenticationConfiguration;
    //3개를 주입받아온다
    
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
    //인증 작업을 처리하는 메서드
    //수동으로 빈을 등록해줘야한다
    
    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception
    //인증 필터를 빈으로 등록해준다
    //setAuthenticationManager메서드를 호출하여, 인증 작업을 처리할수 있도록한다
    
    @Bean
    public JwtAuthorizationFilter jwtAuthorizationFilter()
    //인가 필터를 빈으로 등록해준다
    
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    //필터를 만들어서 체인으로 넣어주는 역할을 한다
    // CSRF 설정
    // 기본 설정인 Session 방식은 사용하지 않고 JWT 방식을 사용하기 위한 설정
    // 필터의 순서 관리
     
    homeController 수정
    @GetMapping("/")
    public String home(Model model, @AuthenticationPrincipal UserDetailsImpl userDetails) {
        model.addAttribute("username", userDetails.getUsername());
        return "index";
    }

    authenticationPrincipal을 사용하여 유저의 정보를 받아와서
    홈페이지에 부착한다.

접근 불가 페이지 구현

  • UserDetailsImpl를 통해 권한을 설정할 수 있다
    권한은 1개이상 가능하며
    권한 이름은 ROLE_ 로 시작해야한다
    ROLE_USER, ROLE_ADMIN등등.

  • UserDetailsImpl을 통해 authority값을 동적으로 조절할 수 있다.
  • spring scurity에서 @secure 어노테이션을 controller단에 부착하면된다.
    @Secured(UserRoleEnum.Authority.ADMIN) // 관리자용
    @GetMapping("/products/secured")
    public String getProductsByAdmin(@AuthenticationPrincipal UserDetailsImpl userDetails) {
        System.out.println("userDetails.getUsername() = " + userDetails.getUsername());
        for (GrantedAuthority authority : userDetails.getAuthorities()) {
            System.out.println("authority.getAuthority() = " + authority.getAuthority());
        }  
        
        return "redirect:/";
    }

    또한 configuration에 @EnableMethodSecurity(securedEnable = true)를 사용해야 사용가능하다.

Validation

  • 입력한 값에 대한 검증을 해준다
  • 구현
    @Getter
    public class ProductRequestDto {
        @NotBlank // 공백 불가
        private String name;
        @Email //이메일 형식 요구
        private String email;
        @Positive(message = "양수만 가능합니다.")//양수만 가능   
        private int price;
        @Negative(message = "음수만 가능합니다.")//음수만 가능
        private int discount;
        @Size(min = 2, max = 10)// 2~10자 이내
        private String link;
        @Max(10)// 최대 10자
        private int max;
        @Min(2)//최소 2자
        private int min;
    }

    validation을 적용한 dto이다.

    @ResponseBody
    public ProductRequestDto testValid(@RequestBody @Valid ProductRequestDto requestDto) {
        return requestDto;
    }

    사용할 컨트롤러의 매개변수에 @valid 어노테이션을 부착해 줘야한다.

    문제가 없다면? 잘 보내짐

    다시 로그인페이지로 튕겨나간것을 볼 수 있음
    name은 방금 notBlank를 설정했음 ㅇㅇ.
    name을 빈칸으로 보냈기때문에 오류가 떴음

    2024-08-29T16:13:45.825+09:00  WARN 17284 --- [spring-auth] [nio-8080-exec-8] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public cohttp://m.sparta.springauth.dto.ProductRequestDto com.sparta.springauth.controller.ProductController.testValid(cohttp://m.sparta.springauth.dto.ProductRequestDto): [Field error in object 'productRequestDto' on field 'name': rejected value []; codes [NotBlank.productRequestDto.name,NotBlank.name,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [productRequestDto.name,name]; arguments []; default message [name]]; default message [공백일 수 없습니다]] ]

    오류는 console에서 출력해준다.

  • validatgion 예외처리
    회원가입 시 validation을 이용한 예외처리를 해보자
        @PostMapping("/user/signup")
        public String signup(@Valid SignupRequestDto requestDto, BindingResult bindingResult)
        //validation 어노테이션을 붙여야함
        //binding result객체에 오류에 대한 정보가 담김
        {
            // Validation 예외처리
            List<FieldError> fieldErrors = bindingResult.getFieldErrors();
            //bindingResult.getFieldErrors()를 하면 오류가 난 필드를 가져옴
            if (fieldErrors.size() > 0) {
            //size가 0이면 오류가 발생하지 않은것
                for (FieldError fieldError : bindingResult.getFieldErrors()) {
                    log.error(fieldError.getField() + " 필드 : " + fieldError.getDefaultMessage());
                }
                //for문을 돌며 list에 있는 오류를 다 뽑아옴
                return "redirect:/api/user/signup";
            }
    
            userService.signup(requestDto);
    
            return "redirect:/api/user/login-page";
        }

    binding result를 통해 오류를 뿌려줌

 

 

 

 

 


오늘 해야할 일 ✔️ 🔺 ❌

🔺 과제 해설강의 보기

🔺 피드백 수정

✔️코딩테스트 인강 3개 듣기

✔️  강의 1주차 듣기


오늘의 회고 & 12시간 몰입했는가? 

스프링 시큐리티.. 어렵군... 내일은 과제 수정 마무리 하면될것같다..!

'TIL' 카테고리의 다른 글

20240902 본캠프 36일차 TIL  (0) 2024.09.02
20240830 본캠프 35일차 TIL  (0) 2024.08.31
20240828 본캠프 33일차 TIL  (0) 2024.08.28
20240827 본캠프 32일차 TIL  (0) 2024.08.27
20240826 본캠프 31일차 TIL  (0) 2024.08.26