Springboot

[Springboot] login 구현

KJihun 2023. 6. 29. 20:43
728x90

 

로그인이란 인증, 인가를 거치는 단계이다.

 


 

1. 인증(Authentication)과 인가(Authorization)

인증: 해당유저가 실제 등록된 유저인지 확인하는 단계
인가: 해당 유저가 특정 리소스에 접근이 가능한지 허가를 확인하는 단계

 

하지만 보통의 웹 사이트들은 비연결성 무상태로 통신하기에 로그인을 구현하기에 어려움이 따른다.

 


 

비연결성 무상태

비연결성 무상태란 리소스 절약을 위해

클라이언트가 요청 시 연결, 처리 후 요청을 끊는 방식을 뜻한다.

클라이언트 - 서버 간 연결을 계속 유지한다면 서버 측에서 기하학적 비용이 발생할 수도 있기 때문이다.

그런데 요청을 끊으면 어떻게 로그인한 페이지가 로그아웃이 되지 않고 유지되는가?

보통 쿠키세션, JWT 방식을 이용하여 로그인을 유지한다.

 


 

쿠키세션 방식 인증

쿠키: 클라이언트에 저장될 목적으로 생성한 작은 정보를 담은 파일
세션: 서버에서 일정 시간동안 클라이언트 상태를 유지하기 위해 사용되는 메커니즘

 

쿠키와 세션은 HTTP에 상태정보를 유지(Stateful) 하기 위해 사용된다.


서버에서 생성한 세션ID는 클라이언트의 쿠키값으로 저장되어 클라이언트 식별에 사용된다.
서버가 인증하고 관련된 최소한의 정보를 저장하는 방식이다.

 


 

JWT(json web token)기반 인증

사용자의 속성을 토큰에 넣어 전달하는 방식이라고 생각하면 된다.

일반적으로 쿠키 저장소를 사용하여 JWT를 저장한다.


1. JWT는 토큰 자체에 정보를 담기에 서버 측에서 세션 유지를 할 필요가 없어 서버의 부담을 줄여준다.

2. 여러대의 서버를 사용하여도 서버 간 세션 정보를 공유할 필요가 없기에 서버의 부하를 분산시킬 수 있다. 
3. 토큰 자체에 서명을 포함하고 있어 변조 여부를 확인할 수 있기에 데이터의 무결성을 보장한다.

 


 

JWT 생성 단계

1. JWT 데이터 구현

2. 토큰 생성
3. JWT 저장 방법(1. cookie에 저장, 2. header에 넣어 전송. 예시는 cookie에 넣어 전송하는 방법)
4. JWT 토큰 substring(token name값인 Bearer를 자르고 value값만 추출)
5. 토큰 검증
6. 토큰에서 사용자 정보 가져오기

코드 예시

@Component
public class JwtUtil {

	// JWT 데이터 구현
    public static final String AUTHORIZATION_HEADER = "Authorization";
    public static final String BEARER_PREFIX = "Bearer ";
    @Value("${jwt.secret.key}") // Base64 Encode 한 SecretKey
    private String secretKey;
    private Key key;
    private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
    public static final Logger logger = LoggerFactory.getLogger("JWT 관련 로그");

	// ...
    
    
    // 토큰 생성
    public String createToken(String username, UserRoleEnum role) {
        Date date = new Date();
        return BEARER_PREFIX +
                Jwts.builder()
                        ...
    }

    // JWT Cookie 에 저장
    public void addJwtToCookie(String token, HttpServletResponse res) {
        try { ...
            res.addCookie(cookie);
        } catch (UnsupportedEncodingException e) {
            logger.error(e.getMessage());
        }
    }

    // JWT 토큰 substring
    public String substringToken(String tokenValue) {
        if (StringUtils.hasText(tokenValue) && tokenValue.startsWith(BEARER_PREFIX)) {
            // 1 2 3 4 5 6 7
            // B E A R E R " "
            // 공백까지 7글자
            return tokenValue.substring(7);
        }
        throw new ...
    }

    // 토큰 검증
    public boolean validateToken(String token) {
        try {
            // parserBuilder(): JWT 파서생성. 이후 JWT를 파싱할 수 있다.
            // setSigningKey(key): 암호화할때 사용할 키
            // build(): JWT파서를 빌드하여 완전한 JWT 파서 객체 생성
            // parseClaimsJws(token): JWT의 유효성을 검증하고, JWT에 포함된 클레임 (claims)을 추출
            Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
            return true;
        } catch (SecurityException | MalformedJwtException | SignatureException e) {
            ...
        }
        return false;
    }

    // 토큰에서 사용자 정보 가져오기
    public Claims getUserInfoFromToken(String token) {
        // 검증 후 body 부분에 존재하는 claims(데이터 집합)를 가져옴
        return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
    }
}

 


 

알게된 것

1. JWT를 이용한 로그인 구현방법

2. 일방향 해시함수인 BCrypt

2. BCrypt로 인코딩 된 비밀번호와 평문 비밀번호를 비교하는 Matchs 메서드

3. 값의 존재 여부를 나타내며 null 체크 필요 없이 안전하게 코드를 작성할 수 있는 Optional Class

 

'Springboot' 카테고리의 다른 글

[Springboot] Security  (0) 2023.07.01
[Springboot] Filter와 Spring Security  (0) 2023.06.30
[Springboot] @Valid  (0) 2023.06.28
[Springboot] PathVariable vs RequestParam  (0) 2023.06.23
Springboot  (0) 2023.06.23