Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- Algorithm
- Serialization
- 이것이 자바다
- R
- datastructure
- 혼자 공부하는 C언어
- JSON
- s
- 윤성우 열혈자료구조
- insertion sort
- coding test
- Selection Sorting
- stream
- list 컬렉션
- 메모리구조
- 알기쉬운 알고리즘
- buffer
- C 언어 코딩 도장
- 이스케이프 문자
- C programming
- Graph
- 윤성우의 열혈 자료구조
- Stack
Archives
- Today
- Total
Engineering Note
[Server] Spring Boot3 Security 적용 방법 본문
시큐 리티 의존성 추가
maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
gradle
// https://mvnrepository.com/artifact/org.springframework.security/spring-security-core
implementation group: 'org.springframework.security', name: 'spring-security-core', version: '6.5.3'
package com.shop.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.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf
.ignoringRequestMatchers("/h2-console/**") // 특정 경로만 CSRF 비활성화, Spring MVC CSRF 적용해야함, REST API 개발시 JWT 사용, CSRF 비활성화
)
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/", "/members/new","/members/login", "/members/logout", "/css/**", "/js/**").permitAll() // 인증 없이 접근 허용
.requestMatchers("/h2-console/**").permitAll() // H2 콘솔 접근 허용
.anyRequest().authenticated() // 나머지는 인증 필요
)
.formLogin(form -> form
.loginPage("/members/login") // 커스텀 로그인 페이지 URL
.defaultSuccessUrl("/") // 로그인 성공 후 이동
.usernameParameter("email")
.failureUrl("/members/login/error")
)
.logout(logout -> logout
.logoutUrl("/members/logout")
.logoutSuccessUrl("/")
);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
로그인 기능을 위한 Serivce 코드 구현
로그인 요청에 대한 처리는 UserDetailsService 인터페이스를 구현한 loadUserByUsername() 메서드에서 처리한다. saveMember()와 validateDuplicateMember() 메서드는 회원가입시에 사용하는 코드이다.
package com.shop.service;
import com.shop.entity.Member;
import com.shop.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.userdetails.User;
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 org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
@RequiredArgsConstructor
@Slf4j
public class MemberService implements UserDetailsService {
private final MemberRepository memberRepository;
public Member saveMember(Member member){
validateDuplicateMember(member);
return memberRepository.save(member);
}
private void validateDuplicateMember(Member member){
Member findMember = memberRepository.findByEmail(member.getEmail());
if(findMember != null){
throw new IllegalStateException("이미 가입된 회원입니다.");
}
}
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
log.info("로그인 시도 =====> email: {}", email);
Member member = memberRepository.findByEmail(email);
if(member == null){
log.info("===== 회원 데이터 없음 =====");
throw new UsernameNotFoundException(email);
}
log.info("===== 로그인 회원 조회 성공 =====");
return User.builder()
.username(member.getEmail())
.password(member.getPassword())
.roles(member.getRole().toString())
.build();
}
}
적용한 기능을 테스트 하는 방법
테스트를 위한 의존성 추가
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
테스트 코드
package com.shop.controller;
import com.shop.dto.MemberFormDto;
import com.shop.entity.Member;
import com.shop.service.MemberService;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;
import static org.junit.jupiter.api.Assertions.*;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
@Transactional
@TestPropertySource(locations = "classpath:application-test.yml")
class MemberControllerTest {
@Autowired
private MemberService memberService;
@Autowired
private MockMvc mockMvc;
@Autowired
private PasswordEncoder passwordEncoder;
public Member createMember(String email, String password){
MemberFormDto memberFormDto = new MemberFormDto();
memberFormDto.setEmail(email);
memberFormDto.setName("홍길동");
memberFormDto.setAddress("서울시 마포구 합정동");
memberFormDto.setPassword(password);
Member member = Member.createMember(memberFormDto, passwordEncoder);
return memberService.saveMember(member);
}
@Test
@DisplayName("로그인 성공 테스트")
public void loginSuccessTest() throws Exception{
//회원가입
String email = "test@email.com";
String password = "12345678";
this.createMember(email, password);
//Security 로그인 인증
mockMvc.perform(formLogin()
.userParameter("email")
.user(email)
.password(password)
.loginProcessingUrl("/members/login"))
.andExpect(authenticated())
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/"));
}
}
'Server' 카테고리의 다른 글
[Server] 리버스 프록시(Reverse Proxy)란? (0) | 2025.09.20 |
---|---|
[Server] Pagination 개념과 Pagination 에서 사용되는 용어, 페이지 시작 번호계산 방법 (0) | 2025.09.17 |
[Sever] 스프링 부트 쓰레드, DB 커넥션 (0) | 2025.09.04 |
[Server] JPA에서 페이징 처리 (0) | 2025.09.02 |
[Kafka] Kafka를 사용하는 이유(비동기는 기본, 느슨한 결합으로 확장성있는 아키텍처 지원) (1) | 2025.08.27 |
Comments