Spring Security 프로젝트 설정 4 - Authentication Service와 Controller

✒️ 2025-05-28 14:22 내용 수정



흐름

spring_security_authentication_flow.png


인증 관련 클래스

  1. 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;  
}
  1. 같은 패키지에 User Entity와 대응되는 DTO 역할을 할 RegisterRequest 클래스를 만든다.
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;  
}
  1. 같은 패키지에 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 추가

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 설정

  1. 애플리케이션 작동을 확인할 DemoControllerdemo 패키지와 함께 생성한다.
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");  
    }  
}
  1. 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 수정

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();  
    }  
}

테스트

  1. 웹 브라우저를 열고 postman 혹은 TalentAPI를 실행하여 먼저 DemoControllerhttp://localhost:port/api/v1/demo-controller GET 요청을 보낸다.
    • Authentication에 아무 내용을 작성하지 않아 인증되어 있지 않기에 보안이 적용된 /api/v1/demo-controller의 자원에 접근할 수 없어 403 응답이 돌아온다.

spring_security_controller 1.png

  1. 이번엔 http://localhost:port/api/v1/auth/authenticate POST 요청을 임의의 이메일과 비밀번호로 전송한다.
    • DB에는 해당 이메일을 가진 사용자가 없는 상태이므로 사용자가 존재하지 않아 403 응답이 돌아온다.

spring_security_controller 2.png

  1. http://localhost:port/api/v1/auth/register로 새 사용자 정보를 입력하여 등록하고, 토큰이 생성되는지 확인한다.

spring_security_controller 3.png

  1. 인증이 제대로 작동하는지 확인하기 위해 사용자의 이메일은 그대로 두고 비밀번호를 일부러 다르게 입력하여 http://localhost:port/api/v1/auth/authenticate POST 요청을 보내면 403 응답이 돌아온다.
    • 토큰이 생기고 검증 절차를 걸쳐 사용자의 이름을 추출한 후 DB에 있는 내용과 비교했을 때 비밀번호가 일치하지 않아 AuthenticationManager를 통한 검증 절차에 실패하여 403 응답이 생긴다.

spring_security_controller 5.png

  1. 마지막으로 http://localhost:port/api/v1/demo-controller GET 요청에서 Authentication 항목에 Bearer Token을 선택하고, http://localhost:port/api/v1/auth/authenticate으로 받은 Token을 넣은 뒤 요청을 전송하면 200 결과와 함께 DemoController에서 설정했던 endpoint 결과가 뜬다.

spring_security_controller 6.png