- 오늘의 계획 -
주특기 플러스 주차 todoList 요구사항대로 완성하기
- 오늘 한 것 -
Spring security + jwt 강의를 보고 머리속으로 정리를 하여 적용 시켜보았다
Spring Security 적용을 위해 gradle에 라이브러리 등등을 추가
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("io.jsonwebtoken:jjwt-api:0.12.3")
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.3")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.3")
아직 잘 모르니 일단은 강의대로 따라 해보았고 현 상태로 swagger를 실행 시키면 login 화면이 뜬다
spring security는 기본적으로 제공되는 filter들이 여러가지 있고 이것들이 filterChain으로 적용된다
인증을 위한 필터를 적용하기 위해서는 customFilter를 적용할껀데 순서는 아직 다 외우지 않았으니 시키는대로 필터 작성을 해본다
@Configuration
@EnableWebSecurity//http 기반으로 통신을 할 때 관련 보안기능을 설정 을 할때 다는 어노테이션
@EnableMethodSecurity
class SecurityConfig(
private val jwtAuthenticationFilter: JwtAuthenticationFilter,
private val authenticationEntryPoint: AuthenticationEntryPoint,
private val accessDeniedHandler:CustomAccessDeniedHandler,
) {
@Bean
fun filterChain(http:HttpSecurity):SecurityFilterChain{
return http
.httpBasic{it.disable()}
.formLogin{it.disable()}
.csrf{it.disable()}
.authorizeHttpRequests{
it.requestMatchers(
"/users/Login",
"/users/signUp",
"/swagger-ui/**",
"/v3/api-docs/**",
//위에 경로를 인증에서 제외한다
).permitAll()
.anyRequest().authenticated()
}
.addFilterBefore(jwtAuthenticationFilter,UsernamePasswordAuthenticationFilter::class.java)
.exceptionHandling{
it.authenticationEntryPoint(authenticationEntryPoint).accessDeniedHandler(accessDeniedHandler)
}// 테스트할때 주석 쳐보고 할 것
.build()
}
}
이렇게 하면 앞에서 login화면 등이 꺼지고 다시 정상적으로 sawgger가 원래대로 작동한다
jwt 토큰 발급을 위한 코드를 작성한다
@Component
class JwtPlugin {
companion object{
const val SECRET = "PO4c8z41Hia5gJG3oeuFJMRYBB4Ws4aZ"
const val ISSUER = "team.sparta.com"
const val ACCESS_TOKEN_EXPIRATION_HOUR:Long = 168
}//yml에 빼주면 되는데 지금 작성 안되있어서 패쓰
fun validateToken(jwt:String): Result<Jws<Claims>>{
return kotlin.runCatching {
val key = Keys.hmacShaKeyFor(SECRET.toByteArray(StandardCharsets.UTF_8))
Jwts.parser().verifyWith(key).build().parseSignedClaims(jwt)
}
}
fun generateAccessToken(subject:String,email:String,role:String):String{
return generateToken(subject,email,role,Duration.ofHours(ACCESS_TOKEN_EXPIRATION_HOUR))
}
private fun generateToken(subject:String,email:String,role:String,expirationPeriod:Duration):String{
val claims:Claims = Jwts.claims()
.add(mapOf("role" to role,"email" to email))
.build()
val key = Keys.hmacShaKeyFor(SECRET.toByteArray(StandardCharsets.UTF_8))
val now = Instant.now()
return Jwts.builder()
.subject(subject)
.issuer(ISSUER)
.issuedAt(Date.from(now))
.expiration(Date.from(now.plus(expirationPeriod)))
.claims(claims)
.signWith(key)
.compact()
}
}
비밀번호 암호화
@Configuration
class PasswordEncoderConfig {
@Bean
fun passwordEncoder():PasswordEncoder{
return BCryptPasswordEncoder()
}
}
//
//service
@Transactional
override fun signUp(signUpDto: SignUpDto): UserResponseDto {
if (userRepository.existsByEmail(signUpDto.email)) throw IllegalStateException("가입된 이메일")
//return UserEntity.toUserResponse(userRepository.save(UserEntity.toUserEntity(signUpDto,passwordEncoder)))
val role = when(signUpDto.role){
"ADMIN" -> UserRole.ADMIN
"USER" -> UserRole.USER
else -> throw IllegalArgumentException("Invalid role")
}
return userRepository.save(
UserEntity(
name = signUpDto.name,
email = signUpDto.email,
password = passwordEncoder.encode(signUpDto.password),
role = role
)
).toUserResponse()
}
위에 encoder 파일을 생성해주고 회원가입 서비스에 encoder를 주입한 다음 저장할때 사용해주면 암호화되서 db에 저장이 된다
로그인
requestDto 에서 이름 이메일 역할을 받고 서비스에서 받은 값과 데이터베이스에 저장된 값을 비교해서 토큰을 리턴 해주면 된다
override fun login(loginDto: LoginDto): LoginResponseDto {
val userFind = userRepository.findByEmail(loginDto.email) ?: throw IllegalStateException("사용자를 찾을 수 없습니다")
if (userFind.role.name == loginDto.role && passwordEncoder.matches(loginDto.password, userFind.password)) {
return LoginResponseDto(
accessToken = jwtPlugin.generateAccessToken(
subject = userFind.userId.toString(),
email = userFind.email,
role = userFind.role.name
)
)
} else {
throw InvalidCredentialException("닉네임 또는 패스워드를 확인해주세요")
}
}
여기서 발급 받은 토큰을 authorize를 통해 로그인 할수 있다
JWT 인증 구현
jwt를 요청 header에서 추출해서 사용한다 - > 검증에 성공할시 Authentication객체에 인증 됐다는걸 표기 하고 SecurityContext에 저장한다
JwtAuthenticationFilter 생
@Component
class JwtAuthenticationFilter(
private val jwtPlugin: JwtPlugin
):OncePerRequestFilter() {
companion object {
private val BEARER_PATTERN = Regex("^Bearer (.+?)$")
}
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
val jwt = request.getBearerToken()
if(jwt != null) {
jwtPlugin.validateToken(jwt)
.onSuccess {
val userId = it.payload.subject.toLong()
val role = it.payload.get("role", String::class.java)
val email = it.payload.get("email", String::class.java)
val principal = UserPrincipal(
id = userId,
email = email,
roles = setOf(role)
)
val authentication = JwtAuthenticationToken(
principal = principal,
details = WebAuthenticationDetailsSource().buildDetails(request)
)
SecurityContextHolder.getContext().authentication =authentication
}
}
filterChain.doFilter(request,response)
}
private fun HttpServletRequest.getBearerToken(): String? {
val headerValue = this.getHeader(HttpHeaders.AUTHORIZATION) ?: return null
return BEARER_PATTERN.find(headerValue)?.groupValues?.get(1)
}
}
jwt정보를 추출해서 정보를 저장한다
UserPrincipal 객체
data class UserPrincipal(
val id: Long,
val email:String,
val authorities:Collection<GrantedAuthority>
){
constructor(id:Long,email: String,roles:Set<String>):this(
id,
email,
roles.map { SimpleGrantedAuthority("ROLE_$it") }
)
//부생성자
}
유저정보를 담는 객체
JwtAuthenticationToken 클래스 생성
class JwtAuthenticationToken(
private val principal: UserPrincipal,
details:WebAuthenticationDetails
):AbstractAuthenticationToken(principal.authorities) {
init {
super.setAuthenticated(true)
super.setDetails(details)
}
//초기화 -
override fun getCredentials() = null
override fun getPrincipal() = principal
override fun isAuthenticated(): Boolean {
return true
}
}
Authentication 객체를 생성
인증을 구현하고 객체를 생성해서 context에 넣어주고 filterChain에 적용한다
일단은 이렇게 이해하기로 했고 todoList에 적용 시켜서 동작이 잘 되는거 까지 확인했다
프로젝트 주간에 팀원들이 설정 해놓은걸 그냥 어노테이션만 넣어서 사용했는데 생각보다 어려웠고 자세한 부분은 스킵을 많이 했기 때문에 조금더 깊게 파야할거같다
'TIL' 카테고리의 다른 글
24_02_13 TIL (0) | 2024.02.13 |
---|---|
24_02_08 TIL (0) | 2024.02.08 |
24_01_29 TIL (1) | 2024.01.29 |
24_01_26 TIL (1) | 2024.01.26 |
24_01_23 TIL (0) | 2024.01.23 |