Spring Security 프로젝트 설정 4 - Authentication Service와 Controller
✒️ 2025-05-28 14:22 내용 수정
-
가이드 영상 : Amigoscode's Spring Boot 3 + Spring Security 6 - JWT Authentication and Authorisation
-
Spring Security의 가이드 영상을 찾아보던 중 위 영상이 도움이 되어 영상 내용을 직접 따라하며 내용을 정리하였다.
- 이전에 Spring Security 5.x 버전 영상을 참고했다가 Spring Security Config에서 많은 부분이 달라져서 Spring Security 6.x 버전 영상을 다시 찾아 정리했다.
-
SpringSecurity 프로젝트 설정 목록
- Spring Security 기본 사용자 추가 및 테스트
- Spring Security 프로젝트 설정 1 - DB연결과 JPA 설정
- Spring Security 프로젝트 설정 2 - JwtService와 Filter 설정
- Spring Security 프로젝트 설정 3 - Security Config
- Spring Security 프로젝트 설정 4 - Authentication Service와 Controller
- Spring Security 프로젝트 설정 5 - Security CORS 설정
- Spring Security 프로젝트 설정 6 - JWT Refresh Token 생성 및 저장
- Spring Security 프로젝트 설정 7 - JWT Refresh Token 재발급
- Spring Security 프로젝트 설정 8 - JWT 클라이언트 저장
- Spring Security 프로젝트 설정 9 - JWT 로그아웃
- Spring Security 프로젝트 설정 10 - 권한 설정
흐름

인증 관련 클래스
auth패키지를 생성 후AuthenticationResponse클래스를 생성한다.- 해당 클래스는 인증 응답으로 사용할 클래스로, 필드에 토큰을 가지고 있다.
package com.example.security.auth;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AuthenticationResponse {
private String token;
}
- 같은 패키지에
UserEntity와 대응되는 DTO 역할을 할RegisterRequest클래스를 만든다.- 데이터 객체#DTO(Data Transform Object) 참고.
- 클라이언트로부터 오는 데이터를 담아 Entity로 변환할 때 사용할 DTO 클래스다.
package com.example.security.auth;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RegisterRequest {
private String firstname;
private String lastname;
private String email;
private String password;
}
- 같은 패키지에
AuthenticationRequest클래스를 생성한다.- 인증 요청이 들어오면 인증에 필요한 이메일과 비밀번호를 담아 인증 과정에 사용할 예정이다.
package com.example.security.auth;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AuthenticationRequest {
private String email;
String password;
}
Service 추가
- 인증 과정을 처리할
AuthenticationService클래스를 생성한다.
package com.example.security.auth;
import com.example.security.config.JwtService;
import com.example.security.user.Role;
import com.example.security.user.User;
import com.example.security.user.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class AuthenticationService {
// DB와 상호작용하는 사용자 repo
private final UserRepository repository;
// 비밀번호 인코더
private final PasswordEncoder passwordEncoder;
// jwt 서비스
private final JwtService jwtService;
// 사용자 신원 확인
private final AuthenticationManager authenticationManager;
// 회원가입
public AuthenticationResponse register(RegisterRequest request) {
// 요청으로부터 온 데이터로 사용자 객체 생성
var user = User.builder()
.firstname(request.getFirstname())
.lastname(request.getLastname())
.email(request.getEmail())
.password(passwordEncoder.encode(request.getPassword()))
.role(Role.USER)
.build();
// 사용자 저장
repository.save(user);
// 토큰 생성 - 사용자 정보로 생성
var jwtToken = jwtService.generateToken(user);
// 인증 응답 객체 생성
return AuthenticationResponse.builder()
.token(jwtToken)
.build();
}
// 인증 확인
public AuthenticationResponse authenticate(AuthenticationRequest request) {
// 요청으로 들어온 사용자의 신원 확인
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.getEmail(),
request.getPassword()
)
);
// 위의 인증을 거친 사용자를 DB에 검색
var user = repository.findByEmail(request.getEmail())
.orElseThrow();
// 토큰 생성 - 사용자 정보로 생성
var jwtToken = jwtService.generateToken(user);
// 인증 응답 객체 생성
return AuthenticationResponse.builder()
.token(jwtToken)
.build();
}
}
Controller 설정
- 애플리케이션 작동을 확인할
DemoController을demo패키지와 함께 생성한다.
package com.example.security.auth.demo;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/demo-controller")
@RequiredArgsConstructor
public class DemoController {
@GetMapping
public ResponseEntity<String> sayHello() {
return ResponseEntity.ok("Hello from secured endpoint");
}
}
AuthenticationController를 생성한다.
package com.example.security.auth;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/auth")
@RequiredArgsConstructor
public class AuthenticationController {
private final AuthenticationService service;
// 회원가입
@PostMapping("/register")
public ResponseEntity<AuthenticationResponse> register (
@RequestBody RegisterRequest request
) {
return ResponseEntity.ok(service.register(request));
}
// 인증
@PostMapping("/authenticate")
public ResponseEntity<AuthenticationResponse> authenticate (
@RequestBody AuthenticationRequest request
) {
return ResponseEntity.ok(service.authenticate(request));
}
}
SecurityConfig 수정
SecurityConfig의SecurityFilterChain에서http.requestMatchers()을 수정한다.- Controller에 요청을 추가하면서 인증이 필요한 요청과 아닌 요청을 이 곳에서 수정하여 인증 조건을 걸어준다.
AuthenticationController의 모든 엔드 포인트(/api/v1/auth/)는 허용하도록 설정한다.
package com.example.security.config;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthFilter;
private final AuthenticationProvider authenticationProvider;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http)
throws Exception {
http
// session stateless로 인해 꺼 둠
.csrf((auth)->auth.disable())
.authorizeRequests()
.requestMatchers("/api/v1/auth/*") // 나열된 요청들은
.permitAll() // 모두 허용
.anyRequest() // 그 외의 모든 요청은
.authenticated() // 인증 필요
.and()
.sessionManagement((session)->
session // session state는 저장되면 안되므로 stateless로 설정
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authenticationProvider(authenticationProvider)
.addFilterBefore(jwtAuthFilter,
UsernamePasswordAuthenticationFilter.class); // jwt 필터 가동
return http.build();
}
}
테스트
- 웹 브라우저를 열고 postman 혹은 TalentAPI를 실행하여 먼저
DemoController에http://localhost:port/api/v1/demo-controllerGET 요청을 보낸다.- Authentication에 아무 내용을 작성하지 않아 인증되어 있지 않기에 보안이 적용된
/api/v1/demo-controller의 자원에 접근할 수 없어403응답이 돌아온다.
- Authentication에 아무 내용을 작성하지 않아 인증되어 있지 않기에 보안이 적용된
- 이번엔
http://localhost:port/api/v1/auth/authenticatePOST 요청을 임의의 이메일과 비밀번호로 전송한다.- DB에는 해당 이메일을 가진 사용자가 없는 상태이므로 사용자가 존재하지 않아
403응답이 돌아온다.
- DB에는 해당 이메일을 가진 사용자가 없는 상태이므로 사용자가 존재하지 않아
http://localhost:port/api/v1/auth/register로 새 사용자 정보를 입력하여 등록하고, 토큰이 생성되는지 확인한다.
- https://jwt.io/ 에서 토큰을 넣으면 디코딩된 결과에서 아까 입력한 사용자 id와 유효 기간을 확인할 수 있다.
- 인증이 제대로 작동하는지 확인하기 위해 사용자의 이메일은 그대로 두고 비밀번호를 일부러 다르게 입력하여
http://localhost:port/api/v1/auth/authenticatePOST 요청을 보내면403응답이 돌아온다.- 토큰이 생기고 검증 절차를 걸쳐 사용자의 이름을 추출한 후 DB에 있는 내용과 비교했을 때 비밀번호가 일치하지 않아
AuthenticationManager를 통한 검증 절차에 실패하여403응답이 생긴다.
- 토큰이 생기고 검증 절차를 걸쳐 사용자의 이름을 추출한 후 DB에 있는 내용과 비교했을 때 비밀번호가 일치하지 않아
- 마지막으로
http://localhost:port/api/v1/demo-controllerGET 요청에서 Authentication 항목에Bearer Token을 선택하고,http://localhost:port/api/v1/auth/authenticate으로 받은 Token을 넣은 뒤 요청을 전송하면200결과와 함께DemoController에서 설정했던endpoint결과가 뜬다.