0. 이 글의 작성 이유
최소한으로 JWT토큰을 발급하고 사용자에게 전달하는 과정을 설명하기 위함
다음에 Spring Security를 이용한 보안 처리를 진행할 예정이어서 미리 대비해 둔 코드가 존재할 수 있다. no used가 발생해도 당황하지 말자.
같이 보면 좋은 글 : https://developer-youn.tistory.com/166
이전 글
https://developer-youn.tistory.com/165
환경
Spring Boot 3.x
Spring Security 6.x
1. Spring Security, JJWT Gradle 적용하기
Rest API의 필터링 및 JWT적용을 위해 Gradle을 적용한다.
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("io.jsonwebtoken:jjwt:0.9.1")
implementation("javax.xml.bind:jaxb-api:2.4.0-b180830.0359")
여기서 뭔가 이상한 게 들어갔다. javax.xml.bind:jaxb-api는 뭐 하는 라이브러리길래 필자는 추가했는가
해당 라이브러리가 없는 경우 아래 내용을 진행하면서 이런 에러가 발생한다.
decode함수의 리턴 타입이 java.xml.bind에 있는 클래스로 잡혀있다.
문제는 java11부터 JavaEE모듈이 제거되었다. 그래서 별도로 추가를 해야 한다.
2. JWT Token 발행 코드 작성
@Component
class JWTUtils{
companion object {
/** 만료시간 **/
const val EXP_TIME: Long = 1000L * 60 * 3
/** Salt **/
const val JWT_SECRET: String = "secret"
}
/** JJWT **/
val signatureAlgorithm: SignatureAlgorithm = SignatureAlgorithm.HS256
/**
* Token 발행
*/
fun issueToken(userName: String): String {
val claims: Claims = Jwts.claims()
claims["username"] = userName
return Jwts.builder()
.setClaims(claims)
.setExpiration(Date(System.currentTimeMillis() + EXP_TIME))
.signWith(signatureAlgorithm, JWT_SECRET)
.compact()
}
토큰의 발급은 이렇게 하면 된다.
코틀린에서의 특징 중 하나를 여기서 간단하게 설명하자면 코틀린은 Java에서의 static이 존재하지 않는다.
대신 companion object를 통해 static처럼 정의할 수 있다.
여기서 Salt로 적혀있는 JWT_SECRETE이 있다.
Salt, 소금이라는 건 원재료 위에 양념을 하나 더 치는 것이다.
여기서는 JWT로 암호화하는 과정에서 우리가 별도로 정해둔 양념을 통해 쉽게 복호화되지 못하도록 양념을 친다고 생각하면 된다.
이 예제처럼 secret이라고 하지 않고 어려운 문장을 작성해 두면 좋을 것이다.
2. WebSecurityConfig 파일 작성
Spring Security를 통해 일부 설정을 진행한다.
@EnableWebSecurity
@Configuration
class WebSecurityConfig(private val jwtFilter: JwtFilter) {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http.csrf { it.disable() }
http.logout { it.disable() }
http.cors { it.disable() }
http.sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) }
http.authorizeHttpRequests {
it.requestMatchers("/**").permitAll()
.anyRequest().authenticated()
}
return http.build()
}
@Bean
fun myPasswordEncoder(): PasswordEncoder = BCryptPasswordEncoder(14)
@Bean
fun authenticationManager(config: AuthenticationConfiguration): AuthenticationManager =
config.authenticationManager
}
Spring boot 3.x, Spring security6.x 버전은 과거 방식과 설정하는 방법이 달라진다. kotlin dsl도 지원하지 않고 deprecated, removed된 클래스, 메서드들이 존재하기 때문에 잘 설정해야 한다.
주요 설정
- 내 편의를 위해 csrf, cors, logout 다 비활성화시킨다.
- 세션 정책은 사용하지 않을 것이다.
- 모든 경로의 요청에 대해 인가할 것이다.
PasswordEncoder와 AuthencicationManager에 대한 Bean은 나중을 위해 미리 작성해 둔다.
3. 회원가입 API로 요청 시 토큰 던져주기
미리 말하자면 절대 이렇게 사용하면 안 된다.
여기는 어디까지나 토큰 발급이 이렇게 하면 가능합니다라고 최소한으로 보여주기 위함이므로 코드가 이상할 수 있다.
서비스 파일의 signin을 이렇게 작성했다.
@Service
class UserServiceImpl(
@Autowired var userRepository: UserRepository,
private val passwordEncoder: PasswordEncoder,
private val authenticationManager: AuthenticationManager,
private val jwtUtils: JWTUtils
) : UserService {
override fun save(userDTO: UserRequest): Long = userRepository.save(userDTO2Entity(userDTO))
override fun find(userId: Long): UserRequest = userEntity2DTO(userRepository.findUser(userId))
@Transactional
override fun signin(userSignin: UserSigninDTO): String{
userRepository.save(SiteUser(userSignin.email, userSignin.password, userSignin.name))
return jwtUtils.issueToken(userSignin.name)
}
private fun userDTO2Entity(userDTO: UserRequest): SiteUser = SiteUser(userDTO.email, userDTO.userName, userDTO.password)
private fun userEntity2DTO(user: SiteUser): UserRequest = UserRequest(user.email, user.password, user.username)
}
위에서 만든 JWTUtils를 통해 토큰을 발급하고 던져주기로 했다.
컨트롤러는 이렇게 작성했다.
@RestController
@RequestMapping("/api/user")
class UserController(@Autowired var userService: UserService,){
@GetMapping("/search/{userId}")
fun searchUser(@PathVariable userId : Long): UserRequest = userService.find(userId)
@PostMapping("/signin")
fun siginin(@RequestBody userSignin: UserSigninDTO) = ResponseEntity.ok().body(userService.signin(userSignin))
}
이렇게 하면 signin으로 요청 시 토큰이 반환될 것이다.
4. 다음 글에서는
위에서 약간 순서가 틀렸지만 테스트코드 작성과 함께 정말 현실성 있는 코드와 함께 사용자의 ROLE정의 등을 진행해 보자.
ref
'JAVA > 코프링' 카테고리의 다른 글
맨땅에서 뭐라도 해보는 토이 코프링(2) - 유저 도메인 부터 간단한 컨트롤러까지 한 호흡에 (0) | 2023.09.17 |
---|---|
맨땅에서 뭐라도 해보는 토이 코프링(1) - 프로젝트 생성 (0) | 2023.09.17 |