Junit Test Application-17-jwt 세팅

jaegeunsong97·2023년 8월 4일
0

Junit Bank Application 깃허브

Junit Bank Application 기록 노션

jwt 설명

대칭키, RSA

  • formLogin().disable()
  • jwt는 무상태 서버 -> jSessionId 사용X -> 토큰을 사용
  • 서버에서 HS256을 이용해 암호화 후 토큰을 생성, 토큰 전달
  • FE에서는 토큰을 들고 항상 접근 -> 토큰을 검증
  • 따라서 RSA 방식이 아닌 대칭키 사용
package shop.mtcoding.bank.config.jwt;

public interface JwtValueObject {

     public static final String SECRET = "메타코딩"; // HS256 (대칭키), 환경변수로 해야됨, 서버만 알아야하는 키
     public static final int EXPIRATION_TIME = 1000 * 60 * 60 * 24 * 7; // 일주일
     public static final String TOKEN_PREFIX = "Bearer "; // 조심
     public static final String HEADER = "Authorization";
}
package shop.mtcoding.bank.config.jwt;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class JwtProcess {

     private final Logger log = LoggerFactory.getLogger(getClass());

     // 토큰 생성
     public static String create() {

     }

     // 토큰 검증
     public static void verify() {

     }
}

2개의 메소드 필요

package shop.mtcoding.bank.config.auth;

import java.util.ArrayList;
import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import shop.mtcoding.bank.domain.user.User;

@RequiredArgsConstructor
@Getter
public class LoginUser implements UserDetails {

     private final User user;

     @Override
     public Collection<? extends GrantedAuthority> getAuthorities() {
          Collection<GrantedAuthority> authorities = new ArrayList<>();
          authorities.add(() -> "ROLE_" + user.getRole());
          return authorities;
     }

     @Override
     public String getPassword() {
          return user.getPassword();
     }

     @Override
     public String getUsername() {
          return user.getUsername();
     }

     @Override
     public boolean isAccountNonExpired() {
          return true;
     }

     @Override
     public boolean isAccountNonLocked() {
          return true;
     }

     @Override
     public boolean isCredentialsNonExpired() {
          return true;
     }

     @Override
     public boolean isEnabled() {
          return true;
     }
}
package shop.mtcoding.bank.config.auth;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import shop.mtcoding.bank.domain.user.User;
import shop.mtcoding.bank.domain.user.UserRepository;

@Service // IOC 등록
public class LoginService implements UserDetailsService {

     @Autowired
     private UserRepository userRepository;

     // 시큐리티로 로그인할 때, 시큐리티가 loadUserByUsername() 실행해서 username 체크
     // user가 없으면 오류
     // user가 있으면 정상적으로 시큐리티 컨텍스트 내부 세션에 로그인된 세션이 만들어진다.
     @Override
     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
          User userPS = userRepository.findByUsername(username).orElseThrow(
                    () -> new InternalAuthenticationServiceException("인증 실패"));
          return new LoginUser(userPS);
     }
}

InternalAuthenticationServiceException로 터트려야하는 이유 : 시큐리티가 돌때, 개발자에게 제어권이 없기 때문에

코드 추가

package shop.mtcoding.bank.config.jwt;

import java.util.Date;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;

import shop.mtcoding.bank.config.auth.LoginUser;
import shop.mtcoding.bank.domain.user.User;
import shop.mtcoding.bank.domain.user.UserEnum;

public class JwtProcess {

     private final Logger log = LoggerFactory.getLogger(getClass());

     // 토큰 생성
     public static String create(LoginUser loginUser) {
          String jwtToken = JWT.create()
                    .withSubject("bank") // 토큰 제목
                    .withExpiresAt(new Date(System.currentTimeMillis() + JwtValueObject.EXPIRATION_TIME)) // 만료시간
                    .withClaim("id", loginUser.getUser().getId())
                    .withClaim("role", loginUser.getUser().getRole() + "")
                    .sign(Algorithm.HMAC512(JwtValueObject.SECRET)); // 단방향 암호화
          return JwtValueObject.TOKEN_PREFIX + jwtToken;
     }

     // 토큰 검증
     public static LoginUser verify(String token) {
          DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC512(JwtValueObject.SECRET)).build().verify(token);
          Long id = decodedJWT.getClaim("id").asLong();
          String role = decodedJWT.getClaim("role").asString();
          User user = User.builder().id(id).role(UserEnum.valueOf(role)).build();
          LoginUser loginUser = new LoginUser(user); // JWT에 들어있는 것으로 객체 생성
          return loginUser; // 강제로 시큐리티 세션에 직접 주입(강제로그인)
     }
}

생성과 검증을 한 곳에서 -> 대칭키

시큐리티 세션에 강제 주입하는 이유는 나중에

profile
블로그 이전 : https://medium.com/@jaegeunsong97

0개의 댓글