[Spring Blog]10. 시큐리티 로그인 구현

Mar 15, 2024
[Spring Blog]10. 시큐리티 로그인 구현
 
SecurityConfig 수정하기
package shop.mtcoding.blog._core.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.SecurityFilterChain; @Configuration // 컴퍼넌트 스캔 public class SecurityConfig { @Bean public BCryptPasswordEncoder encoder(){ return new BCryptPasswordEncoder(); } @Bean public WebSecurityCustomizer ignore(){ return w -> w.ignoring().requestMatchers("/board/*", "/static/**", "/h2-console/**"); } @Bean SecurityFilterChain configure(HttpSecurity http) throws Exception { http.csrf(c -> c.disable()); http.authorizeHttpRequests(a -> { a.requestMatchers("/user/updateForm", "/board/**").authenticated() .anyRequest().permitAll(); }); http.formLogin(f -> { f.loginPage("/loginForm").loginProcessingUrl("/login").defaultSuccessUrl("/").failureUrl("/loginForm"); }); return http.build(); } }
 
username 조회 메서드 만들기
public User findByUsername(String username) { Query query = em.createNativeQuery("select * from user_tb where username=?", User.class); query.setParameter(1, username); try { User user = (User) query.getSingleResult(); return user; }catch (Exception e){ return null; } }
 
MyLoginUser객체 만들기
package shop.mtcoding.blog._core.config.security; import lombok.RequiredArgsConstructor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import shop.mtcoding.blog.user.User; import java.util.Collection; // 세션에 저장되는 오브젝트 @RequiredArgsConstructor public class MyLoginUser implements UserDetails { private final User user; @Override public String getPassword() { return user.getPassword(); } @Override public String getUsername() { return user.getUsername(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return null; } }
 
MyLoginService 구현하기
package shop.mtcoding.blog._core.config.security; import lombok.RequiredArgsConstructor; 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; import shop.mtcoding.blog.user.User; import shop.mtcoding.blog.user.UserRepository; // POST, /login, x-www-form-urlencoded, 키값이 username, password @RequiredArgsConstructor @Service public class MyLoginService implements UserDetailsService { private final UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { System.out.println("loadUserByUsername : "+username); User user = userRepository.findByUsername(username); if(user == null){ System.out.println("user는 null"); return null; }else{ System.out.println("user를 찾았어요"); return new MyLoginUser(user); // SecurityContextHolder 저장 } } }
 
EncodeTest 작성 후 해쉬값 확인
notion image
 
해쉬값 data.sql에 붙여넣기
notion image
 
SecurityConfig
@Bean public BCryptPasswordEncoder encoder(){ return new BCryptPasswordEncoder(); }
 
로그인 테스트 해보기
BoardController
@GetMapping("/") public String index(HttpServletRequest request, @AuthenticationPrincipal MyLoginUser myLoginUser) { System.out.println("로그인 되었나? : "+myLoginUser.getUsername()); List<Board> boardList = boardRepository.findAll(); request.setAttribute("boardList", boardList); return "index"; }
 
로그인 했을 때 확인 가능
notion image
 
1. 사용자가 로그인 요청을 보냅니다. 2. 스프링 시큐리티는 이 요청을 가로채어 사용자가 제공한 인증 정보(예: 사용자 이름과 비밀번호)를 검증합니다. 3. 인증이 성공하면, 스프링 시큐리티는 보안 컨텍스트에 사용자의 정보를 저장하고, 인증된 사용자로써의 권한을 부여합니다. 4. 이 정보는 주로 세션에 저장되어 후속 요청에서 사용됩니다. 스프링 시큐리티는 세션에 저장된 인증 정보를 이용해 요청이 들어올 때마다 사용자를 식별하고 권한을 확인합니다. 5. 새로운 요청이 들어오면, 스프링 시큐리티는 이 요청에 대한 인증 헤더, 쿠키 또는 다른 인증 수단을 확인하여 보안 컨텍스트를 복원하고, 사용자를 인증합니다. 6. 만약 인증에 실패하면, 해당 요청은 보호된 자원에 접근할 수 없게 됩니다.

머스태치에서 사용가능한 세션 정보 저장해두기

이유는 ~~~~~~~~~~~~~~~~~~~~~~~~ 세션 - > SPRING_SECURITY_CONTEXT -> SecurityContext 객체 -> Authentication -> MyLoginUser(UserDetails) -> User
 
기존 컨트롤러 인증 부분 수정하기
UserController
@GetMapping("/user/updateForm") public String updateForm(HttpServletRequest request, @AuthenticationPrincipal MyLoginUser myLoginUser) { User user = userRepository.findByUsername(myLoginUser.getUsername()); request.setAttribute("user", user); return "user/updateForm"; }
 
인증체크부분 삭제
notion image
notion image
notion image
package shop.mtcoding.blog.board; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import org.springframework.security.core.Authentication; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.context.SecurityContext; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import shop.mtcoding.blog._core.config.security.MyLoginUser; import shop.mtcoding.blog.user.User; import shop.mtcoding.blog.user.UserRepository; import shop.mtcoding.blog.user.UserRequest; import java.util.HashMap; import java.util.List; @RequiredArgsConstructor @Controller public class BoardController { private final HttpSession session; private final BoardRepository boardRepository; // ?title=제목1&content=내용1 // title=제목1&content=내용1 @PostMapping("/board/{id}/update") public String update(@PathVariable int id, BoardRequest.UpdateDTO requestDTO, @AuthenticationPrincipal MyLoginUser myLoginUser){ // 권한 체크 Board board = boardRepository.findById(id); if (board.getUserId() != myLoginUser.getUser().getId()) { return "error/403"; } // 핵심 로직 // update board_tb set title = ?, content = ? where id = ?; boardRepository.update(requestDTO, id); return "redirect:/board/"+id; } @GetMapping("/board/{id}/updateForm") public String updateForm(@PathVariable int id, HttpServletRequest request, @AuthenticationPrincipal MyLoginUser myLoginUser){ // 모델 위임 (id로 board를 조회) Board board = boardRepository.findById(id); // 권한 체크 if (board.getUserId() != myLoginUser.getUser().getId()) { return "error/403"; } // 가방에 담기 request.setAttribute("board", board); return "board/updateForm"; } @PostMapping("/board/{id}/delete") public String delete(@PathVariable int id, HttpServletRequest request, @AuthenticationPrincipal MyLoginUser myLoginUser) { // 2. 권한 없으면 나가 Board board = boardRepository.findById(id); if (board.getUserId() != myLoginUser.getUser().getId()) { request.setAttribute("status", 403); request.setAttribute("msg", "게시글을 삭제할 권한이 없습니다"); return "error/40x"; } boardRepository.deleteById(id); return "redirect:/"; } @PostMapping("/board/save") public String save(BoardRequest.SaveDTO requestDTO, HttpServletRequest request, @AuthenticationPrincipal MyLoginUser myLoginUser) { // 2. 바디 데이터 확인 및 유효성 검사 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, myLoginUser.getUser().getId()); return "redirect:/"; } @GetMapping("/") public String index(HttpServletRequest request) { List<Board> boardList = boardRepository.findAll(); request.setAttribute("boardList", boardList); return "index"; } // /board/saveForm 요청(Get)이 온다 @GetMapping("/board/saveForm") public String saveForm() { return "board/saveForm"; } @GetMapping("/board/{id}") public String detail(@PathVariable int id, HttpServletRequest request, @AuthenticationPrincipal MyLoginUser myLoginUser) { // 1. 모델 진입 - 상세보기 데이터 가져오기 BoardResponse.DetailDTO responseDTO = boardRepository.findByIdWithUser(id); boolean pageOwner; if (myLoginUser == null) { pageOwner = false; } else { int 게시글작성자번호 = responseDTO.getUserId(); int 로그인한사람의번호 = myLoginUser.getUser().getId(); pageOwner = 게시글작성자번호 == 로그인한사람의번호; } request.setAttribute("board", responseDTO); request.setAttribute("pageOwner", pageOwner); return "board/detail"; } }
 
회원가입 패스워드 인코딩
private final BCryptPasswordEncoder passwordEncoder; @PostMapping("/join") public String join(UserRequest.JoinDTO requestDTO){ System.out.println(requestDTO); String rawPassword = requestDTO.getPassword(); String endPassword = passwordEncoder.encode(rawPassword); userRepository.save(requestDTO); // 모델에 위임하기 return "redirect:/loginForm"; }
 
Share article

Essential IT