티스토리 뷰

반응형

안녕하십니까 웹 프로젝트를 하면서 로그인 및 인증에 대한 처리에 대해 oauth2 및 jwt 방식을 사용해보신적 있으십니까?


저는 facebook 로그인 방식으로는 oauth2를 사용해봤고 일반 로그인 처리방식 및 인증 방식으로는 jwt 방식을 많이 사용해봐서 경험을 공유하고자 합니다.


그 중에서 jwt에 대해 기재하려고 합니다.


JSON Web Token (JWT)은 공개 된 업계 표준 인 RFC 7519 방식으로 두 당사자간에 확실하게 클레임을 표시합니다. 

- jwt.io -


jwt.io를 방문해보시면 jwt 예제 뿐 아니라 다양한 언어들을 지원하는 한다는 것을 알 수 있습니다.


그럼 jwt가 어떻게 표시되는지 볼까요? 




이미지를 보시면 왼쪽이 Encoded된 문자열이고 그 문자열을 Decoded하면 오른쪽과 같은 Data가 됩니다.


Decode를 하면 저렇게 3군데로 나워지나 봅니다.

  • HEADER : 알고리즘 및 토큰 타입을 기재

  • PAYLOAD (BODY) : 실 데이터가 있는 부분

  • SIGNATURE : 시그니처라고 토큰을 인코딩 및 유효성 검증할 때 사용하는 고유 시그니처 암호화 코드

저는 Java를 이용해 구현하려고 합니다. 그럼 Java에서도 다양한 라이브러리가 있는데 저는 그 중에서도 JJWT라는 라이브러리를 사용해보겠습니다.





일단 github에 가보니 설명서가 잘 되어 있네요.

저는 gradle를 사용하니 gradle dependency에 다음과 같은 의존성을 추가해줍니다.
// jwt
compile 'io.jsonwebtoken:jjwt-api:0.10.5'
runtime 'io.jsonwebtoken:jjwt-impl:0.10.5',
           'io.jsonwebtoken:jjwt-jackson:0.10.5'    


그럼 직접적으로 토큰을 생성 및 사용을 해볼까요???


jjwt 예제에서는 다음과 같이 만들라고 나와있네요.


import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import java.security.Key;

// We need a signing key, so we'll create one just for this example. Usually
// the key would be read from your application configuration instead.
Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);

String jws = Jwts.builder().setSubject("Joe").signWith(key).compact();
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ.1KP0SsvENi7Uz1oQc07aXTL7kpQG5jBNIybqr60AlD4

이런식으로 만들면 밑에 문자열처럼 토큰이 생성된다네요. 정말 간단하네요!


그리고 저 JWT를 다시 Decode해서 데이터를 얻어내려면 다음과 같이 사용을 해야한다고 합니다.

Jwts.parser().setSigningKey(key).parseClaimsJws(jws).getBody().getSubject().equals("Joe");


이제 간단한 사용법을 배웠으니 실전에서도 사용해 봐야겠죠?


일단 jwt을 어떻게 사용할 것인지에 대해 생각을 하고 설계를 해야합니다.

저같은 경우는 jwt를 다음과 같이 사용했습니다.


1. 로그인 처리 시 jwt 발급

2. 발급된 jwt를 redis에 저장

3. 로그인 후 프론트 단에서 jwt를 보내주며 유효성 검증


유효성 검증

1. 기본적으로 프론트에서 보내는 jwt가 redis에 저장된 값과 일치하는지 확인
   (토큰이 만료됬는지도 검증)

2. 내가 사용하는 서버에서 만든 토큰인지 signature 키를 가지고 유효성 검증



그럼 한번 만들어 보겠습니다.

일단 저같은 경우에는 SHA-256알고리즘을 이용해서 토큰을 만들었습니다.

private static final String headerString = "X-JWT";
       // s!e@c#r$e%t%k^e&y*!#@$#%$^%&^*s!e@c#r$e%t%k^e&y*!#@$#%$^%&^*s!e@c#r$e%t%k^e&y*!#@$#%$^%&^*s!e@c#r$e%t%k^e&y*!#@$#%$^%&^*
	private String secretKey = PropertiesService.getProperty("coinlink.jwt.secretKey").trim();

	private byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(secretKey);
	
	private SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

	private final Key KEY = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
	
	// 토큰 생성
	public String getUserToken(HttpServletRequest request, HttpServletResponse response, UserVO userVO) {

		String jwt = Jwts.builder()
				.setHeaderParam("typ", "JWT")
				.setSubject(userVO.getEmail())
				.claim("uid", userVO.getUid())
				.setExpiration(new Date(System.currentTimeMillis()  +  1 * (1000 * 60 * 60 * 24) ) )
				.signWith(KEY,signatureAlgorithm)
				.compact();
		
		return jwt;		
	}


일단 위에 변수 선언부터 봅시다

private String secretKey = PropertiesService.getProperty("coinlink.jwt.secretKey").trim();

private byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(secretKey);
	
private SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

private final Key KEY = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());

일단 위의 secretKey 와 apiKeySecretBytes를 봅시다.


secreKey는 server쪽에서 token을 생성을 하기 위한 암호 코드 같은거고 그 밑에서 byte 형태로 바꾼 후에 JWT에서 제공하는 SHA256 알고리즘을 이용해 KEY를 생성합니다.



String jwt = Jwts.builder()
  	    .setHeaderParam("typ", "JWT")
	    .setSubject(userVO.getEmail())
	    .claim("uid", userVO.getUid())
	    .setExpiration(new Date(System.currentTimeMillis()  +  1 * (1000 * 60 * 60 * 24) ) )
	    .signWith(KEY,signatureAlgorithm)
	    .compact();


그리고 위에 처럼 토큰을 생성해 주면 되는데요.

Header에는 JWT 타입이라는 것을 명시해 주고

Subject & claim은 PAYLOAD, 즉 body부분을 명시해 줍니다. 그리고 Expiration 유효시간을 정해주고 아까 만든 KEY를 이용해 토큰을 만들어 주면 끝입니다!




그리고 이 토큰을 유효성 검증 및 데이터를 가져오기 위해서 각자 필요한 method들을 만들어 주시면 되는데요!


저 같은 경우는 유효성 검증을 위해 2가지의 유효성 검증 method 데이터를 추출하기 위한 method 총 4가지를 만들었습니다.


// 토큰 유효성 검증
public Boolean validationToken(String jwt){

		if (jwt != null) {
			
				String userKey = this.getUserUID(jwt);
				
				String key = RedisClientTemplate.get(RedisSourceType.USER_KEY.getKey(userKey), redisTemplate);
	
				if (key.equals(jwt)) {
					return true;
				}			
				return false;
		}
		return false;
	}

// 토큰 만료 확인 
public Boolean getExpToken(String jwt) {
		try {
			Jws claims = Jwts.parser().setSigningKey(KEY).parseClaimsJws(jwt);
			
			Date exp = claims.getBody().getExpiration();
			
			Date now = new Date();
			
			if (exp.after(now)) {
				return true;		
			}				
			return false;
		} catch (Exception e) {
			return false;
//			UnauthorizedException("Illegal Token");
		}
//		throw new NullArgumentException("Token is NULL");
	}
	
public String getUserEmail(String jwt) throws RuntimeException{
		try {
			
			Jws claims = Jwts.parser().setSigningKey(KEY).parseClaimsJws(jwt);
			return claims.getBody().getSubject();
			
		} catch (Exception e) {
			return null;
		}
	}
	
	
public String getUserUID(String jwt) throws RuntimeException{
		try {
			
			Jws claims = Jwts.parser().setSigningKey(KEY).parseClaimsJws(jwt);
			return claims.getBody().get("uid") + "";
			
		} catch (Exception e) {
			return null;
		}
	}


이렇게 4개를 생성했으며 그 이후 부터는 각자 알아서 사용하시면 되겠죠??


저같은 경우는 인터셉터에 유효성 검증 로직을 만들어서 사용을 했는데

참고 하세요!


그리고 토큰은 진짜 유효성 검증 및 로그인에 대한 처리 이정도로 사용하시고 너무 많은 데이터를 담지 않는 것을 추천드리며 jwt 또한 완벽한 보안을 제공하지 않으며 너무 믿지 마세요.



그럼 이상입니다.


-------------------------------------------------


추가

예전에 쓴 글이라 개념이 많이 부족했습니다.

JWT는 토큰 생성에 대한 규칙을 정한 일종의 표준이고 JWT로만 보안로직을 만들 순 없으며, Oauth2방식의 토큰 인증방식을 사용하고 그리고 그 토큰을 만들기 위해 JWT를 사용하곤 합니다.




출처 및 참고

https://jwt.io/

https://hyojun123.github.io/2018/07/17/JWT2/

반응형

'BackEnd > Java' 카테고리의 다른 글

Spring boot + Mybatis 연결하기  (0) 2019.03.26
Spring boot Redis 사용하기  (3) 2019.03.12
spring boot properties파일 분리하기  (0) 2019.03.04
nginx  (0) 2019.02.27
spring boot HikariCP  (1) 2019.01.06
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함