이 글을 쓴 이유는 수차례 같은 에러로 인해 프로젝트를 새로 작성하고 깃허브 저장소도 새로 만들기를 반복한 끝에 ! 언제까지 새로 만들 수 없다!! 해서 구글링과 튜터님의 도움으로 드디어 해결 되었다.🤗 정말 ......별거 아니였다 ㅠㅠㅋㅋㅋㅋ 별거아닌 것으로 저 처럼 고생하고 있을 누군가를 위해 기록한다.😂
매번 깃허브에 프로젝트를 푸시하고 다른 컴퓨터에서 풀 하는 과정에서 , 자바클래스들이 이름모를 'J'파일로 바껴지면서 에러로 만신창이가 된 프로젝트를 마주하게 됐다.
'j'파일로 바껴져 에러현상
점섬으로 생겨진 에러현상
@어노테이션에러 현상
프로젝트를 풀로 받자마자 이런 현상을 보고 SDK 설치하라는 문구를 보고 설치를 한다.
해당 버전으로 해도 ㅠㅠ 오류는 여전했다.
인텔리제이 기준으로 build.gradle에서 빨간 박스의 돌고래 표시(Load Gradle Changes) (단축키 ctrl + shift + O)를 눌러준다.
저 표시는 라이브러리들이나 환경세팅이 변경됐을 경우 그 세팅에 맞게 프로젝트를 설정해주는 기능이다.
또는 간혹 돌고래 표시가 안 뜰때도 있다. 그럴 때는 build.gradle 창에서 오른쪽 마우스 - [linked Gradle Project] 클릭 후 로딩을 기다리면 ~ ^^ ㅎㅎ 해결🎉
해결
스프링 시큐리티는 SecurityContext에 인증된 Authentication 객체를 넣어두고 현재 스레드 내에서 공유되도록 관리하고 있는데요.
아래는 SecurityContext 인터페이스에 기재된 주석의 일부를 발췌했습니다.
(SecurirtContext : Interface defining the minimum security information associated with the current thread of execution.)
인증이후 편의적으로 현재 인증된 세션유저를 가져오기 위해 @AuthenticationPrincipal 어노테이션을 통해 UserDetails 인터페이스를 구현한 유저 객체를 주입할 때 사용하는 편입니다.
🔥 즉, 'JWT 토큰과 Custom Security Filter를 통해 사용자를 로그인이 된 상태로 유지시켜준다' 라고 이해하면 된다.
@ResponseBody
@PostMapping("/login-page")
public String login(@RequestBody LoginRequestDto loginRequestDto, HttpServletResponse response) {
userService.login(loginRequestDto, response);
return "success";
🔥 기본적으로 GetMapping의 로그인을 Security에서 사용을 하고 있기 때문에 충돌이 발생한다.
그래서 /login-page로 바꿔준다.
// 스프링 시큐리티
implementation 'org.springframework.boot:spring-boot-starter-security'
package com.sparta.myselectshop.config;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.WebSecurityCustomizer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@RequiredArgsConstructor
@EnableWebSecurity // 스프링 Security 지원을 가능하게 함
@EnableGlobalMethodSecurity(securedEnabled = true) // @Secured 어노테이션 활성화
public class WebSecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
// h2-console 사용 및 resources 접근 허용 설정
return (web) -> web.ignoring()
.requestMatchers(PathRequest.toH2Console())
.requestMatchers(PathRequest.toStaticResources().atCommonLocations());
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().antMatchers("/api/user/**").permitAll()
.antMatchers("/api/search").permitAll()
.antMatchers("/api/shop").permitAll()
.anyRequest().authenticated();
http.formLogin().loginPage("/api/user/login-page").permitAll();
http.exceptionHandling().accessDeniedPage("/api/user/forbidden");
return http.build();
}
}
package com.sparta.myselectshop.dto;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
public class SecurityExceptionDto {
private int statusCode;
private String msg;
public SecurityExceptionDto(int statusCode, String msg) {
this.statusCode = statusCode;
this.msg = msg;
}
}
@GetMapping("/forbidden")
public ModelAndView getForbidden() {
return new ModelAndView("forbidden");
}
@PostMapping("/forbidden")
public ModelAndView postForbidden() {
return new ModelAndView("forbidden");
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeHttpRequests().requestMatchers("/api/user/**").permitAll()
.requestMatchers("/api/search").permitAll()
.requestMatchers("/api/shop").permitAll()
.anyRequest().authenticated();
http.formLogin().loginPage("/api/user/login-page").permitAll();
http.exceptionHandling().accessDeniedPage("/api/user/forbidden");
return http.build();
}
stateless : server side에 client와 server의 동작, 상태정보를 저장하지 않는 형태, server의 응답이 client와의 세션 상태와 독립적임
문서에 의하면
Filter base class that aims to guarantee a single execution per request dispatch, on any servlet container. It provides a doFilterInternal method with HttpServletRequest and HttpServletResponse arguments.
어느 서블릿 컨테이너에서나 요청 당 한 번의 실행을 보장하는 것을 목표로 한다.
doFilterInternal메소드와 HttpServletRequest와 HttpServletResponse인자를 제공한다.
인증 또는 인가를 거치고나서 특정 url로 포워딩하면, 요청이 들어왔으니 인증 및 인가필터를 다시 실행시켜야 하지만, OncePerRequestFilter를 사용함으로써 인증이나 인가를 한번만 거치고 다음 로직을 진행할 수 있도록 한다.
혜원님 다 옮기신거예요? 대단해요......!!!
너무 야무지게 잘 옮겨주셔가지고 제가 그동안 못달았던 것들까지 저도 참고해보겠습니다!!
한 해 마무리 잘하시고 저와는 또 내년에 만나요 : >