회원가입, 로그인 테스트
중복된 아이디로 회원가입, 아이디 또는 비밀번호 불일치 둘 다 403이 뜨고 로그에만
이미 사용중인 아이디입니다, 아이디 또는 비밀번호가 일치하지 않습니다. 라고 뜬다.
IllegalArgumentException 이 로그단에만 뜨는 에러인가??
예외처리를 새로 해주고 다시 테스트했다.
이미 존재하는 아이디로 회원가입 시도했을 때
충돌 상태코드와 함께 메시지가 잘 뜬다.
맞지 않는 아이디 또는 비밀번호로 로그인했을 때
잘 나온다. 다만 회원가입은 공백으로 냈을 때도 회원가입이 되고
로그인은 공백으로 냈을 때 403에러만 보내주고 로그에만 null값이 되어서는 안된다고 나온다.
이 두개는 각 입력값이 조건을 주면 해결되는걸까?
회원가입을 할 때 입력받은 비밀번호를 암호화해서 DB에 저장했다.
메소드의 파라미터에 스프링 시큐리티의 PasswordEncoder 를 추가하여 객체를 생성하면 비밀번호가 반드시 암호화된다.
🚩 스프링 시큐리티 + Swagger 권한 부여
로그인해서 발급받은 토큰을 Swaggerconfig 의 OpenAi 빈을 추가한다.
.addSecuritySchemes() : 인증 정보 입력을 위한 버튼
.addSecurityItem() : 요구 사항
Api 호출을 위해 HTTP 요청을 보낼 때 Aythorizarion 헤더에 JWT 기반의 Bearer 토큰을 사용한다고 설정해놓았다.
요구사항의 경우 메소드마다 어노테이션을 사용하여 걸어줄 수도 있다.
SwaggerConfig 설정 후 API 화면 상에 위와 같이 인증 버튼과 창이 생겼다.
api 를 실행했을 때 Authorization 헤더에 입력한 토큰 값이 함께 나온다. 아직 사용자 권한 구분은 안했다.
사용자 권한 구분
TokenProvider 에 메소드 추가
// 토큰의 subject 를 복호화하여 문자열 형태로 반환
fun validateTokenAndGetSubject(token: String): String? = Jwts.parserBuilder()
.setSigningKey(secretKey.toByteArray())
.build()
.parseClaimsJws(token)
.body
.subject
사용자 정보 필터링
@Order(0) // 의존성 주입 우선순위
@Component
class JwtAuthenticationFilter(
private val tokenProvider: TokenProvider
): OncePerRequestFilter() {
// 인증 정보 설정
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
val token = parseBearerToken(request)
val user = parseUserSpecification(token)
UsernamePasswordAuthenticationToken.authenticated(user, token, user.authorities)
.apply { details = WebAuthenticationDetails(request) } // 요청날린 client 또는 프록시의 ip 주소와 세션 id 저장
.also { SecurityContextHolder.getContext().authentication = it }
filterChain.doFilter(request, response)
}
private fun parseBearerToken(request: HttpServletRequest)
= request
.getHeader(HttpHeaders.AUTHORIZATION) // http 요청의 헤더에서 authorizarion 값을 찾아서
.takeIf { it?.startsWith("Bearer ", true) ?: false } // 접두어 확인, 제외하고 파싱
?.substring(7)
private fun parseUserSpecification(token: String?) = (
token?.takeIf { it.length >= 10 }
?.let { tokenProvider.validateTokenAndGetSubject(it) } // 토큰 복호화
?: "anonymous:anonymous" // 너무 짧을 때는 익명
).split(":")
.let { User(it[0], "", listOf(SimpleGrantedAuthority(it[1]))) }
}
컨트롤러 또는 메소드에 어노테이션을 붙여서 호출 제한
@PreAuthorize
토큰을 받아서 그 토큰으로 게시글 작성을 하는데 토큰이 검증이 안된다는 오류가 떴다.
토큰을 생성할 때 알고리즘과 secretkey를 지정하는 signWith 이 인자로 알고리즘과 base64로 인코딩된 키를 받고있는데 내가 임의로 설정한 키를 그대로 넣었기 때문이다.
secretkey 를 아무렇게나 넣었었는데
The JWT JWA Specification (RFC 7518, Section 3.2) states that keys used with HS256 MUST have a size >= 256 bits
256비트의 크기를 가져야 하고
fun createToken(userSpecification: String) = Jwts.builder()
.signWith(io.jsonwebtoken.SignatureAlgorithm.HS256, Base64.getEncoder().encodeToString(secretKey.toByteArray()))
.setSubject(userSpecification)
.setIssuer(issuer)
.setIssuedAt(Timestamp.valueOf(LocalDateTime.now()))
.setExpiration(Date.from(Instant.now().plus(expirationHours, ChronoUnit.HOURS)))
.compact()!!
secretkey를 넣을 때 base64 로 인코딩해서 넣었더니 잘 된다.
올바른 토큰을 부여받은 사람만 게시글, 댓글의 작성 수정 삭제 할 수있도록 만들었다.
이제 해야할 건
작성한 게시글과 댓글에 유저 아이디를 부여하고 수정, 삭제할 때 아이디가 일치하는 유저만 가능하도록 만들어야 한다.
게시글이랑 댓글 작성할 때 유저의 이메일을 부여하는 건 됐다. 사실 이름을 부여하고싶었는데 유저정보에서 어떻게 가져오는지 못찾았다...
컨트롤러에서 인자로 Principal 을 받아오면 유저의 name(고유 식별 정보) 를 가져올 수 있다.
principal 내부의 equals 메소드로 게시글과 유저의 고유 정보를 비교하고 같지 않으면 수정, 삭제 시 예외를 날려주게 했다.
정상적으로 된다!
이제 댓글만 완성하면 끝인데 자꾸 댓글 권한 접근이 안된다..... 될때까지 하고 TIL 먼저 쓴다.
'왕초보일지' 카테고리의 다른 글
240113 TIL | (1) | 2024.01.13 |
---|---|
240112 TIL | Co-Ha 내가 구현한 기능 공부, 기록 (1) | 2024.01.12 |
240110 TIL | 스프링시큐리티 로그인 구현하는 중 (1) | 2024.01.10 |
240109 TIL | (2) | 2024.01.09 |
240108 TIL | 7주차 팀프로젝트 (3) | 2024.01.08 |