[Spring] jwt를 통한 인증, 인가 원리 / 구현 - Part 1. 토큰 발급

CodeKong의 기술 블로그·2023년 9월 25일
1

SPRING BOOT

목록 보기
11/24
post-thumbnail

[완료된 포스트입니다.]

이번에는 jwt를 이용하여 인증, 인가를 구현해보겠습니다!

며칠동안 구현 원리를 이해하려고 노력했고 나름 정리했습니다!


먼저 jwt를 크게 두 부분으로 나누어 이해했습니다.

1. jwt 토큰 발급

2. 요청에 대한 jwt 인가

제가 나름 공부하고 정리하면서 그린 클래스 다이어 그램인데요
먼저 천천히 보시고 마지막까지 보신 이후 다시 보시면 이해가 더 잘될거에요!

주황선은 로그인 부분, 파란선은 인가 부분으로 구분하였습니다

한 챕터가 끝날 때 마다 다시 순서대로 정리해보겠습니다.


로그인

LoginDto.java

@Builder
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class LoginDto {

    private String email;
    private String password;

}

로그인 입력 정보를 받아줄 DTO입니다.

AuthController.java

@CrossOrigin(origins = "http://localhost:3000")
@RestController
@RequiredArgsConstructor
@RequestMapping("/auth")
public class AuthController {

    private final LoginService loginService;

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginDto loginDto) {
        TokenInfo token = loginService.login(loginDto.getEmail(), loginDto.getPassword());
        return new ResponseEntity<>(token,HttpStatus.OK);
    }

}

LoginDto 받아서 loginService에 email과 password를 넘겨줍니다.

(저는 email을 id역할로 하기 때문에 각자 상황에 맞게 바꿔주시면 됩니다! security 기본설정은 username입니다)

LoginService.java

@Service
@Transactional
@RequiredArgsConstructor
public class LoginService {

    private final AuthenticationManagerBuilder authenticationManagerBuilder;
    private final JwtTokenProvider jwtTokenProvider;

    public TokenInfo login(String email, String password) {

        //전달 받은 정보들을 통해 토큰 생성
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(email, password);
        
        //email 기반으로 인증 후 Authentication 객체 반환
        Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);

        TokenInfo token = jwtTokenProvider.generateToken(authentication);
        return token;
    }


}

login 함수에서는 email과 password를 받아 UsernamePasswordAuthenticationToken 형태의 token을 생성하고 이를
AuthenticationManagerBuilder 클래스의 authenticate함수로 넘겨줍니다.

이때, authenticate함수가 실행될 때 CustomUserDetailsService의 loadUserByUsername 가 호출되어 인증을 수행합니다.

이후 인증에 성공하면 generateToken의 반환값인 token을 반환합니다.

UserDetailService.java

@Service
@RequiredArgsConstructor
public class UserDetailService implements UserDetailsService {

    private final UserService userService;

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
    
    //userRepository.findByEmail로 대체 가능
        return userService.findUser(email);
    }
}

UserDetailService에서는 단순히 userRepository에 email로 등록된 user가 있는지 조회하는 코드입니다.

저는 userService를 분리했지만 userRepository에서 바로 찾으셔도 됩니다.

jwtTokenProvider.java

@Component
public class JwtTokenProvider {

    private final Key key;

    //application.yml에 설정한 secretKey를 주입받음
    public JwtTokenProvider(@Value("${jwt.secret}") String secretKey) {
        byte[] keyBytes = Decoders.BASE64.decode(secretKey);
        this.key = Keys.hmacShaKeyFor(keyBytes);
    }

    //JWT 토큰 생성
    public TokenInfo generateToken(Authentication authentication) {

        //권한 얻어오기
        String authorities = authentication.getAuthorities()
                .stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.joining(","));

        long now = (new Date()).getTime();

        //Access Token 생성
        Date accessTokenExpiresIn = new Date(now + 864000000);
        String accessToken = Jwts.builder()
                .setSubject(authentication.getName())
                .claim("auth", authorities)
                .setExpiration(accessTokenExpiresIn)
                .signWith(key, SignatureAlgorithm.HS256)
                .compact();


        //Refresh Token 생성
        String refreshToken = Jwts.builder()
                .setExpiration(new Date(now + 86400000))
                .signWith(key, SignatureAlgorithm.HS256)
                .compact();

        return TokenInfo.builder()
                .grantType("Bearer")
                .accessToken(accessToken)
                .refreshToken(refreshToken)
                .build();
    }

    ...

}

@Value("${jwt.secret}") String secretKey 부분에서는 application.yml에

jwt:

secret: VlwEyVBsYt9V4zq57TejMnVByzblYcfPQye08f7MGVA9XkKa

같이 적었습니다!


TEST - spring

@SpringBootTest
@Transactional
@AutoConfigureMockMvc
class AuthControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private LoginService loginService;



    @DisplayName("토큰발급")
    @Test
    void login() throws Exception {

        //given
        LoginDto loginDto = LoginDto.builder()
                .email("TestEmail@email.com")
                .password("testpassword")
                .build();

        String reqJSON = new Gson().toJson(loginDto);

        //when

        MvcResult mvcResult = mockMvc.perform(
                post("/auth/login")
                        .contentType("application/json")
                        .content(reqJSON)
        ).andReturn();

        //Gson으로 이쁘게 json 출력
        Gson gson = new GsonBuilder().setPrettyPrinting().create();
        TokenInfo tokenInfo = gson.fromJson(mvcResult.getResponse().getContentAsString(), TokenInfo.class);
        String resJSON = gson.toJson(tokenInfo);

        //then

        System.out.println("resJSON = " + resJSON);
    }

}

MockMVC를 이용해 post요청을 보내고 응답값을 Gson을 통해 이쁘게 출력해줍니다!

(결과값만 볼려면 주석부분에서 mvcResult.getResponse().getContentAsString()을 그냥 출력하면 됩니다)

통과 완료!


TEST - postman


postman에서 값들을 담아 요청을 보내보겠습니다.

통과 완료!

다음 포스트에서는 인가 부분에 대해 다뤄보도록 할게요!

0개의 댓글