스프링 시큐리티와 JPA를 활용한 Basic Auth
HTTP 기본 인증은 HTTP 헤더를 사용하는 인증 방법으로, 간단하게 username과 password를 사용해서 인증을 진행하는 방법입니다.
이때 Base64를 통해 간단한 인코딩을 하여 보내는 것이 일반적입니다.
이렇게 인코딩 된 인증 정보를 HTTP 헤더의 Authorization에 실어서 전송을 합니다.
좀 더 자세한 정보는 아래의 페이지를 참고해 주세요
https://engineerinsight.tistory.com/69
[HTTP] HTTP 기본 인증 (Basic Authentication): 개념과 사용 방법
💋 인증(Authentication) 인증은 사용자의 신원을 확인하는 과정이다. 누군지? 를 보는 것이다. 스프링에서 인증은 보안과 직결되기 때문에 매우 중요한 부분이며, 아래와 같은 절차를 따른다. 1. 사
engineerinsight.tistory.com
이제 JPA를 통해 DB에 저장된 계정과 매칭하여 기본 인증을 수행하는 코드를 보겠습니다.
처음 만들 때는 간단할 거라고 생각했었는데, JDK 17, Springboot 3.x 버전부터 WebSecurityConfigurerAdapter가 없어진 상태로 만들려고 하니 참고할 만한 예제가 생각보다 없더라고요.
그래서 나중에 또 사용할지 모르니 정리 차원에서 적어보려고 합니다.
1. 기본 구성
우선 필요한 클래스 및 인터페이스는 아래와 같습니다.
참고로 시큐리티 관련 설정 파일은 메인 클래스와 같은 패키지 안에 포함되어 있어야 합니다.
그렇지 않으면 bean을 인식하지 못해서 제대로 적용이 되지 않으니깐 유의해주세요.
- LoginService: 레포지토리를 통해 로그인 계정을 찾는다
- NewMember: 계정 엔티티
- SecurityConfig: 스프링 시큐리티 관련 설정
- SecurityService: 스프링 시큐리티에서 사용자의 정보를 가져오는 인터페이스
- UserRepository: 계정 정보를 찾기 위해 DB에 직접 접근
2. 의존성 추가
JPA와 스프링 시큐리티를 추가해 주세요
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
3. 계정 엔티티와 레포지토리 (JPA 설정)
우선 NewMember라는 클래스로 계정 엔티티를 만듭니다.
// NewMember.java
@Entity(name = "NEWMEMBER") //테이블 이름
@Data
public class NewMember {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
private String userId;
private String pw;
private String roles;
}
위와 같이 엔티티를 만들면 그에 따른 테이블은 옆에 그림처럼 생성되어 있어야 제대로 매칭되어 진행이 됩니다.
그리고 계정을 찾는 레포지토리를 만들어줍니다.
// UserRepository.java
@Repository
public interface MemberRepository extends JpaRepository<NewMember, Long> {
Optional<NewMember> findByUserId(String userId);
}
마지막으로 레포지토리를 통해 계정 정보를 리턴하는 LoginService를 만듭니다.
// LoginService.java
@Service
@RequiredArgsConstructor
public class LoginService {
private final UserRepository userRepository;
public Optional<NewMember> findOne(String userId){
return userRepository.findByUserId(userId);
}
}
여기까지가 DB의 계정을 갖고 와서 비교하기 위한 JPA 관련 작업이었습니다.
4. 스프링 시큐리티 설정
BCrypt 방식으로 암호화를 적용하기 위해 BCryptPasswordEncoder를 사용하여 암호화 메서드를 생성합니다.
그리고 filterChain을 통해 에러 페이지를 제외한 모든 페이지에서는 기본 인증이 필요하도록 설정합니다.
// SecurityConfig.java
@Configuration
@RequiredArgsConstructor
public class SecurityConfig{
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
return http
.authorizeRequests(authorize -> {
authorize.requestMatchers("/error/**").permitAll(); // error 페이지는 인증 불필요
authorize.anyRequest().authenticated(); // 나머지는 모두 인증 필요
})
.httpBasic(Customizer.withDefaults()) // 기본 인증 사용
.build();
}
}
마지막으로 SecurityService에서는 loadUserByUsername을 오버라이드하여 DB에서 계정 정보를 불러와서 User 객체에 넣고 리턴합니다.
여기서 User는 import org.springframework.security.core.userdetails.User; 의 User입니다.
loadUserByUsername에서 리턴되는 UserDetails 클래스는 스프링 시큐리티에서 사용자의 정보를 담는 인터페이스입니다.
SecurityService가 구현하는 UserDetailsService는 스프링 시큐리티에서 레포지토리 역할을 하는 것으로, 스프링 시큐리티에서 사용자의 정보를 가져오는 인터페이스입니다.
// SecurityService.java
@Service
@RequiredArgsConstructor
@Slf4j
public class SecurityService implements UserDetailsService {
private final LoginService loginService;
private final PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String insertedUserId) throws UsernameNotFoundException {
Optional<NewMember> findOne = loginService.findOne(insertedUserId);
log.info("findOne : {}", findOne);
NewMember newMember = findOne.orElseThrow(() -> new UsernameNotFoundException("등록되지 않은 ID"));
return User.builder()
.username(newMember.getUserId())
.password(newMember.getPw())
.roles(newMember.getRoles())
.build();
}
public NewMember createUser(String userId, String pw, PasswordEncoder passwordEncoder) {
NewMember newMember = new NewMember();
newMember.setId(null);
newMember.setUserId(userId);
newMember.setPw(passwordEncoder.encode(pw));
newMember.setRoles("USER");
return newMember;
}
}
createUser는 현재 사용하지는 않지만 테스트용 계정 생성 메서드로 만들어놓았습니다.
계정 생성할 때 위에서 만들어 놓은 암호화 메서드를 사용합니다.
회원가입 부분은 다른 페이지에서 구현을 하고 스프링시큐리트는 그것을 검증하는 부분으로만 사용합니다.
여기서 주의할 점은 BCryptPasswordEncoder를 사용하여 비밀번호를 저장하면 아래와 같은 형식으로 저장된다는 것입니다.
이것은 test라는 ID에 비밀번호로 admin123으로 등록을 해놓은 상태입니다.
Bcrypt 복호화 사이트에서 체크를 해보면 매칭되는 것을 볼 수 있습니다.
Bcrypt는 솔트값을 더해서 값을 생성하기 때문에 그때그때 바뀔 수 있다는 것을 알아두세요!
그리고 여기서 하나 더 말씀드릴 MSSQL 관련 저의 실수는 컬럼을 생성할 때 varChar가 아닌 nvarchar로 생성했더니 데이터 검증이 제대로 이뤄지지 않았습니다.
문자열 형식의 차이 때문인 거 같았습니다.
참고: https://jeongkyun-it.tistory.com/187
[Database] 자료형 Varchar와 nVarchar의 차이는? (개념/ 예제)
서론 이번 글에서는 database에서의 자료형 varchar와 nvarchar의 차이점에 대해 간단히 알아보려한다. VARCHAR vs NVARCHAR (n)varchar란? 문자열을 저장할 때 사용하는 자료형이다. 표현 범위로는 지정할 수
jeongkyun-it.tistory.com
아무튼 최종적으로 이렇게 하면 JPA를 활용하여 DB의 계정을 활용한 HTTP Basic Auth가 완성되게 됩니다.
브라우저 창으로 계정 정보를 묻고 입력하면 결과가 잘 나옵니다.
결과는 제가 임의로 만든 요청을 하나 보내준 것입니다.
포스트맨으로 결과가 잘 나옵니다.
감사합니다.
참고
https://dnl1029.tistory.com/35
spring security로 ID/PASSWORD 로그인 구현하기
본 글에서는 spring security를 통해, restapi로 사용자가 ID와 Password를 입력하면 DB에 저장된 값과 비교해 인증을 하는 간단한 서비스를 개발해보려합니다. JDK 17 / springboot는 3.0.6 버전으로 작성되었습
dnl1029.tistory.com
[Spring Security] Spring Boot를 이용한 Database Authentication
이번엔 spring boot app에 Database Authentication을 적용하는 방법을 알아본다. (이전에 생성한 spring boot app은 여기에서 확인할 수 있다.) 1. Database Authentication > SecurityConfigur
velog.io