Spring Security
를 활용한 회원가입 기능 구현
스프링 시큐리티를 사용하기 위해 의존성을 추가.
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
예전에는 스프링 시큐리티 설정을 위해 WebSecurityConfigurerAdapter
를 extends 해서 configure 메소드를 오버라이딩 해서 구현했었다. 그런데 이제는 deprecated 되었기 때문에 다른 방식을 생각해야했다.
열심히 구글링해본 결과, SecurityFilterChain
를 아예 Bean
으로 등록해서 사용하는 방식을 추천했다.
(출처 : 스프링 공식 블로그)
위의 방식을 토대로 SecurityConfig
를 작성해보았다.
@Configuration
@EnableWebSecurity
@AllArgsConstructor
public class SecurityConfig {
@Autowired
MemberService memberService;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(new CustomAuthenticationEntryPoint());
// 인증되지 않은 사용자가 리소스에 접근했을 때 수행되는 핸들러 등록
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
일단은 인증되지 않은 사용자의 리소스에 대한 접근 처리를 우선적으로 하였다.
먼저, 각 멤버가 일반 유저인지, 관리자인지 구분할 수 있는 역할을 부여하기 위해 com.shop.constant
패키지에 Role.java
를 생성했다.
com.shop.constant.Role.java
package com.shop.constant;
public enum Role {
USER, ADMIN
}
회원 가입 화면으로부터 넘어오는 가입 정보를 담은 dto 생성
com.shop.dto.MemberFormDto.java
package com.shop.dto;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class MemberFormDto {
private String name;
private String email;
private String password;
private String address;
}
com.shop.entity.Member.java
package com.shop.entity;
import com.shop.constant.Role;
import com.shop.dto.MemberFormDto;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.persistence.*;
@Entity
@Table(name="member")
@Getter
@Setter
@ToString
public class Member { // 회원 정보 저장 엔티티
@Id
@Column(name="member_id")
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
@Column(unique = true)
private String email; // 회원은 이메일을 통해 유일하게 구분하므로
// unique 속성 지정
private String password;
private String address;
@Enumerated(EnumType.STRING) // enum의 순서가 바뀌지 않도록 하기위해
private Role role; // enumType을 String으로 지정
public static Member createMember(MemberFormDto memberFormDto,
PasswordEncoder passwordEncoder) {
Member member = new Member();
member.setName(memberFormDto.getName());
member.setEmail(memberFormDto.getEmail());
member.setAddress(memberFormDto.getAddress());
String password = passwordEncoder.encode(memberFormDto.getPassword());
// 스프링 시큐리티 설정 클래스에 등록한 BCryptPasswordEncoder Bean을
// 파라미터로 넘겨서 비밀번호 암호화
member.setPassword(password);
member.setRole(Role.ADMIN);
return member;
}
}
com.shop.repository.MemberRepository.java
package com.shop.repository;
import com.shop.entity.Member;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MemberRepository extends JpaRepository<Member, Long> {
Member findByEmail(String email);
}
com.shop.service.MemberService.java
package com.shop.service;
import com.shop.entity.Member;
import com.shop.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
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 javax.transaction.Transactional;
@Service
@Transactional // 로직을 처리하다가 에러가 발생하였다면, 변경된 데이터를 로직을 수행하기 이전 상태로 콜백
@RequiredArgsConstructor
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("이미 가입된 회원입니다.");
// 이미 가입된 회원인 경우 예외 처리
}
}
}
Constant : ItemSellStatus
, Role
Dto : MemberFormDto
Entity : Item
, Member
Repository : ItemRepository
, MemberRepository
Service : MemberService
MemberFormDto(Dto)
에서는 회원가입 화면으로부터 넘어오는 가입정보 저장Member(Entity)
에서는 회원정보를 저장MemberRepository(Repository)
는 쿼리 메소드를 통해 회원 조회MemberService(Service)
는 비즈니스 로직을 처리Spring Security
에서 인증/ 인가 실패에 따른 리다이렉트에 대해 학습했다.
1) 아직 회원가입 하지 않은 회원 : 인증(Authentication) 의 문제
2) 회원가입은 했으나 권한이 없는 회원 : 인가(Authorization) 의 문제
인증은 AuthenticationEntryPoint
인터페이스, 인가는 AccessDeniedHandler
인터페이스를 통해 세분화하여 처리할 수 있다는 것을 알게 되었다.