Bulls 프로젝트 5

chaean·2023년 10월 31일

프로젝트 - Bulls

목록 보기
5/11

기존 Access Token만 사용했던 코드에서 Refresh Token도 사용해보도록 바꿔봤음.

JwtProvider.java

    // access token create
    public String createToken(Authentication authentication) {
        // 권한 가져오기
        String authorities = authentication.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.joining(","));

        return Jwts.builder()
                .setSubject(authentication.getName())
                .claim("roles", authorities)
                .setIssuedAt(new Date(System.currentTimeMillis())) // 토큰 발행 시간 정보
                .setExpiration(new Date(System.currentTimeMillis() + expiration)) // 유효시간 저장 -> ms단위
                .signWith(SignatureAlgorithm.HS256, secretKey) // 사용할 암호화 알고리즘, signature에 들어갈 secret값 세팅
                .compact();
    }

    // refresh token create
    public String createRefreshToken() {
        return Jwts.builder()
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + refreshExpiration))
                .signWith(SignatureAlgorithm.HS256, secretKey)
                .compact();
    }
    
    

Refresh Token은 Access Token 재발급용으로 사용할 것이기 때문에 별다른 정보를 넣지않음.

UserService.js

    public TokenDTO login(String student_id, String password) {
        // User 검증
        Optional<User> optionalUser = Optional.ofNullable(userRepository.findByStudent_id(student_id).orElseThrow(() ->
                new BadCredentialsException("존재하지 않는 계정입니다.")));

        if (!bCryptPasswordEncoder.matches(password, optionalUser.get().getPassword())) {
            throw new BadCredentialsException("비밀번호가 일치하지 않습니다..");
        }
        Authentication authentication = new UsernamePasswordAuthenticationToken(optionalUser.get().getStudent_id(), optionalUser.get().getPassword());
        String accessToken = jwtProvider.createToken(authentication);

        // refresh
        String refreshToken = jwtProvider.createRefreshToken();
        Refresh refresh = new Refresh();
        refresh.setStudentId(optionalUser.get().getStudent_id());
        refresh.setRefresh(refreshToken);
        refresh.setDate(timeNow());

        updateRefreshToken(optionalUser.get().getStudent_id(), refresh);

        return TokenDTO.builder()
                .accessToken(accessToken)
                .refreshToken(refreshToken)
                .build();
    }

로그인을 하면 Authentication 객체를 생성하여 파라미터를 보내주고 refreshToken은 DB에 따로 저장.

LoginController.java

    @PostMapping("/loginForm")
    public ResponseEntity<TokenDTO> login(@RequestBody @Valid LoginRequestDTO loginRequestDTO, HttpServletResponse response) {
        TokenDTO token = userService.login(loginRequestDTO.getStudent_id(), loginRequestDTO.getPassword());

        if (token != null){
            log.info("로그인 POST  1 [성공]");

            Cookie accessCookie = new Cookie("access", token.getAccessToken());
            Cookie refreshCookie = new Cookie("refresh", token.getRefreshToken());

            accessCookie.setHttpOnly(true);
            accessCookie.setMaxAge(3600); // 초
            accessCookie.setPath("/");
            response.addCookie(accessCookie);

            refreshCookie.setHttpOnly(true);
//            refreshCookie.setMaxAge();
            refreshCookie.setPath("/");
            response.addCookie(refreshCookie);

            return new ResponseEntity<>(new TokenDTO(token.getAccessToken(), token.getRefreshToken()), HttpStatus.OK);
        }
        else {
            log.info("로그인 POST  2 [실패]");
            // 401에러
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new TokenDTO("ID 또는 암호에 오류가 있습니다.",""));
        }
    }

두 개의 토큰 모두 쿠키에 저장하였고 -> (애초에 둘 다 쿠키에 넣는게 맞는지도 공부해봐야 함)
일단 Controller에서 쿠키 저장을 했는데 어디에서 구현하는지에 대해서는 알아보고 더 좋은 방법으로 사용하도록 바꿀 예정.

JwtFilter.java

    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        // 헤더에서 토큰 추출
        String AccessToken = resolveToken(request);

        // token의 유효성 검사
        if (AccessToken != null && "ACCESS".equals(jwtProvider.validateToken(AccessToken))) {
            Authentication authentication = jwtProvider.getAuthentication(AccessToken);
            // 권한부여
            SecurityContextHolder.getContext().setAuthentication(authentication);
            log.info("권한 부여 성공");
        } else if (AccessToken != null && "EXPIRED".equals(jwtProvider.validateToken(AccessToken))) { // access token 만료
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401 Error
            response.getWriter().write("Access Token이 만료되었습니다.");
            log.info("Access Token이 만료되었습니다.");
        }
        filterChain.doFilter(request, response);
    }

validateToken()에서는 token을 파라미터로 받아 해당 토큰의 유효성을 확인한 뒤
정상적인 토큰이라면 "ACCESS"라는 문자열을 반환하고
만료되었다면 "EXPIRED"라는 문자를 반환하도록 만들었고
만약 만료되었을 시 상태코드에 401에러를 발생.

CookieController.java

    @PostMapping("/reissue")
    public ResponseEntity<String> reissue(HttpServletResponse response,@RequestBody Map<String, String> refresh) {

        String token = refresh.get("refresh"); // refresh token
        String newAccessToken = userService.reissueToken(token);
        
        if (newAccessToken == "401" || newAccessToken == null) { // 401 -> refreshToken 만료 , null -> 다른 에러
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("오류가 발생했습니다. 다시 로그인해주세요");
        }
        Cookie accessCookie = new Cookie("access", newAccessToken);
        accessCookie.setHttpOnly(true);
        accessCookie.setMaxAge(3600); // 초
        accessCookie.setPath("/");
        response.addCookie(accessCookie);
        
        return ResponseEntity.ok(newAccessToken);
    }

UserService.java

    public String reissueToken(String token) {
        if (jwtProvider.validateToken(token) == "EXPIRED") { // refresh token의 토큰 만료
            log.info("Refresh Token도 만료됐다는데??");
            return "401";
        } else if (jwtProvider.validateToken(token) == "ACCESS") { // refresh token 토큰 유효
            String student_id = refreshRepository.findByRefresh(token).getStudentId();
            String password = userRepository.findByStudent_id(student_id).get().getPassword();

            Authentication authentication = new UsernamePasswordAuthenticationToken(student_id, password);
            String accessToken = jwtProvider.createToken(authentication);
            return accessToken;
        }
        log.info("UserSerive null 에러");
        return null;
    }

프론트에서는 401에러를 받을 시 controller를 통해 reissueToken()함수를 실행시키고
refreshToken도 만료되었을 시 401에러를,
유효하다면 인증객체를 만든 뒤 새로운 accessToken을 만들어 반환.
Controller에서는 반환받은 accessToken의 반환값을 받아 예외처리 한 뒤 정상이면 쿠키에 다시 추가.

-> setMaxage, setPath등 쿠키 설정하는 것에 대해서 더 알아봐야 할듯???

'
'
'
'
'
'
학교 다니면서 혼자 이것저것 만들어보려니까 시간이 너무 오래 걸렸지만 다 하고 나니까 너무 개운하다.
같이 프로젝트하기로 한 친구랑 DB를 같이 사용해야해서
AWS/RDS먼저 하고 드디어 이제 서비스 로직 구현한다.. 로그인만 몇개월 동안 잡고있었나....
'
'
'
'
현재 프로젝트에서는 AccessToken만 사용하여 로그인하도록 변경하였습니다.

profile
백엔드 개발자

0개의 댓글