
1. SecurityConfig 만들기
- 코드의 변경이 없으면 부모 이름(추상적)으로 리턴할 필요 없음
package shop.mtcoding.blog._core.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration// 메모리에 띄우기 위한 문법
public class SecurityConfig {
@Bean // IoC에 뜸
SecurityFilterChain configure(HttpSecurity http) throws Exception{
return http.build(); // 코드의 변경이 없으면 부모 이름(추상적)으로 리턴할 필요 없음
}
}
2. 현재 상황
- 모든 것을 무효 상태로 만듦
- 하나씩 닫아줘야 함
- 원래 있던 FilterChain에서 아무것도 없는 FilterChain을 만들어 그냥 다 통과된 것

3. 예전 코드
package shop.mtcoding.blog._core.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration// 메모리에 띄우기 위한 문법
public class SecurityConfig {
@Bean
SecurityFilterChain configure(HttpSecurity http) throws Exception{
http.authorizeRequests().anyRequest(); // 예전 코드
return http.build(); // 코드의 변경이 없으면 부모 이름(추상적)으로 리턴할 필요 없음
}
}
- @Deprecated
runtime 때 알려줌
해당 요소(클래스, 메서드, 필드 등)가 더 이상 권장되지 않음
여전히 동작은 하지만, 이후의 버전에서는 사용을 지양하거나 사용을 중단할 수 있음

4. 기본 주소를 우리가 만든 페이지로 변경하기
- 인터페이스를 볼 때 중요한 것 : 이 메서드가 리턴 타입 or method가 있는지 없는지 확인!
- 주소를 잘 만들어야 함
*: 하나의 주소만 가능
** : 모든 주소가 가능
- authenticated() : 인증된 사용자만 접근 가능
- anyRequest() : 모든 요청에 대한 설정
- permitAll() : 모든 사용자가 접근 가능
package shop.mtcoding.blog._core.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration// 메모리에 띄우기 위한 문법
public class SecurityConfig {
@Bean
SecurityFilterChain configure(HttpSecurity http) throws Exception {
// 주소로 필터링 : 인증이 필요한 페이지를 주소로 구분
http.authorizeHttpRequests(a -> { // 리턴 타입이 있으면 {} 필요 없음
a.requestMatchers("/user/updateForm, /board/**").authenticated() // 인증이 필요한 페이지
.anyRequest().permitAll(); // 인증이 필요없는 페이지
});
// 기본 주소를 우리가 만든 페이지로 변경함
http.formLogin(f -> {
f.loginPage("/loginForm");
});
return http.build(); // 코드의 변경이 없으면 부모 이름(추상적)으로 리턴할 필요 없음
}
}


5. 컨트롤러에서 인증 로직 삭제하기
- UserController 수정하기
package shop.mtcoding.blog.user;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import shop.mtcoding.blog.board.Board;
import shop.mtcoding.blog.board.BoardRequest;
@AllArgsConstructor
@Controller
public class UserController {
// fianl 변수는 반드시 초기화 되어야 함
private final UserRepository userRepository; // null
private final HttpSession session;
// @AllArgsConstructor를 사용하면서 필요 없어짐
// public UserController(UserRepository userRepository, HttpSession session) {
// this.userRepository = userRepository;
// this.session = session;
// }
@GetMapping("/loginForm") // view만 원함
public String loginForm() {
return "user/loginForm";
}
// 원래는 get요청이나 예외 post요청하면 됨
// 민감한 정보는 쿼리 스트링에 담아보낼 수 없음
//원래는 get요청이나 예외 post요청하면 됨
//민감한 정보는 쿼리 스트링에 담아보낼 수 없음
// @PostMapping("/login")
// public String login(UserRequest.LoginDTO requestDTO) {
//
// // 1. 유효성 검사
// if (requestDTO.getUsername().length() < 3) {
// return "error/400";
// }
//
// // 2. 모델 필요 select * from user_tb where username=? and password=?
// User user = userRepository.findByUsernameAndPassword(requestDTO); // DB에 조회할때 필요하니까 데이터를 받음
// if (user == null) {
// return "error/401";
// } else {
// session.setAttribute("sessionUser", user);
// return "redirect:/";
// }
// }
@GetMapping("/joinForm") // view만 원함
public String joinForm() {
return "user/joinForm";
}
@PostMapping("/join")
public String join(UserRequest.JoinDTO requestDTO) {
System.out.println(requestDTO);
// 1. 유효성 검사
if (requestDTO.getUsername().length() < 3) {
return "error/400";
}
userRepository.save(requestDTO); // 모델에 위임하기
return "redirect:/loginForm"; //리다이렉션불러놓은게 있어서 다시부른거
}
@GetMapping("/user/updateForm")
public String updateForm() {
// 인증 체크하기
User sessionUser = (User) session.getAttribute("sessionUser");
return "/user/updateForm";
}
@PostMapping("/user/update")
public String updateUser(UserRequest.UpdateDTO requestDTO, HttpServletRequest request) {
// 세션에서 사용자 정보 가져오기
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null) {
return "redirect:/loginForm"; // 로그인 페이지로 리다이렉트
}
// 비밀번호 업데이트
userRepository.userUpdate(requestDTO, sessionUser.getId());
session.setAttribute("sessionUser", sessionUser);
return "redirect:/"; // 홈 페이지로 리다이렉트
}
@GetMapping("/logout")
public String logout() {
// 1번 서랍에 있는 uset를 삭제해야 로그아웃이 됨
session.invalidate(); // 서랍의 내용 삭제
return "redirect:/";
}
- BoardController 수정하기
package shop.mtcoding.blog.board;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import shop.mtcoding.blog.user.User;
import java.util.List;
@RequiredArgsConstructor
@Controller
public class BoardController {
private final HttpSession session; // DI
private final BoardRepository boardRepository; // DI
// ?title=제목1&content=내용1
// title=제목1&content=내용1
// 쿼리 스트링과 -x-www-form-urlencoded와 파싱 방법이 동일함
// http://localhost:8080?page=0
@GetMapping({"/"})
public String index(HttpServletRequest request) {
List<Board> boardList = boardRepository.findAll();
request.setAttribute("boardList", boardList);
return "index";
}
// 상세보기시 호출
@GetMapping("/board/{id}") // 1이 프라이머리키 -> 뭐든 넣어도 실행시키려면 변수화시켜서 {}
public String detail(@PathVariable int id, HttpServletRequest request) {
System.out.println("id : " + id);
// 1. 바로 모델 진입 -> 상세보기 데이터 가져오기
// body 데이터가 없으면 유효성 검사할 필요 없음
BoardResponse.DetailDTO reponseDTO = boardRepository.findByIdWithUser(id); //메서드 이름 변경
// user 객체를 가져와서 session 값 받기 : object라 다운 캐스팅 해야함
User sessionUser = (User) session.getAttribute("sessionUser");
//System.out.println("sessionUser: " + sessionUser);
// 2. 페이지 주인 여부 체크(board의 userId와 sessionId의 값 비교)
boolean pageOwner = false;
if (reponseDTO.getUserId() == sessionUser.getId()) {
//System.out.println("getUserId:" + reponseDTO.getUserId());
pageOwner = true;
}
request.setAttribute("board", reponseDTO);
request.setAttribute("pageOwner", pageOwner); // 이 값을 mustache에게 줘야함!
return "board/detail";
}
@GetMapping("/board/saveForm") // /board/saveForm Get요청이 옴
public String saveForm() { // session 영역에 접근하기 위한
// 1. session 영역에 sessionUser 키 값에 user 객체가 있는지 체크하기
User sessionUser = (User) session.getAttribute("sessionUser");
// null이 아니면 /board/saveForm으로 이동
return "board/saveForm";
}
@PostMapping("/board/save")
public String save(BoardRequest.SaveDTO requestDTO, HttpServletRequest request) {
// 인증 체크
User sessionUser = (User) session.getAttribute("sessionUser");
System.out.println("sessionUser:" + sessionUser);
// 바디 데이터 확인 및 유효성 검사
System.out.println(requestDTO);
if (requestDTO.getTitle().length() > 30) {
request.setAttribute("status", 400);
request.setAttribute("msg", "title의 길이가 30자를 초과해서는 안되요");
return "error/40x"; // BadRequest
}
// 3. 모델 위임
// insert into board_tb(title, content, user_id, created_at) values(?,?,?, now());
boardRepository.save(requestDTO, sessionUser.getId());
return "redirect:/";
}
@GetMapping("/board/{id}/updateForm") // 보드에 해당 페이지
public String updateFormn(@PathVariable int id, HttpServletRequest request) {
// 인증 체크하기
User sessionUser = (User) session.getAttribute("sessionUser");
// 권한 체크하기
Board board = boardRepository.findById(id);
if (board.getUserId() != sessionUser.getId()) {
request.setAttribute("status", 403);
request.setAttribute("msg", "게시글을 수정할 권한이 없습니다");
return "error/40x"; // 리다이렉트 하면 데이터 사라지니까 하면 안됨
}
// 가방에 담기
request.setAttribute("board", board);
return "board/updateForm";
}
@PostMapping("/board/{id}/update")
public String update(@PathVariable int id, BoardRequest.UpdateDTO requestDTO) {
// System.out.println(requestDTO); 정보 받기 확인
// 인증 체크하기
User sessionUser = (User) session.getAttribute("sessionUser");
// 권한 체크하기
Board board = boardRepository.findById(id);
if (board.getUserId() != sessionUser.getId()) {
return "error/403";
}
// update board_tb set title =?, content =?, where id =?
boardRepository.update(requestDTO, id);
return "redirect:/board/" + id; // 수정한 게시글로 돌아가기
}
@PostMapping("/board/{id}/delete") // body데이터가 없어서 유효성 검사 안해도 됨
public String delete(@PathVariable int id) {
// 인증 검사하기
User sessionUser = (User) session.getAttribute("sessionUser");
// 권한 검사하기
Board board = boardRepository.findById(id);
if (board.getUserId() != sessionUser.getId()) {
return "error/403";
}
boardRepository.deleteById(id);
return "redirect:/";
}
}
5. 테스트해보기

- “/user/updateForm”으로 요청 시 “loginForm”으로 리다이렉션 됨
- “/board/1”로 요청 시 “loginForm”으로 리다이렉션 됨

- 핵심 : 앞에서 막는 것
뒤에서 많으면 코드를 중복해서 적어야 함
- 현재 상황
안 막은 main페이지를 제외한 /board관련 아무것도 안들어가짐
- 주소 잘 짓는 방법
인증이 필요한 페이지 앞에 특정 키워드를 적음
@Getmappin("/api/user/updateForm") -> /api/**로 끝남
6. SecurityConfig에 ignore() 만들기
- 인증과 상관없이 열어야 하는 주소
- 정적 파일만 security filter에서 제외시키기
- 보통은 잠글 주소를 적어주는 것이 맞음
- 처음부터 주소 설계를 잘해야 함
package shop.mtcoding.blog._core.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.RegexRequestMatcher;
@Configuration// 메모리에 띄우기 위한 문법
public class SecurityConfig {
// 인증과 상관없이 열어야 하는 주소
// 주소 설계를 잘해야 함
@Bean // IoC에 뜸
public WebSecurityCustomizer ignore(){ // 정적 파일만 security filter에서 제외시키기
return w -> w.ignoring().requestMatchers("/board/**", /static/**", "/h2-console/**");
}
@Bean // IoC에 뜸
SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.csrf(c->c.disable());
// 주소로 필터링 : 인증이 필요한 페이지를 주소로 구분
http.authorizeHttpRequests(a -> {
a.requestMatchers(RegexRequestMatcher.regexMatcher("/board/\\d+")).permitAll()
.requestMatchers("/user/**", "/board/**").authenticated() // 인증이 필요한 페이지
.anyRequest().permitAll(); // 인증이 필요없는 페이지
});
// 기본 주소를 우리가 만든 페이지로 변경함
http.formLogin(f -> {
// 시큐리티가 들고있는 페이지를 사용할 것
f.loginPage("/loginForm").loginProcessingUrl("/login").defaultSuccessUrl("/")
.failureUrl("/loginForm"); // 실패하면 이동
});
return http.build(); // 코드의 변경이 없으면 부모 이름(추상적)으로 리턴할 필요 없음
}
}
Share article