팀프로젝트<뉴스피드 만들기>(3일차)
2024. 5. 29. 23:11ㆍTIL
🕕
팀 프로젝트 (뉴스피드 만들기)
💡 프로젝트 기간: 2024-05-27~2024-06-03
프로젝트 진행과정
튜터 피드백 수용
확장성 위해 삭제시간 별도로 저장
제약조건을 @Valid 어노테이션 사용하지 않고 내부 로직으로 해결하기
검색 tag는 하나만 가능하게 제약 걸기
비밀번호 저장할때 암호화하기
로그인 부분 인증 구현하기
인증 부분 구현하기
@RestController
@RequestMapping("/auth")
class AuthenticationController(
private val authService: AuthService // 컨트롤러에서 서비스 연결
) {
@PostMapping("/register")
fun register(@RequestBody signupRequest: SignUpRequest): ResponseEntity.BodyBuilder {
authService.register(signupRequest)
return ResponseEntity.status(HttpStatus.CREATED)
}
@PostMapping("/login")
fun login(@RequestBody loginRequest: LoginRequest): ResponseEntity<String> {
val token = authService.login(loginRequest)
//authService에서 인증이 성공하면 토큰이 반환되므로 token 변수에 token정보가 담기고 인증이 실패하면 token 변수에 null이 담김
return if (token != null) {
ResponseEntity.ok(token) //이 부분에서 인증이 된 상태, 컨트롤러를 따로 만드는게 아닌 기존의 memberController에 추가하기?
} else {
ResponseEntity.status(401).body("인증 실패로 해당 토큰이 존재하지 않습니다")
}
}
}
@Service
class AuthService(
private val authenticationManager: AuthenticationManager, // spring security에서 제공하는 인터페이스(인증을 관리하고 수행)
private val jwtTokenProvider: JwtTokenProvider, // JWT토큰 만드는 클래스 주입
private val passwordEncoder: PasswordEncoder, // 비밀번호 암호화
private val memberSecurityRepository: MemberSecurityRepository, //리포지토리 연결
private val memberRepository: MemberRepository
) {
//이메일이랑 비밀번호 받아서 로그인 하는 함수
fun login(loginRequest: LoginRequest): String? {
val authentication: Authentication = authenticationManager.authenticate(//데이터베이스와의 비교는 Spring Security가 내부적으로 처리하며 그 설정은 SecurityConfig안에 이
UsernamePasswordAuthenticationToken(loginRequest.email, loginRequest.password)
) //authenticationManager.authenticate를 이용해서 사용자 인증을 시도하고 매개변수로 UsernamePasswordAuthenticationToken 객체를 생성해서 이메일이랑 비밀번호를 전달함
// 즉 authetication이라는 변수에 사용자의 인증 정보가 담겨져 있는 상태
// if(loginRequest.password==) 식으로 데이터베이스의 이메일 비밀번호와 일치하는지를 통한 것이 아닌 authenticationManager.authenticate()함수를 이용해서 인증을 함
if (authentication.isAuthenticated) { // 성공적으로 인증되었으면 true
return jwtTokenProvider.generateToken(loginRequest.email) // 성공적으로 인증되었다면 이메일을 기반으로 JWT토큰을 생성해서 반환함
}
return null //인증 실패했으면 null반환
}
fun register(signUpRequest: SignUpRequest){ //회원가입할때 사용자를 등록하는 함수
val memberSecurity = MemberSecurity(email = signUpRequest.email, password = passwordEncoder.encode(signUpRequest.password)) // membersecurity dto에서 입력받은 email이랑 암호화된 비밀번호를 해당 변수에 저장
memberSecurityRepository.save(memberSecurity) //데이터베이스에 암호화된 비밀번호랑 이메일을 저장함
}
}
이 클래스는 UserDetailService를 상속받기 때문에 요녀석이 내부적으로 데이터베이스에서 사용자 정보를 가져오는 서비스를 담당한다
@Service //@service어노테이션이 있지만 컨트롤러에서 사용되는게 아니라 Spring Security에서 사용자 인증과 권한 부여 과정에서 내부적으로 사용된다
class GetUserDetailsService(
private val memberSecurityRepository: MemberSecurityRepository
) : UserDetailsService { // UserDetailsService 요녀석이 데이터베이스에서 사용자 정보를 가져오는 서비스
override fun loadUserByUsername(email: String): UserDetails {
val user = memberSecurityRepository.findByEmail(email)
?: throw UsernameNotFoundException("해당 이메일이 존재하지 않음")
return User(
user.email,
user.password,
emptyList()
)
}
}
@configureGlobal함수의 userDetailService를 통해서 데이터베이스에 있는 정보를 가져오고
@Configuration
@EnableWebSecurity
class SecurityConfig(
private val jwtTokenProvider: JwtTokenProvider,
private val userDetailsService: GetUserDetailsService,
private val passwordEncoder: PasswordEncoder
) {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http.csrf { it.disable() }
.sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) }
.authorizeHttpRequests {
it.requestMatchers("/auth/**").permitAll()
.anyRequest().authenticated()
}
.addFilterBefore(JwtTokenFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter::class.java)
return http.build()
}
@Bean
fun passwordEncoder(): PasswordEncoder {
return BCryptPasswordEncoder()
}
@Bean
fun authenticationManager(authenticationConfiguration: AuthenticationConfiguration): AuthenticationManager {
return authenticationConfiguration.authenticationManager
}
@Autowired
fun configureGlobal(auth: AuthenticationManagerBuilder) { //AuthenticationManagerBuilder는 사용자 인증 정보를 담고있는 빌더 클래스
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder) //userDetailsService는 데이터베이스에 있는 정보를 가져옴, .passwordEncoder(passwordEncoder) 요 부분이 사용자가 입력한 비밀번호랑 데이터베이스에 있는 비밀번호랑 맞는지의 로직
} //요 userDetailService를 구현하고 있는것이 GetUserDetailsService임
//요 함수는 AuthenticationManagerBuilder를 사용해서 사용자 인증 정보를 전역적으로 구성하는 함수다
}
토큰 생성
@Component
class JwtTokenProvider(
//yml파일에 있는 정보 가져다 쓰기
@Value("\${jwt.secret}") private val secretKey: String,
@Value("\${jwt.validity}") private val validityInMilliseconds: Long,
private val userDetailsService: UserDetailsService // UserDetailService는 Spring Security에서 제공하는 인터페이스(사용자의 상세 정보를 로드하는데 사용됨)
) {
//토큰을 생성하는 함수
fun generateToken(email: String): String {
val now = Date()
val validity = Date(now.time + validityInMilliseconds)
return Jwts.builder() //구성요소 설정
.setSubject(email) // 이메일이 토큰의 식별자로서 사용
.setIssuedAt(now) // 토큰 발행 시간(현재시간)
.setExpiration(validity) // 토큰 만료시간 현재 1시간으로 뒤로 설정되어있음
.signWith(SignatureAlgorithm.HS256, secretKey) // secretKey를 이용해서 토큰에 서명을 해서 무결성을 보장, HS256은 대칭 키 알고리즘
.compact() // 최종적으로 이 구성들로 토큰을 생성하고 문자열 형태로 반환함
}
// 이 토큰이 유효한지 검증하는 함수
fun validateToken(token: String): Boolean {
return try {
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token) //시크릿 키로 토큰의 서명을 검증하고 토큰을 파싱해서 해당 토큰의 클레임(정보)를 추출함 ,파싱은 토큰에 포함된 정보를 열어보는것
true // 예외가 발생하지 않으면 이 토큰은 유효한 토큰이므로 true 반환
} catch (e: Exception) {
false // 예외발생했으므로 이 토큰은 유효하지 않은 토큰이니 false 반환
}
}
//이 토큰에서 이메일을 추출하는 함수 **요 함수는 AuthService의 login함수에 이미 정의되있으므로 중복된 기능이다 삭제?**
fun getEmailFromToken(token: String): String {
val claims: Claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).body // 토큰을 파싱하고(열고) 본문인 body를 가져옴
return claims.subject // 그 body중에 email이 들어있는 subject를 반환함
}
}
// 클라이언트가 서버로 토큰을 보낼때 작동하는 클래스
// Spring Security 에 필요한 구성요소 이기때문에 별도의 서비스에 등록하지 않고 독립적으로 사용
//UsernamePasswordAuthenticationFilter는 Spring Security가 제공하는 인증 필터 중 하나인데 이를 상속한 상태
@Component
class JwtTokenFilter(private val jwtTokenProvider: JwtTokenProvider) : UsernamePasswordAuthenticationFilter() {
// 사용자가 토큰과 함께 요청을 보내면은 그 요청에서 토큰을 꺼내는 함수, JWT는 Authorization 이라는 헤더에 토큰 정보가 담겨져있다
private fun resolveToken(request: HttpServletRequest): String? {
val bearerToken = request.getHeader("Authorization") //HTTP 요청의 헤더에서 Authorization 값을 가져와서 bearerToken에다가 저장함
return if (bearerToken != null && bearerToken.startsWith("Bearer ")) { // Bearer로 시작하면은 Bearer를 제거하고 순수 토큰 문자열을 반환
bearerToken.substring(7)
} else null
}
}
이로써 전체적인 인증에 대한 부분의 골격은 갖추어진 상태이다.
다음날은 각각의 기능을 연결해보고 테스트 해봐야 한다
'TIL' 카테고리의 다른 글
팀프로젝트<뉴스피드 만들기>(5일차) (0) | 2024.05.31 |
---|---|
팀프로젝트<뉴스피드 만들기>(4일차) (0) | 2024.05.30 |
팀 프로젝트<뉴스피드 만들기> (2일차) (0) | 2024.05.28 |
팀 프로젝트<뉴스피드 만들기> (1일차) (0) | 2024.05.27 |
(알고리즘) 숫자 짝꿍 (0) | 2024.05.26 |