JWT를 이용한 로그인

승환·2025년 2월 1일

📌 로그인

로그인을 하면 JWT토큰을 발행해서 헤더에 담아서 반환하는 것을 구현하였다.


Controller

private final signinService service;
    private final JwtBuilder jwtBuilder;
    @PostMapping
    public ResponseEntity<ResponseJson<Object>> signin(
            @RequestBody RequestSignincheckDto signincheckDto,
            HttpServletResponse res
            ){
        String jwtToken = null;
        ResponseSignincheckDto response = service.loginCheck(signincheckDto);
        if(response.getState().value){
            jwtToken = jwtBuilder.generateAccessToken(response.getName());
            res.setHeader("Content-Type", "application/json");
            res.setHeader("Authorization", "Bearer " + jwtToken);
        }
        return ResponseEntity.ok(
                ResponseJson.builder()
                        .status(200)
                        .message("JWT")
                        .result(res.getHeader("Authorization"))
                        .build()
        );
    }

Dto는 아이디와 비밀번호를 받는 것과, 로그인이 완료되면 반환받는 객체 두개로 나누었다.

여기서 HttpServletResponse라는게 나오는데
웹에서 우리가 어떤 요청을 받고 그걸 반환할 때 웹에 있는 헤더 혹은 바디에 반환할 수 있게 해주는 것이다. 여기서는 JWT토큰을 헤더에 담아 반환하기 위해서 사용되었다.

로그인 체크를 이용해 만약 정상적인 유저임이 판단되면 JWT토큰을 생성해서 헤더에 넣어준다. 그리고 헤더에 넣은 값을 확인을 위해 Json으로 반환하도록하였다.


Service

 private final authRepository repo;

    @Description("로그인 check")
    public ResponseSignincheckDto loginCheck(RequestSignincheckDto dto) {
        String id = dto.getId();
        String pw = dto.getPw();
        SignUp valid = repo.findByEmailID(id);
        if(valid!=null){
            if(valid.getPassword().equals(pw)){
                return ResponseSignincheckDto.builder()
                        .state(State.OK)
                        .name(valid.getUserName())
                        .build();
            }
        }
        return ResponseSignincheckDto.builder()
                .state(State.NULL)
                .build();
    }

Service이다. 로그인 채크 기능을 넣어서 authReposiroty Jpa로 탐색을 해서 있는 아이디라면 로그인이 가능하도록 하였다. State를 넣어서 반환하게 하는데, State는 단순히 불린 값으로 해도 상관없지만 보는 사람의 가독성을 위해서 Enum을 하나 정의해서 OK, Null등의 값을 지정하였다.
그리고 그 값을 불린 값으로 value받을 수 있도록 하여 컨트롤러에서 처리하도록 하였다.


Repository

public interface authRepository extends JpaRepository<SignUp,Long> {
    SignUp findByEmailID(String id);
}

이전에 한번 썼던 리포지토리인데, Jpa에서 제공하는 메서드형 쿼리는 findBy~라고 시작하는 것 뒤에 칼럼을 적으면 대상을 탐색해 준다.
따라서 아이디를 탐색할 수 있도록 만든 것이다.


JWT

@Component
public interface JwtBuilder {
    String generateJWT(String name,Long exptime);
    String generateAccessToken(String name);
    String generateRefreshToken(String name);
    JwtCode validateToken(String token);
}

컨트롤러에서 사용하는 JWT빌더의 인터페이스이다. @Component를 이용해서 스프링 빈에 등록될 수 있도록 하였고, 사용할 메서드를 등록해주었다.

@Component
public class JwtBuilderImpl implements JwtBuilder {
    @Value("${jwt.secret.key}")
    private String SecretKey; //키값임.
    private static final Long AccessTokenExpTime = 1000*60L*3L;
    private static final Long RefreshTokenExpTime = 60L * 1000 * 60L;
    public String generateJWT(String name,Long exptime){
        Map<String,Object> header = new HashMap<>();
        header.put("typ", "JWT"); //토큰 헤더 설정

        Date ext = new Date();
        ext.setTime(ext.getTime()+exptime); //유효시간 설정

        Map<String,Object> payload = new HashMap<>();
        payload.put("user_name",name);//토큰 페이로드설정

        String jwt = Jwts.builder()
                .setHeader(header)
                .setClaims(payload)
                .setSubject("test")
                .setExpiration(ext)
                .signWith(SignatureAlgorithm.HS256,SecretKey.getBytes())
                .compact();
        return jwt;
    }

재정의한 Impl이다. 키값을 정하는 SecretKey는 Value어노테이션으로 application.properies에
jwt.secret.key 이런 식으로 정의한 것을 가져올 수 있게 하였다.
토큰은 유지시간이 있어야 보안적으로 안정적이기 때문에 어세스, 리프레시 토큰의 유지시간을 설정해 주었다.
그리고 헤더, 페이로드 등을 설정한 후에
jwts의 빌더를 통해 등록해주고 마지막에 signWith에 등록한 키와 알고리즘을 넣어주면
토큰 값을 얻을 수 있다.

public String generateAccessToken(String name){
        return generateJWT(name,AccessTokenExpTime);
    }
    public String generateRefreshToken(String name){
        return generateJWT(name,RefreshTokenExpTime);
    }

실제로 사용할 토큰 발급 메서드이다. AccessToken과 RefreshToken을 구분해서 생성해야 하기 때문에 이렇게 메서드를 나눠서 발급 시간을 조정하였다.

    public JwtCode validateToken(String token) {
        if (token == null || token.trim().isEmpty()) {
            return JwtCode.DENIED; // 토큰이 유효하지 않음
        }
        try {
            Jwts.parserBuilder().setSigningKey(SecretKey).build().parseClaimsJws(token);
            return JwtCode.ACCESS;
        } catch (ExpiredJwtException e) { // 기한 만료
            return JwtCode.EXPIRED;
        } catch (Exception e) {
            return JwtCode.DENIED;
        }

토큰을 검증하는 메서드이다. 나중에 어떤 페이지에 접근하건 헤더에 있는 토큰을 검증해서 사용해야할텐데 그 검증을 도와주는 메서드이다.


이제 이걸HTTP에 날려서 확인해보면

이렇게 POST요청을 날려주면

이런 식으로 요청이 반환 되는 것을 볼 수 있다.
이걸 https://jwt.io 사이트에서 디코딩을 해볼 수 있는데,

이런 결과를 얻을 수 있다. 헤더와 페이로드는 알 수 있지만 가장 중요한 사인은 디코딩을 해도 알 수 없는 모습이다. 이렇게 페이지 보안을 강화할 수 있다.

profile
왕초보 학부생

0개의 댓글