[SpringBoot3] 로그인 페이지 실습 2(JWT)

[개발환경]
M2 OSX Ventura 13.2.1
VS Code
SpringBoot 3.0.2
OpenJDK 17
Gradle 7.6
[Docker]
 - mysql:latest

들어가며

Spring Session과 JWT는 둘 다 스프링 부트 애플리케이션에서 인증과 세션 관리를 위해 사용될 수 있지만, 서로 다른 방식으로 작동한다. 

앞선 포스트에서 Spring Session에 대해서 설명하고 직접 구현해 보았다.

2023.03.08 - [DEV/SpringBoot] - [SpringBoot3] 로그인 페이지 실습 1

JWT는 서버와 클라이언트 간의 인증에 사용되며, 클라이언트에서 토큰을 생성하고 서버에서 검증한다. JWT는 사용자 정보와 권한 정보를 포함할 수 있으며, 서버 측에서 세션을 저장하거나 관리하지 않는다. JWT는 토큰을 사용하기 때문에, 서버와 클라이언트 간의 통신에서 매번 인증 정보를 전송해야 할 필요 없이 토큰만을 전송하여 인증할 수 있다.

JWT를 이용한 인증 및 인가 구현

'jjwt' 라이브러리를 이용하여 JWT 토큰을 생성하고 검증할 수 있다.

먼저, 의존성을 추가한다.[build.gradle]

dependencies{
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'io.jsonwebtoken:jjwt'
}

JWT 토큰을 생성하고 검증하는 'JwtUtils' 클래스를 작성한다. [JwtUtils.java]

@Component
public class JwtUtils {
    private final String secret = "secret"; // JWT secret key
    private final long validityInMilliseconds = 3600000; // 1 hour

    public String createToken(String username) {
        Date now = new Date();
        Date validity = new Date(now.getTime() + validityInMilliseconds);

        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(now)
                .setExpiration(validity)
                .signWith(SignatureAlgorithm.HS256, secret)
                .compact();
    }

    public String getUsernameFromToken(String token) {
        return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getSubject();
    }

    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            return false;
        }
    }
}

로그인 화면과 로그인 처리를 담당할 'LoginController' 클래스 작성한다. [LoginController.java]

@RestController
@RequestMapping("/auth")
public class LoginController {
    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private JwtUtils jwtUtils;

    @PostMapping("/login")
    public ResponseEntity<?> authenticateUser(@RequestBody LoginRequest loginRequest) {
        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())
        );

        SecurityContextHolder.getContext().setAuthentication(authentication);
        String jwt = jwtUtils.createToken(loginRequest.getUsername());
        return ResponseEntity.ok(new JwtResponse(jwt));
    }
}

[LoginRequest.java]

@Getter
@Setter
public class LoginRequest {
    private String username;
    private String password;

}

[JwtResponse.java]

@Getter
public class JwtResponse {
    private String token;

    public JwtResponse(String token) {
        this.token = token;
    }

}

인증된 사용자만 접근을 허용할 /home 경로로 이동시킬 'HomeController.java'

@RestController
@RequestMapping("/api")
public class HomeController {
    @GetMapping("/user")
    @PreAuthorize("hasRole('USER')")
    public String userAccess() {
        return "User Content.";

인증 이후 토큰을 검증하는 방법, 안전하게 보관하는 방법에 대해서 다음 포스트에서 다룬다.

참고

점프 투 스프링부트(https://wikidocs.net/book/7601)

반응형