dev.note/Web

[spring boot] spring security 6 로그인 서비스

CrazyK 2022. 12. 14. 17:41
TMI
나이가 들수록 영민함과 멀어지기에
이젠 기록을 해둬야 겠다

 

사전 Gradle 설정 사항(DB가 둘다 되는지 확인하고 싶었다)

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-security'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	runtimeOnly 'com.h2database:h2:2.1.210'
	runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.springframework.security:spring-security-test'
}

1. 설정 클래스에 다음의 내용을 넣어준다

@Configuration
@EnableWebSecurity
@AllArgsConstructor
public class SecurityConfig {
	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}

	@Bean
	public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
		return authenticationConfiguration.getAuthenticationManager();
	}
}

2. JPA 기반이므로 domain 클래스를 작성한다

@Entity @Getter @Setter
public class Member {
	@Id @GeneratedValue(strategy= GenerationType.IDENTITY)
	@Column(name="idx")
	private long idx;
	@Column(name="identity", unique = true) // 8자로 password와 맞추고 싶었다...
	private String identity;
	@Column(name="password")
	private String password;
	@Column(name="username")
	private String username;
}

3. spring security의 UserDetails를 구현하는 DTO를 만들어준다

참고로... 당연한 거지만 Entity를 DTO로 사용하려고 하지 마라

@Getter @Setter @Builder
public class MemberDTO implements UserDetails {
   private long idx;
   private String identity;
   private String password;
   private String username;
   private List<AuthorityDTO> authorities;

   @Override
   public Collection<AuthorityDTO> getAuthorities() {
      return authorities == null ? new ArrayList<>() : authorities;
   }

   @Override
   public boolean isAccountNonExpired() {
      return true;
   }

   @Override
   public boolean isAccountNonLocked() {
      return true;
   }

   @Override
   public boolean isCredentialsNonExpired() {
      return true;
   }

   @Override
   public boolean isEnabled() {
      return true;
   }
}

중요한건 아니지만 AuthorityDTO

@Getter @Setter
public class AuthorityDTO implements GrantedAuthority {
	private long idx;
	private String authority;
	private String authorityName;
}

4. JPA Repository 생성

public interface MemberRepository extends JpaRepository<Member, Long> {
	Optional<Member> findByIdentity(String identity);
}

5. 서비스 구현

@AllArgsConstructor
@Service
public class LoginService implements UserDetailsService {
	private MemberRepository memberRepository;

	@Override
	public UserDetails loadUserByUsername(String identity) throws UsernameNotFoundException {
		Member member = memberRepository.findByIdentity(identity)
			.orElseThrow(() -> new UsernameNotFoundException(identity));
		// 기왕이면 Object Mapper 를 써라. 지금은 설정이 귀찮다..
		return MemberDTO.builder()
			.idx(member.getIdx())
			.identity(member.getIdentity())
			.password(member.getPassword())
			.username(member.getUsername())
			.build();
	}
}

6. 테스트(사전에 필요한 데이터는 넣어주고)

@SpringBootTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class LoginTest {
	@Autowired
	LoginService loginService;
	@Autowired
	private AuthenticationManager authenticationManager;

	@Test
	void loginTest() {
		authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("identity", "password"));
	}
}