728x90
- 스프링 시큐리티 적용 방법
- 이제 build.gradle Spring Security 의존성을 추가해준다.
- 프로젝트를 실행하니 처음 보는 security password가 콘솔에 찍히게 된다.
implementation 'org.springframework.boot:spring-boot-starter-security'
- 임의로 localhost:8080/hello에 접근을 해보았다.
- 302 상태가 반환되며 localhost:8080/login으로 리다이렉트가 되었다.
- 위 로그인 화면은 Spring Security가 기본으로 제공해주는 화면이다.
- Username에 user, Password에 콘솔에 찍힌 문자열을 입력하면 정상적으로 localhost:8080/hello로 넘어가게 된다.
- 참고로 localhost:8080/logout을 요청하면 로그아웃 페이지가 나온다.
그리고 application.properties에 username과 password를 지정할 수 있다.
spring.security.user.name=user
spring.security.user.password=(비밀번호)
이렇듯 Spring Security 의존성만을 추가했음에도 불구하고 기본적인 로그인 기능이 제공되었다.
- 참고로 Spring Security이 기본적으로 제공하는 로그인은 Session을 이용한 로그인이다.
- 로그인한 브라우저의 요청 헤더 JSESSIONID값을 복사해 로그인하지 않은 브라우저에 JSESSIONID값을 넣어 주면 로그인이 된 상태가 된다. 반대로 로그인이 된 브라우저에서 JSESSIONID값을 지우면 로그인이 풀리게 된다.
실제 설정 구현 방법 예제
package com.example.settingtest.config;
import com.example.settingtest.service.MemberService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final MemberService memberService;
//http 요청에 대한 보안 설정
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/members/login") //로그인 페이지 URL
.defaultSuccessUrl("/") //로그인 성공 시 이동할 URL
.usernameParameter("email") //로그인 시 사용할 파라미터 네임을 email로 설정
.failureUrl("/members/login/error") //로그인 실패 시 이동할 URL 설정
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/members/logout")) //로그아웃 URL 설정
.logoutSuccessUrl("/") //로그아웃 성공 시 이동할 URL 설정
;
http
.authorizeRequests() //시큐리티 처리에 httpServletRequest를 이용함을 의미
.mvcMatchers("/", "/members/**", "/item/**", "/api/v1/app/**").permitAll()
.mvcMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.csrf().disable()
;
//인증되지 않은 사용자가 리소스에 접근하였을 떄 수행되는 핸들러를 등록
http.exceptionHandling()
.authenticationEntryPoint(new CustomAuthenticationEntryPoint())
;
}
//BCryptPasswordEncoder의 해시 함수를 이용하여 비밀번호를 암호화하여 저장
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(memberService)
.passwordEncoder(passwordEncoder());
}
//static 디렉터리의 하위 파일은 인증을 무시하도록 설정(이미지, css 등)
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/img/**");
}
}
- @EnableWebSecurity - WebSecurityConfigurerAdapter를 상속받는 클래스에 @EnableWebSecurity 어노테이션을 선언하면 SpringSecurityFilterChain이 자동으로 포함된다. WebSecurityConfigurerAdapter를 상속받아서 메소드 오버라이딩을 통해 보안 설정을 커스터마이징 할 수 있다.
- .mvcMatchers 관련
- permitAll() - 해당 경로에 모든 사용자가 인증없이 접근할 수 있도록 설정함
- hasRole(“ADMIN”) - 해당 계정이 ADMIN role 일 경우에만 접근이 가능하도록 설정
- anyRequest().authenticated() - 위에 경로들을 제외한 나머지 경로들은 모두 인증을 요구하도록 설정
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(memberService)
.passwordEncoder(passwordEncoder());
}
- Spring Security에서 인증은 AuthenticationManager를 통해 이루어지며 AuthenticationManagerBuilder가 AuthenticationManager를 생성한다. userDetailService를 구현하고 있는 객체로 memberService를 지정해주며, 비밀번호 암호화를 위해 passwordEncoder를 지정해준다.
(loadUserByUsername 구현)
@RequiredArgsConstructor
@Service
public class MemberService implements UserDetailsService {
private final MemberJpaRepository memberJpaRepository;
...
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Member member = memberJpaRepository.findByEmail(email);
if(member == null){
throw new UsernameNotFoundException(email);
}
return User.builder()
.username(member.getEmail())
.password(member.getPassword())
.roles(member.getRole().toString())
.build();
}
}
- MemberService가 UserDetailsService를 구현한다.
- UserDetailsService 인터페이스의 loadUserByUsername() 메소드를 오버라이딩합니다. 로그인할 유저의 email을 파라미터로 전달받는다.
- UserDetail을 구현하고 있는 User 객체를 반환해줍니다. User 객체를 생성하기 위해서 생성자로 회원의 이메일, 비밀번호, role을 파라미터로 넘긴다.
728x90
- 회원 가입 test
@SpringBootTest
@Transactional
class MemberServiceTest {
@Autowired
MemberService memberService;
@Autowired
PasswordEncoder passwordEncoder;
public Member createMember(){
MemberFormDto memberFormDto = new MemberFormDto();
memberFormDto.setEmail("test@email.com");
memberFormDto.setName("홍길동");
memberFormDto.setAddress("서울시 마포구 합정동");
memberFormDto.setPassword("1234");
return Member.createMember(memberFormDto, passwordEncoder);
}
@Test
@DisplayName("회원가입 테스트")
public void saveMemberTest(){
Member member = createMember();
Member savedMember = memberService.join(member);
assertEquals(member.getEmail(), savedMember.getEmail());
assertEquals(member.getName(), savedMember.getName());
assertEquals(member.getAddress(), savedMember.getAddress());
assertEquals(member.getPassword(), savedMember.getPassword());
assertEquals(member.getRole(), savedMember.getRole());
}
@Test
@DisplayName("중복 회원 가입 테스트")
public void saveDuplicateMemberTest(){
Member member1 = createMember();
Member member2 = createMember();
memberService.join(member1);
Throwable e = assertThrows(IllegalStateException.class, () -> {
memberService.join(member2);});
assertEquals("이미 가입된 회원입니다.", e.getMessage());
}
}
- 회원 가입 단위테스트 성공화면
- 로그인 test
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
@SpringBootTest
@AutoConfigureMockMvc
@Transactional
class MemberControllerTest {
@Autowired
private MemberService memberService;
@Autowired
private MockMvc mockMvc;
@Autowired
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.join(member);
}
@Test
@DisplayName("로그인 성공 테스트")
public void loginSuccessTest() throws Exception{
String email = "test@email.com";
String password = "1234";
this.createMember(email, password);
mockMvc.perform(formLogin().userParameter("email")
.loginProcessingUrl("/members/login")
.user(email).password(password))
.andExpect(SecurityMockMvcResultMatchers.authenticated());
}
@Test
@DisplayName("로그인 실패 테스트")
public void loginFailTest() throws Exception{
String email = "test@email.com";
String password = "1234";
this.createMember(email, password);
mockMvc.perform(formLogin().userParameter("email")
.loginProcessingUrl("/members/login")
.user(email).password("12345"))
.andExpect(SecurityMockMvcResultMatchers.unauthenticated());
}
}
- 로그인 단위 테스트 성공 화면
참고 : 스프링 부트 쇼핑몰 프로젝트 with JPA - 변구훈 저
728x90
'Spring > 초기 setting' 카테고리의 다른 글
8) API Response(@ControllerAdvice) 설정 (0) | 2023.04.20 |
---|---|
mapStruct 소개 및 활용 (0) | 2023.04.10 |
5) 스프링 시큐리티(spring-security) 개요 (0) | 2023.04.05 |
4) Querydsl 설정 (2) | 2023.03.20 |
3) JPA 다중 스키마 설정 (0) | 2023.03.19 |