Hyunseok
프로그래밍/개인홈페이지 :: Spring boot + JWT + OAuth2 + Redis로 Restful 로그인을 만들어보자 2
2022. 11. 28. 11:44

 

이 글은 아래의 글의 2편입니다

2022.11.28 - [프로그래밍/ㄴ 개인홈페이지] - :: Spring boot + JWT + OAuth2 + Vue + Redis 로그인을 만들어보자 1

 

:: Spring boot + JWT + OAuth2 + Vue + Redis 로그인을 만들어보자 1

첫 팀 프로젝트에서 못해서 분했던 OAuth2 로그인.. 이번에는 꼭 해보리다 생각하고 시큐리티를 건드리기 시작했다 인터넷 보니 온갖 여러 가지 방법으로 만들어놨던데.. 나도 그 반열에 한 번 올

hbyun.tistory.com

 

 

 

이번 편에서는 util들을 설정해보자 간단한게 JWT와 Redis용 유틸들을 만들어보자

 

1. JWTUtil(manager)

@Log4j2
public class JwtManager {
    @Value("${dev.hyns.secretkey}")
    private String secretKey;
    private final long DAY = 259200000 / 3;

    public String AccessTokenGenerator(Long mid, String email) {
        SecretKey key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8));
        Date today = new Date();
        String jwt = Jwts.builder()
                .setIssuer("hyns.dev")
                .setIssuedAt(today)
                .setExpiration(new Date(today.getTime() + DAY/24/6))
                .claim("email", email)
                .claim("userNumber", mid)
                .setSubject("bblog token")
                .signWith(key, SignatureAlgorithm.HS256)
                .compact();
        return "Bearer "+jwt;
    }

    public String RefreshTokenGenerator(Long mid, String email) {
        SecretKey key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8));
        Date today = new Date();
        String jwt = Jwts.builder()
                .setIssuer("hyns.dev")
                .setIssuedAt(today)
                .setExpiration(new Date(today.getTime() + DAY*7))
                .claim("email", email)
                .claim("userNumber", mid)
                .setSubject("bblog token")
                .signWith(key, SignatureAlgorithm.HS256)
                .compact();
        return "Bearer "+jwt;
    }

    public Boolean tokenValidator(String token) {
        try {
            Jwts.parserBuilder()
            	.setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
                .build()
                .parseClaimsJws(token.split("Bearer ")[1]);
            return true;
        } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
            log.info("잘못된 토큰", e);
            return false;
        } catch (ExpiredJwtException e) {
            log.info("유효기간이 지난 토큰", e);
            return false;
        } catch (UnsupportedJwtException e) {
            log.info("지원되지 않는 토큰", e);
            return false;
        } catch (IllegalArgumentException e) {
            log.info("claims가 비어있음", e);
            return false;
        }
    }

    public Claims parseClaims(String token) {
        try {
            return Jwts.parserBuilder()
            			.setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
                        .build()
                        .parseClaimsJws(token.split("Bearer ")[1])
                        .getBody();
        } catch (ExpiredJwtException e) {
            return e.getClaims();
        }
    }
}

(리팩토링이 절실하다 액세스 토큰만 만들고 시작한 프로젝트인데 리프래시 토큰

따로 만들어서 여러 개 수정하기가 너무 귀찮아서 그냥 리프래시용을 하나 더 만들어버렸다 ㅂㄷㅂㄷ..... )

 

이렇게 생성용 하나, 증명용 하나, 그냥 말 그대로 payload용 하나 이렇게 만들어놨다

 

이제 아무 곳이나 가서 써먹기로 하고..

 

Redis도 설정해주자 제일 먼저 redis의 설정부터 해줬다

 

1. RedisConfig

@Configuration
@RequiredArgsConstructor
public class RedisConfig {
    @Value("${dev.hyns.redishost}")
    private String host;

    @Value("${dev.hyns.redisport}")
    private int port;



    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
        config.setHostName(host);
        config.setPort(port);
        return new LettuceConnectionFactory(config);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

application.properties에 미리 설정해둔 host, port를 적어주고 (아까 도커에 적어둔 것과 똑같이 적으면 된다 )

 

나중에 쓰일 템플릿도 지정해준다

요즘에는 이거 안 써도 지 알아서 넣어준다는데.. 믿을 수가 없으니 그냥 넣어준다

 

그러고 나서 이 컨픽 파일을 이용해 사용할 유틸을 만들어준다

 

2. RedisUtil

@Component
@RequiredArgsConstructor
public class RedisUtil {
    private final RedisTemplate<String, String> rt;
    private final MembersRepository mrepo;
    private final JwtManager manager;

    @Transactional
    public void setRefreshToken(String token, Long mid) {
        ValueOperations<String, String> rToken = rt.opsForValue();
        rToken.set(token, mid.toString(), Duration.ofDays(7L));
        mrepo.loggedMember(mid, true);
    }

    @Transactional
    public void removeRefreshToken(String rToken) {
        Optional<String> mid = Optional.ofNullable(rt.opsForValue().get(rToken));
        if (mid.isPresent()) {
            mrepo.loggedMember(Long.parseLong(mid.get()), false);
            rt.delete(rToken);
        }
    }

    @Transactional
    public TokenInfo tokenReIssueValidator(String aToken, String rToken) {
        Optional<Members> targetMember = mrepo
                .findById(Long.parseLong(manager.parseClaims(aToken)
                .get("userNumber")
                .toString()));
                
        Long atkMid = targetMember
        				.orElse(
                        	Members
                            	.builder()
                                .mid(0L)
                                .oauth(false)
                                .build())
                                .getMid();
                                
        String mid = Optional
        				.ofNullable(rt.opsForValue().get(rToken))
                        .orElse("-1");
        if (
                Long.parseLong(mid) == atkMid 
                & manager.tokenValidator(rToken) 
                & isLogged(aToken)
            ) {
            String rtkn = manager
            .RefreshTokenGenerator(
            		targetMember.get().getMid(), 
                    targetMember.get().getEmail()
            );
            
            String atkn = manager
            .AccessTokenGenerator(
            		targetMember.get().getMid(), 
                    targetMember.get().getEmail()
            );
            removeRefreshToken(rToken);
            setRefreshToken(rtkn, targetMember.get().getMid());
            return TokenInfo.builder()
                    .nickname(targetMember.get().getNickname())
                    .aToken(atkn)
                    .rToken(rtkn)
                    .build();
        } else {
            removeRefreshToken(rToken);
            mrepo.loggedMember(Long.parseLong(mid), false);
            return null;
        }
    }

    public Boolean rTokenChecker(String rToken) {
        return Optional
        		.ofNullable(rt.opsForValue()
                .get(rToken))
                .isPresent() ? true : false;
    }

    public Boolean adminChecker(String rToken) {
        return mrepo.findById(Long.parseLong(manager.parseClaims(rToken)
        			.get("userNumber")
                    .toString())
                )
                    
                .orElse(Members.builder().mid(0L).oauth(false).build()).getRoles().stream()
                .anyMatch(v -> v.equals(Roles.ROLE_ADMIN));
    }

    public Boolean isLogged(String aToken) {
        return mrepo.findById(Long.parseLong(manager.parseClaims(aToken)
        			.get("userNumber")
                    .toString())
                )
                .orElse(Members.builder().mid(0L).oauth(false).logged(false).build()).isLogged();
    }

}

일단 간단하게 rtk를 설정해줄 set, 그다음 rm 두 개만 만들어주고 나머지는 validator, checker로 검사한다

 

원래 이렇게 길지 않았는데.. 뭐 하나 넣고 계속 넣고 수정하다 보니 이리 길어졌다 

 

흑흑.. 너무 슬프다

 

이제 security의 설정으로 들어가보자 

 

는.. 다음 편에서 

 

3편

2022.11.28 - [프로그래밍/ㄴ 개인홈페이지] - :: Spring boot + JWT + OAuth2 + Redis로 Restful 로그인을 만들어보자 3

 

 


프로그래밍/개인홈페이지의 다른 글