InTen

[Spring] 스프링 Security Token Provider 만드는 법 본문

프로그래밍/자바

[Spring] 스프링 Security Token Provider 만드는 법

인텐 2022. 11. 23. 12:28

본 환경은 Rest 기반 코드 입니다.

저희는 이전 장에서 새로 바뀐 Spring SecurityConfig에 대해서 구성을 해보았는데요.

그렇다면 토큰을 만들어서 넣어주는 토큰 provider를 이번시간에 만들어 보려고 합니다.

이번에 Deprecated된 메소드는 두개로 Jwts 클래스의 signWith와 parser() 입니다.

본 코드 입니다.

수정 전의 코드는 본 코드를 본 후 비교를 하시면 될 것 같습니다.

package com.perfume.allpouse.config.security;

import com.perfume.allpouse.data.entity.User;
import com.perfume.allpouse.service.impl.UserServiceImpl;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Date;

@Component
public class TokenProvider implements InitializingBean{

    private final Logger LOGGER = LoggerFactory.getLogger(TokenProvider.class);

    private final UserServiceImpl userServiceImpl;

    public TokenProvider(UserServiceImpl userServiceImpl) {
        this.userServiceImpl = userServiceImpl;
    }

    @Value("${springboot.jwt.secret}")
    private String secretKey; // application.properties에서 값을 불러오며 32bit의 길이를 넘어야 한다.
    private Key key;
    private final long tokenValidMillisecond = 1000L * 60 * 60 * 24 * 30; //토큰 유효기간 1달


    @Override
    public void afterPropertiesSet() {
        LOGGER.info("INIT : JWT SecretKey 초기화 시작");
        this.key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); // 22년 이후 BASE64에서 HMAC 사용을 권장
        LOGGER.info("INIT : JWT SecretKey 초기화 완료");
    }

    public long getId(String token) {
        LOGGER.info("[getUserName] 토큰에서 회원 ID 추출 ");
        long id = Long.parseLong(Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody().getSubject());
        LOGGER.info("[getUserName] 토큰에서 회원 ID 추출 완료 iD : {}" ,id);
        return id;
    }

    public String createToken(String roles, long id) {
        LOGGER.info("[createToken] 토큰 생성 시작 ");

        Claims claims = Jwts.claims().setSubject(String.valueOf(id));
        claims.put("roles", roles);
        Date now = new Date();

        String token = Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(now)
                .setExpiration(new Date(now.getTime() + tokenValidMillisecond))
                .signWith(key, SignatureAlgorithm.HS256)
                .compact();

        LOGGER.info("[createToken] 토큰 생성 완료 token : ", token);
        return token;
    }

    public Authentication getAuthentication (String token) {

        LOGGER.info("[getAuthentication] 토큰 인증 정보 조회 시작");
        User user = userServiceImpl.loadUserById(this.getId(token));
        LOGGER.info("[getAuthentication] 토큰 인증 정보 조회 완료 user : {}", user.toString());

        return new UsernamePasswordAuthenticationToken(user, "",user.getAuthorities());
    }

    public String resolveToken(HttpServletRequest request) {
        LOGGER.info("[resolveToken] 토큰 정보 헤더에서 읽어오기 ");
        return request.getHeader("X-AUTH-TOKEN");
    }

    public boolean valiateToken(String token) {
        LOGGER.info("[valiateToken] 토큰 유효성 검증");
        try {
            Jws<Claims> claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);

            return !claims.getBody().getExpiration().before(new Date());
        } catch (Exception e) {
            LOGGER.info("[valiateToken] 토큰 유효성 예외 발생 ");
            return false;
        }


    }

}

43줄의 코드를 살펴보자 지금은 hmac으로 비밀키를 암호화를 진행하고 있다. hmac 키는 32bit 이상의 길이의 비밀키를 가져야 하기 때문에 secretkey의 길이는 영문으로 32자 이상으로 해야 한다.

this.key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); // 22년 이후 BASE64에서 HMAC 사용을 권장

아래의 코드는 기존의 base 64 코드 이다. 지금은 signWith 메소드가 base64를 지원하지 않아 사용하지 않는다.

this.key = Base64.getEncoder().encodeToString(secretKey.getBytes(StandardCharsets.UTF_8));

 

49줄의 코드이다. 기존의 코드는 key가 뒤로 가며 base64방식으로 되어 있는 키였지만 아래 방식으로 한다면 취소선이 그이며 해당 메소드의 대한 정보가 나온다.

.signWith(SignatureAlgorithm.HS256, key)

기존 signWith에 @Deprecated가 붙었으며 사유를 찾아보니 사람들이 key를 스트링으로 받으니 서명되지 않은 일반 문자열 키를 사용할려고 하며 헷갈려 하기도 하고 base64는 암호화가 아닌데 암호화처럼 알고 사용하려는 오용들이 많아서 삭제하게 되었다고 합니다.

이부분은 제가 예전에 spring io 블로그에 있던걸 번역해서 제가 이해한것 인데 이상하게도 그 글이 삭제가 되어서 찾을 수가 없게 되었습니다. 제가 해석한 이유와 다른 사유로 deprecated 된 것이라면 댓글로 알려주세요.

아래는 수정된 코드 입니다.

.signWith(key, SignatureAlgorithm.HS256)

마지막은 parser() 메소드가 사라졌다는것 입니다.

기존 코드는 parser() 메소드만 사용해서 가져왔습니다.

long id = Long.parseLong(Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject());

수정된 코드에서는

parserBuilder().~~~~.build() 와 같은 형태로 변경이 되었습니다.

long id = Long.parseLong(Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody().getSubject());

이렇게 새로 변경된 provider 부분에 대한 설명은 끝 입니다.

createtoken 메소드는 나중에 로그인 구현 기능을 만들며 로그인 컨트롤러 쪽에서 사용할 것 입니다.

간단히 설명하자면 token provider는 token serviceImpl 라고 봐도 될 것 같습니다.

Comments