본문 바로가기
공부/프로그래밍

[spring security, jwt] jwt 인증 설정하기 + Cannot convert access token to JSON 에러 잡기

by demonic_ 2021. 7. 19.
반응형

Spring Sercurity Oauth2 에서 인증방법 중 jwt인증을 쉽게하는 방법이 있다. @EnableAuthorizationServer 를 설정하는 곳에서 JwtAccessTokenConverter 를 추가하면 된다. 설정은 다음과 같다.

...
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }
...

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        ...
        endpoints.tokenStore(tokenStore());
        endpoints.accessTokenConverter(accessTokenConverter());
    }
...
    private JwtAccessTokenConverter accessTokenConverter () {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        return converter;
    }

 

이렇게 한 뒤 token을 요청하면 다음처럼 결과를 받는다.

{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MjY0NTI2MTAsInVzZXJfbmFtZSI6IlRSQUlORUUtMDEwMTIzNCIsImF1dGhvcml0aWVzIjpbIlJPTEVfVFJBSU5FRSJdLCJqdGkiOiI1NmYxNzNhYi0wNjVhLTQ2MDQtODZjZC1lOWViNzRkMWIzOWMiLCJjbGllbnRfaWQiOiJlZGl5YU9hdXRoMlNlcnZpY2UiLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXX0.Ac8FlIDOPitYRAoFmr-8k-cR1BPrpw7j9EIOYsToLnQ",
    "token_type": "bearer",
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJUUkFJTkVFLTAxMDEyMzQiLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiYXRpIjoiNTZmMTczYWItMDY1YS00NjA0LTg2Y2QtZTllYjc0ZDFiMzljIiwiZXhwIjoxNjI5MDAxNDEwLCJhdXRob3JpdGllcyI6WyJST0xFX1RSQUlORUUiXSwianRpIjoiOTE1OTg3YjUtZWM4Zi00MmJjLTkwMGQtNDNjYTJmNGViM2I4IiwiY2xpZW50X2lkIjoiZWRpeWFPYXV0aDJTZXJ2aWNlIn0.FuvzRDIJGaWzo5wQoocw9BR4x3dLAwVJOljldS5_HhA",
    "expires_in": 43199,
    "scope": "read write",
    "jti": "56f173ab-065a-4604-86cd-e9eb74d1b39c"
}

 

그럼 이것을 가지고 인증페이지를 접속해보았다.여전히 Bearer 방식이므로 Header Authorization 에 값을 넣을때 포함해야 한다.

curl --location --request GET 'http://localhost:10000/api/token/valid' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MjY0NTI2MTAsInVzZXJfbmFtZSI6IlRSQUlORUUtMDEwMTIzNCIsImF1dGhvcml0aWVzIjpbIlJPTEVfVFJBSU5FRSJdLCJqdGkiOiI1NmYxNzNhYi0wNjVhLTQ2MDQtODZjZC1lOWViNzRkMWIzOWMiLCJjbGllbnRfaWQiOiJlZGl5YU9hdXRoMlNlcnZpY2UiLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXX0.Ac8FlIDOPitYRAoFmr-8k-cR1BPrpw7j9EIOYsToLnQ' \
--header 'Content-Type: application/json' \
--header 'Cookie: JSESSIONID=DD49D822503EE7270B94A65CE54EC97F' \
--data-raw '{}'

 

문제는 다음과 같은 에러가 발생했다.

{
    "error": "invalid_token",
    "error_description": "Cannot convert access token to JSON"
}

그래서 해당 오류를 발생시키는 파일을 찾아갔는데, decode 하면서 NullPointException이 발생한 것이다. 에러문구가 제대로 표기되지 않아 한참을 찾았는데 알고보니 verifier 에 null 로 들어가 있어서 그랬다.

 

그럼 verifier 에는 왜 Null이 들어있을까? JwtAccessTokenConverter 클래스에서 keyPair를 생성하면서 삽입되는데 내 경우 그것을 생략해서 그렇다. 아래 사이트 경우 keyPair를 구성하여 삽입하는 구절이 있다.

https://yookeun.github.io/java/2017/07/23/spring-jwt/

 

Spring security JWT 연동

1. 기존 oauth2의 문제점 기존의 OAuth2의 단점은 api를 호출할때마다 accessToken이 유효한지 실제 oauth서버에 통해 검증하는 것이다. 이때 매번 oauth에서 해당 토큰의 만료여부등을 DB등에서 조회하고

yookeun.github.io

 

 

 

그런데 내 경우는 KeyPair 를 삽입하지 않기 때문에 다른방법을 사용해야 했다. 확인해본 결과 afterPropertiesSet 를 실행함으로써 verifier 에 삽입해야 한다.

    public JwtAccessTokenConverter accessTokenConverter () {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        try {
            converter.afterPropertiesSet();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return converter;
    }

아래는 afterPropertiesSet의 내부구조다

...
	public void afterPropertiesSet() throws Exception {
		if (verifier != null) {
			// Assume signer also set independently if needed
			return;
		}
		SignatureVerifier verifier = new MacSigner(verifierKey);
		try {
			verifier = new RsaVerifier(verifierKey);
		}
		catch (Exception e) {
			logger.warn("Unable to create an RSA verifier from verifierKey (ignoreable if using MAC)");
		}
		// Check the signing and verification keys match
		if (signer instanceof RsaSigner) {
			byte[] test = "test".getBytes();
			try {
				verifier.verify(test, signer.sign(test));
				logger.info("Signing and verification RSA keys match");
			}
			catch (InvalidSignatureException e) {
				logger.error("Signing and verification RSA keys do not match");
			}
		}
		else if (verifier instanceof MacSigner) {
			// Avoid a race condition where setters are called in the wrong order. Use of
			// == is intentional.
			Assert.state(this.signingKey == this.verifierKey,
					"For MAC signing you do not need to specify the verifier key separately, and if you do it must match the signing key");
		}
		this.verifier = verifier;
	}
...

그런데 이 설정이 문제인 것은 새로 시작할때마다 signingKey 가 랜덤으로 생성된다는 것이다. 이럴경우 이전에 발행된 키가 모두 무용지물이 될 수 있기 때문에 위험할 수 있다. 때문에 키를 설정해주는 것이 좋다. 아래 사진을 보면 verifierKey 가 랜덤으로 생성된 것이다

 

때문에 key를 생성해주는게 좋다. 다음 설정을 추가해주자.

    public JwtAccessTokenConverter accessTokenConverter () {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("12345");
        try {
            converter.afterPropertiesSet();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return converter;
    }

 

그럼 이제 호출해보면 잘 통과되는걸 확인할 수 있었다.

 

 

끝.

반응형

댓글