티스토리 뷰
FrameWork/Spring
[Spring] Security +Google Oauth2 + JWT 구현하기 (3) - 생성한 JWT에 대해 인증/인가 하기
KoB 2022. 6. 5. 12:08
이어서 계속 구현해보겠습니다.
구현 자체가 목적이므로 자세한 설명을 생략합니다.
- JWT 생성하기
- JWT에 권한 추가해주기
- 생성한 JWT에 대해 인증/인가 하기 (진행)
- JWT 재발급 해주기
구현
JwtFilter
class JwtFilter(
private val jwtProvider: JwtProvider
) : OncePerRequestFilter() {
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
val jwt = jwtProvider.resolveToken(request)
if (StringUtils.hasText(jwt) && jwtProvider.validateAccessToken(jwt)) {
val authentication = jwtProvider.findAuthentication(jwt)
SecurityContextHolder.getContext().authentication = authentication
}
filterChain.doFilter(request, response)
}
}
JWT 검증은 Filter를 통해서 진행됩니다.
resolveToken은 header에서 bearer를 제외한 순수 토큰만을 가져옵니다.
JwtProvider
@Component
class JwtProvider(
private val memberRepository: MemberRepository
) {
companion object {
private const val AUTHORITIES_KEY = "auth"
private const val BEARER_TYPE = "bearer"
private const val ACCESS_TOKEN_EXPIRE_TIME = (1000 * 60 * 30)
private const val REFRESH_TOKEN_EXPIRE_TIME = (1000 * 60 * 60 * 24 * 7)
}
private val key: Key by lazy {
val secretKey: String = "ZVc3Z0g4bm5TVzRQUDJxUXBIOGRBUGtjRVg2WDl0dzVYVkMyWWs1Qlk3NkZBOXh1UzNoRWUzeTd6cVdEa0x2eQo=" // base64Encoded
Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretKey))
}
fun generateJwtDto(oAuth2User: OAuth2User) : JwtDto {
val now = Date().time
val accessTokenExpiresIn: Date = Date(now + ACCESS_TOKEN_EXPIRE_TIME)
val member = memberRepository.findByEmail(oAuth2User.attributes["email"] as String)
val accessToken = Jwts.builder()
.setSubject(member?.email) // payload "sub": "email"
.claim(AUTHORITIES_KEY, member?.role) // payload "auth": "ROLE_USER"
.setExpiration(accessTokenExpiresIn) // payload "exp": 1516239022 (예시)
.signWith(key, SignatureAlgorithm.HS512) // header "alg": "HS512"
.compact()
val refreshToken = Jwts.builder()
.setSubject(member?.email)
.setExpiration(Date(now + REFRESH_TOKEN_EXPIRE_TIME))
.signWith(key, SignatureAlgorithm.HS512)
.compact()
return JwtDto(
grantType = BEARER_TYPE,
accessToken = accessToken,
refreshToken = refreshToken,
accessTokenExpiresIn = accessTokenExpiresIn.time
)
}
fun resolveToken(request: HttpServletRequest): String {
val bearerToken = request.getHeader(HttpHeaders.AUTHORIZATION)
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(AccessTokenType.BEARER.value)) {
return bearerToken.substring(7)
}
return ""
}
fun validateAccessToken(token: String): Boolean {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token)
return true
} catch (e: SecurityException) {
println("올바르지 못한 토큰입니다.")
} catch (e: MalformedJwtException) {
println("올바르지 못한 토큰입니다.")
} catch (e: ExpiredJwtException) {
println("만료된 토큰입니다.")
} catch (e: UnsupportedJwtException) {
println("지원되지 않는 토큰입니다.")
} catch (e: IllegalArgumentException) {
println("잘못된 토큰입니다.")
}
return true
}
fun findAuthentication(accessToken: String): Authentication {
val claims = parseClaims(accessToken)
val authorities = mutableListOf(claims[AUTHORITIES_KEY] as String).map { role -> SimpleGrantedAuthority(role) }
val user = User(claims[Claims.SUBJECT] as String, "", authorities)
return UsernamePasswordAuthenticationToken(user, "", authorities)
}
private fun parseClaims(accessToken: String): Claims {
return try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(accessToken).body
} catch (e: ExpiredJwtException) {
e.claims
}
}
}
resolveToken, validateAccessToken, findAuthentication, parseClaims 함수가 추가되었습니다.
SecurityConfig
@Configuration
class SecurityConfig(
private val customUserDetailService: CustomUserDetailService,
private val jwtProvider: JwtProvider
) : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http
.csrf {
it.disable()
}
.httpBasic {
it.disable()
}
.sessionManagement {
it.sessionCreationPolicy(SessionCreationPolicy.NEVER)
}
.authorizeRequests {
it.antMatchers("/guest").hasRole("USER")
it.antMatchers("/admin").hasRole("ADMIN")
}
.oauth2Login {
it.userInfoEndpoint().userService(customUserDetailService)
it.defaultSuccessUrl("/auth/login")
it.failureUrl("/fail")
}
.addFilterBefore(JwtFilter(jwtProvider), UsernamePasswordAuthenticationFilter::class.java)
}
}
/guest 와 /admin을 추가해주었습니다.
MainController
@RestController
class MainController {
@GetMapping("/success")
fun success(): String {
return "success"
}
@GetMapping("/fail")
fun fail(): String {
return "fail"
}
@GetMapping("/guest")
fun guest(): String {
return "guest"
}
@GetMapping("/admin")
fun admin(): String {
return "admin"
}
}
실행
1. 구글 계정으로 로그인을 한 후, jwt을 발급 받습니다.
2. 발급 받은 토큰은 User로 /guest로 접근이 가능합니다.
3. 이번엔 Admin이 접근 가능한 /admin로 접근을 시도해봅니다.
4. Forbidden이 뜨는 걸 확인할 수 있고, 접근을 하고 싶으면 DB에 저장된 ROLE_USER를 ROLE_ADMIN으로 바꾸면 됩니다.
'FrameWork > Spring' 카테고리의 다른 글
[Spring] Security +Google Oauth2 + JWT 구현하기 (4) - JWT 재발급 해주기 (0) | 2022.06.05 |
---|---|
[Spring] Security +Google Oauth2 + JWT 구현하기 (3) - 생성한 JWT에 대해 인증/인가 하기 (0) | 2022.06.05 |
[Spring] Security +Google Oauth2 + JWT 구현하기 (2) - JWT에 권한 추가해주기 (0) | 2022.06.04 |
[Spring] Security +Google Oauth2 + JWT 구현하기 (1) - JWT 생성하기 (0) | 2022.06.04 |
[Java] 스프링을 왜 사용할까?(2) - OCP와 DIP 해결 By 순수 자바 (2) | 2022.02.01 |
[Java] 스프링을 왜 사용할까?(1) - OCP와 DIP의 위배 (4) | 2022.01.20 |
댓글