Translate

2020년 7월 1일 수요일

[Vue.js][Axios] Request/Response Console Log






개발 시 Ajax에 대한 Request/Response 콘솔로그를 쉽게 볼 수 있다면 디버깅하기에 편할 것으로 예상이 되어 아래와 같이 axios 를 통해 Ajax 요청 시 응답에 대한 콘솔로그를 제작했다.


/**
 * Axios 응답
 * @param res Response||error 객체
 * @private
 */
function _responseLog(res) {
  var response = res.response ? res.response : res;
  var color = response.status === 200 ? '#58FA58' : '#FA5858';
  var status = response.status ? response.status : res.message;
  console.groupCollapsed(`[Axios] %c[${status}]`, `background-color:${color}; color:black`, `- ${res.config.url}`);
  console.info(`url: ${res.config.url}\nmethod: ${res.config.method}\n`);
  console.info('[Request]\n',
    '\theaders: ', res.config.headers || '', '\n',
    '\tparams: ', res.config.params || '', '\n',
    '\tdata: ', res.config.data || '');
  console.info('[Response]\n',
    '\theaders: ', response.headers || '', '\n',
    '\tdata: ', response.data || '');
  console.groupEnd();
}

const service = axios.create({
  baseURL: baseUrl,
  timeout: request.timeout
});

/**
 * Axios의 Response 인터셉터
 */
service.interceptors.response.use(
  response => {
    _responseLog(response);
    return res;
  },
  error => {
    _responseLog(error);
    return Promise.reject(error);
  }
);


아래와 같이 콘솔로그가 보여지며 꽤  편하게 쓰고 있다.
1004lucifer
1004lucifer


2020년 6월 27일 토요일

[Vue.js] methods 함수 안에서의 this에 대한 고찰 (this를 찾을 수 없는경우)






methods의 함수 내에서 this가 없어서 수행이 안되거나,
프로그램 실행은 되는데 개발자 도구에서 디버깅모드로 this를 참조하려고 할 시 this가 undefied로 나오는 경우가 있다.



Vue.js methods 함수내에서 this를 사용하기위해 주의할 점에 대해서 알아봤다.






이슈

1004lucifer
async 키워드가 들어간 함수에서 console.log 를 이용하여 this 객체를 로그로 남겼는데, 로그는 정상적으로 찍히지만 디버깅모드에서 this 객체가 'undefied' 로 나오는것 있었다.

실제로는 this 객체가 없지만 브라우저에서 버그가 있거나 문제없게 수정을 해주는걸까 싶어서 원인을 알아보기 시작했다.







소스

1004lucifer

export default {
  name: 'App',
  components: {
    HelloWorld
  },
  data: function() {
    return {
      testData1: 'testData1',
      testData2: 'testData2'
    }
  },
  created() {
    console.log('created');
  },
  mounted: function() {
    this.test1();
    this.test2();
    this.test3();
    this.test4();
    this.test5();
    this.test6();
    this.test7();
  },
  methods: {
    // 일반적인 함수 생성
    test1: function() {
      // eslint-disable-next-line no-debugger
      debugger;
      console.log('test1: ', this);
    },

    // 함수에 async 키워드 추가
    test2: async function() {
      // eslint-disable-next-line no-debugger
      debugger;
      console.log('test2: ', this);
    },

    // ECMAScript2015 - 단축 구문 함수 생성
    test3() {
      // eslint-disable-next-line no-debugger
      debugger;
      console.log('test3: ', this);
    },

    // ECMAScript2015 - 단축 구문 함수 생성 (async 키워드 추가)
    async test4() {
      // eslint-disable-next-line no-debugger
      debugger;
      console.log('test4: ', this);
    },

    // Arrow Function 지정
    test5: () => {
      // eslint-disable-next-line no-debugger
      debugger;
      console.log('test5: ', this);
    },


    callbackTest: function(obj) {
      // eslint-disable-next-line no-debugger
      debugger;
      obj.callback(obj.data);
    },
    test6: function() {
      this.callbackTest({
        data: this.testData1,
        // 콜백함수 정상적으로 생성
        callback: function(res) {
          // eslint-disable-next-line no-debugger
          debugger;
          console.log(res, this);
        }
      });
    },
    test7: function() {
      this.callbackTest({
        data: this.testData2,
        // 콜백함수 Arrow Function 으로 생성
        callback: (res) => {
          // eslint-disable-next-line no-debugger
          debugger;
          console.log(res, this);
        }
      });
    },
    test8: function() {
      // this 객체를 복사
      var context = this;
      this.callbackTest({
        data: this.testData2,
        callback: function(res) {
          // eslint-disable-next-line no-debugger
          debugger;
          console.log(res, context);
        }
      });
    },
  }
}




분석


1. Vue 프로젝트 빌드


프로젝트 빌드 시 아래와 같이 dist 디렉토리 하위에 빌드된 파일이 생성이 된다.
1004lucifer



2. 각 함수별 빌드파일과 비교




test1 함수

- 내용이 동일하다.
// ====== 원래 소스 ======
test1: function() {
  console.log('test1: ', this);
}

// ===== 빌드된 소스 =====
test1: function() {
	console.log("test1: ", this)
}



test2
1004lucifer
- async 키워드로 인해 기존의 내용이 wrapping 되었으며, 해당 객체에 this 객체를 똑같이 맞춰준 후 돌려준다.
그렇기 때문에 수행 시 Local 영역에 this 객체가 들어있다.
// ====== 원래 소스 ======
test2: async function() {
  console.log('test2: ', this);
}

// ===== 빌드된 소스 =====
test2: function() {
	var t = Object(a["a"])(regeneratorRuntime.mark((function t() {
		return regeneratorRuntime.wrap((function(t) {
			while (1)
				switch (t.prev = t.next) {
				case 0:
					console.log("test2: ", this);
				case 2:
				case "end":
					return t.stop()
				}
		}
		), t, this)
	}
	)));
	function e() {
		return t.apply(this, arguments)
	}
	return e
}()



test3

- 내용이 동일하다.
// ====== 원래 소스 ======
test3() {
  console.log('test3: ', this);
}

// ===== 빌드된 소스 =====
test3: function() {
	console.log("test3: ", this)
}



test4
1004lucifer
- this 객체를 t 라는 변수에 담아서 내부에서 t 객체를 사용하고 있다.
개발자도구에서는 Closure(test4) 에서 this를 사용하는 것으로 보여진다.
// ====== 원래 소스 ======
async test4() {
  console.log('test4: ', this);
}

// ===== 빌드된 소스 =====
test4: function() {
	var t = this;
	return Object(a["a"])(regeneratorRuntime.mark((function e() {
		return regeneratorRuntime.wrap((function(e) {
			while (1)
				switch (e.prev = e.next) {
				case 0:
					console.log("test4: ", t);
				case 2:
				case "end":
					return e.stop()
				}
		}), e)
	})))()
}



test5

- 빌드되면서 this 자리에 엉뚱한게 들어가 버렸다.

* Vue 공식홈페이지에서 화살표 함수를 사용 시 부모 컨텍스트를 바인딩한다고 사용하지 말라고 기술되어 있다.

* 여기서 상위객체의 h가 어떤 것인지 확인이 가능하다.
// ====== 원래 소스 ======
test5: () => {
  console.log('test5: ', this);
}

// ===== 빌드된 소스 =====
test5: function() {
	console.log("test5: ", h)
}



test6

- 내용이 동일하며, callback 함수에서 수행하는 this는 test6 함수의 this와 다른것을 확인 할 수 있다.
// ====== 원래 소스 ======
test6: function() {
  this.callbackTest({
	data: this.testData1,
	callback: function(res) {
	  console.log(res, this);
	}
  });
}

// ===== 빌드된 소스 =====
callbackTest: function(t) {
	t.callback(t.data)
},
test6: function() {
	this.callbackTest({
		data: this.testData1,
		callback: function(t) {
			console.log(t, this)
		}
	})
}



test7

- callback 함수에 화살표 함수를 사용했더니 부모 컨텍스트(test7)에 바인딩되어 this를 사용 할 수 있게 되었다.
// ====== 원래 소스 ======
test7: function() {
  this.callbackTest({
	data: this.testData2,
	callback: (res) => {
	  console.log(res, this);
	}
  });
}

// ===== 빌드된 소스 =====
callbackTest: function(t) {
	t.callback(t.data)
},
test7: function() {
	var t = this;
	this.callbackTest({
		data: this.testData2,
		callback: function(e) {
			console.log(e, t)
		}
	})
}



test8

- 화살표 함수를 사용하지 않고 test7과 같은 효과를 줄 수 있다.
// ====== 원래 소스 ======
test8: function() {
  var context = this;
  this.callbackTest({
	data: this.testData2,
	callback: function(res) {
	  console.log(res, context);
	}
  });
}

// ===== 빌드된 소스 =====
callbackTest: function(t) {
	t.callback(t.data)
},
test8: function () {
	var t = this;
	this.callbackTest({
		data: this.testData2, callback: function (e) {
			console.log(e, t)
		}
	})
}





결론


어떤 방식으로 함수를 작성하느냐에 따라 Vue객체의 this를 사용할수도 있고 사용하지 못할 수도 있다는걸 알게 되었다.

우선 Vue 공식홈페이지에 있는 예제 코드는 모두 function 키워드가 붙은 정석적인 방법으로 사용이 되어있었다.

Babel이 트랜스컴파일링 하게될 때 상황에 따라 의도치 않게 코드가 나와버려서 Vue 공식홈페이지에 있는 방식대로 코딩을 권장하고 싶긴하지만, 많은 IDE 툴에서 ES6 단축 함수 형식으로 자동완성을 해주고 있기에 그렇게 하기도 좀 애매하기도 하다.

주위 개발자에겐 this 문제가 발생한경우 function 키워드를 넣는 방식으로 코드를 수정해 보라고 가이드를 할 예정이다.




PS.
얼핏 알고있는 내용을 정리하면서 확실하게 개념을 확립한것도 그렇고, 크롬 개발자도구의 Scope 항목에 Closure가 나오는 의미에 대해서 알게될 수 있어서 좋은 포스팅이 되었다.



익명함수 및 클로저 개념


2020년 4월 27일 월요일

[Spring Boot][OAuth2][Facebook] Cannot deserialize instance of `java.lang.String` out of START_OBJECT token 오류 원인





Framework: Spring Boot 2.x


증상

다른 SNS 에서는 문제가 없는데 Facebook 에서만 아래와 같은 오류가 발생하면서 정상적으로 로그인이 되지 않는다.

1004lucifer


org.springframework.http.converter.HttpMessageNotReadableException: An error occurred reading the OAuth 2.0 Error: JSON parse error: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token
 at [Source: (sun.net.www.protocol.http.HttpURLConnection$HttpInputStream); line: 1, column: 10] (through reference chain: java.util.LinkedHashMap["error"]); nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token
 at [Source: (sun.net.www.protocol.http.HttpURLConnection$HttpInputStream); line: 1, column: 10] (through reference chain: java.util.LinkedHashMap["error"])
at org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter.readInternal(OAuth2ErrorHttpMessageConverter.java:79) ~[spring-security-oauth2-core-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter.readInternal(OAuth2ErrorHttpMessageConverter.java:47) ~[spring-security-oauth2-core-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.http.converter.AbstractHttpMessageConverter.read(AbstractHttpMessageConverter.java:199) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler.handleError(OAuth2ErrorResponseErrorHandler.java:59) ~[spring-security-oauth2-client-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.web.client.ResponseErrorHandler.handleError(ResponseErrorHandler.java:63) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:776) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:734) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:645) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService.loadUser(DefaultOAuth2UserService.java:108) ~[spring-security-oauth2-client-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at com.project.security.oauth2.CustomOAuth2UserService.loadUser(CustomOAuth2UserService.java:26) ~[classes/:na]
at org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider.authenticate(OAuth2LoginAuthenticationProvider.java:116) ~[spring-security-oauth2-client-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:175) ~[spring-security-core-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter.attemptAuthentication(OAuth2LoginAuthenticationFilter.java:186) ~[spring-security-oauth2-client-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter.doFilterInternal(OAuth2AuthorizationRequestRedirectFilter.java:160) ~[spring-security-oauth2-client-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:74) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.31.jar:9.0.31]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.31.jar:9.0.31]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.31.jar:9.0.31]
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:94) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.31.jar:9.0.31]
at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:114) ~[spring-boot-actuator-2.1.13.RELEASE.jar:2.1.13.RELEASE]
at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:104) ~[spring-boot-actuator-2.1.13.RELEASE.jar:2.1.13.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.31.jar:9.0.31]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.catalina.valves.RemoteIpValve.invoke(RemoteIpValve.java:747) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:367) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1639) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.31.jar:9.0.31]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_241]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_241]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.31.jar:9.0.31]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_241]
Caused by: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token
 at [Source: (sun.net.www.protocol.http.HttpURLConnection$HttpInputStream); line: 1, column: 10] (through reference chain: java.util.LinkedHashMap["error"])
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:245) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:227) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter.readInternal(OAuth2ErrorHttpMessageConverter.java:74) ~[spring-security-oauth2-core-5.1.8.RELEASE.jar:5.1.8.RELEASE]
... 71 common frames omitted
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token
 at [Source: (sun.net.www.protocol.http.HttpURLConnection$HttpInputStream); line: 1, column: 10] (through reference chain: java.util.LinkedHashMap["error"])
at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63) ~[jackson-databind-2.9.10.3.jar:2.9.10.3]
at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1343) ~[jackson-databind-2.9.10.3.jar:2.9.10.3]
at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1139) ~[jackson-databind-2.9.10.3.jar:2.9.10.3]
at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1093) ~[jackson-databind-2.9.10.3.jar:2.9.10.3]
at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:63) ~[jackson-databind-2.9.10.3.jar:2.9.10.3]
at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:10) ~[jackson-databind-2.9.10.3.jar:2.9.10.3]
at com.fasterxml.jackson.databind.deser.std.MapDeserializer._readAndBindStringKeyMap(MapDeserializer.java:527) ~[jackson-databind-2.9.10.3.jar:2.9.10.3]
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:364) ~[jackson-databind-2.9.10.3.jar:2.9.10.3]
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:29) ~[jackson-databind-2.9.10.3.jar:2.9.10.3]
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4014) ~[jackson-databind-2.9.10.3.jar:2.9.10.3]
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3085) ~[jackson-databind-2.9.10.3.jar:2.9.10.3]
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:239) ~[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
... 73 common frames omitted

1004lucifer
  ...


2020-04-27 18:58:41.687 DEBUG 16420 --- [nio-8080-exec-7] o.s.s.access.vote.AffirmativeBased       : Voter: org.springframework.security.web.access.expression.WebExpressionVoter@72982a80, returned: -1
2020-04-27 18:58:41.688 DEBUG 16420 --- [nio-8080-exec-7] o.s.s.w.a.ExceptionTranslationFilter     : Access is denied (user is anonymous); redirecting to authentication entry point

org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84) ~[spring-security-core-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:233) ~[spring-security-core-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:124) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) ~[spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137) [spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111) [spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:170) [spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) [spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:200) [spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103) [spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]






원인 및 해결방법


아래의 부분을 '아니오' 로 변경 후 다시 테스트해보니 정상적으로 동작했다.






2020년 4월 3일 금요일

[Youtube][WEB] iPhone 에서 유투브 영상 클릭 시 Full Screen 안되게 차단 방법





이슈


 웹에서 유투브 재생 시 영상을 건너뛰지 못하게 controls=0 (링크) 셋팅을 했다.

 Android 에서는 정상적으로 작동하지만 iPhone 에서는 영상을 누르자마자 전체화면으로 바뀌면서 영상을 컨트롤 할 수 있었다.



1004lucifer, 풀스크린, 아이폰

해결방법


 iOS에서 HTML5 플레이어에서 동영상을 인라인으로 재생할지 여부를 선택하는 옵션이 있었다.
  playsinline=1 (링크)




 - playsinline 옵션 없는경우





 - playsinline=1 옵션 지정




2020년 4월 1일 수요일

[Spring Boot Admin] WAS 구동 시 발생하는 'InstanceAlreadyExistsException: com.zaxxer.hikari:name=dataSource,type=HikariDataSource' 에러




Spring Boot 2.x 사용



문제

WAS 구동 시 아래와 같은 에러메시지가 발생하며 웹페이지가 뜨지 않았다.


2020-04-01 18:53:09.673  INFO 19792 --- [com-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1321 ms
2020-04-01 18:53:10.219  INFO 19792 --- [com-startStop-1] o.s.b.a.e.web.ServletEndpointRegistrar   : Registered '/actuator/jolokia' to jolokia-actuator-endpoint
2020-04-01 18:53:10.854  INFO 19792 --- [com-startStop-1] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2020-04-01 18:53:10.930  INFO 19792 --- [com-startStop-1] o.s.b.a.w.s.WelcomePageHandlerMapping    : Adding welcome page template: index
2020-04-01 18:53:11.372  INFO 19792 --- [com-startStop-1] o.s.s.c.ThreadPoolTaskScheduler          : Initializing ExecutorService
2020-04-01 18:53:11.489  INFO 19792 --- [com-startStop-1] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 16 endpoint(s) beneath base path '/actuator'
2020-04-01 18:53:11.544  INFO 19792 --- [com-startStop-1] ConditionEvaluationReportLoggingListener :

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2020-04-01 18:53:11.549 ERROR 19792 --- [com-startStop-1] o.s.boot.SpringApplication               : Application run failed

org.springframework.jmx.export.UnableToRegisterMBeanException: Unable to register MBean [HikariDataSource (null)] with key 'dataSource'; nested exception is javax.management.InstanceAlreadyExistsException: com.zaxxer.hikari:name=dataSource,type=HikariDataSource
        at org.springframework.jmx.export.MBeanExporter.registerBeanNameOrInstance(MBeanExporter.java:626) ~[spring-context-5.1.14.RELEASE.jar:5.1.14.RELEASE]
        at org.springframework.jmx.export.MBeanExporter.lambda$registerBeans$2(MBeanExporter.java:552) ~[spring-context-5.1.14.RELEASE.jar:5.1.14.RELEASE]
        at java.util.HashMap.forEach(HashMap.java:1289) ~[na:1.8.0_241]
        at org.springframework.jmx.export.MBeanExporter.registerBeans(MBeanExporter.java:552) ~[spring-context-5.1.14.RELEASE.jar:5.1.14.RELEASE]
        at org.springframework.jmx.export.MBeanExporter.afterSingletonsInstantiated(MBeanExporter.java:435) ~[spring-context-5.1.14.RELEASE.jar:5.1.14.RELEASE]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:864) ~[spring-beans-5.1.14.RELEASE.jar:5.1.14.RELEASE]
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877) ~[spring-context-5.1.14.RELEASE.jar:5.1.14.RELEASE]
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549) ~[spring-context-5.1.14.RELEASE.jar:5.1.14.RELEASE]
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:141) ~[spring-boot-2.1.13.RELEASE.jar:2.1.13.RELEASE]
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:744) ~[spring-boot-2.1.13.RELEASE.jar:2.1.13.RELEASE]
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:391) ~[spring-boot-2.1.13.RELEASE.jar:2.1.13.RELEASE]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:312) ~[spring-boot-2.1.13.RELEASE.jar:2.1.13.RELEASE]
        at org.springframework.boot.web.servlet.support.SpringBootServletInitializer.run(SpringBootServletInitializer.java:151) [spring-boot-2.1.13.RELEASE.jar:2.1.13.RELEASE]
        at org.springframework.boot.web.servlet.support.SpringBootServletInitializer.createRootApplicationContext(SpringBootServletInitializer.java:131) [spring-boot-2.1.13.RELEASE.jar:2.1.13.RELEASE]
        at org.springframework.boot.web.servlet.support.SpringBootServletInitializer.onStartup(SpringBootServletInitializer.java:91) [spring-boot-2.1.13.RELEASE.jar:2.1.13.RELEASE]
        at org.springframework.web.SpringServletContainerInitializer.onStartup(SpringServletContainerInitializer.java:172) [spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
        at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5154) [catalina.jar:8.5.51]
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) [catalina.jar:8.5.51]
        at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:743) [catalina.jar:8.5.51]
        at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:719) [catalina.jar:8.5.51]
        at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:705) [catalina.jar:8.5.51]
        at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:970) [catalina.jar:8.5.51]
        at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1841) [catalina.jar:8.5.51]
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_241]
        at java.util.concurrent.FutureTask.run(FutureTask.java:266) [na:1.8.0_241]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_241]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_241]
        at java.lang.Thread.run(Thread.java:748) [na:1.8.0_241]
Caused by: javax.management.InstanceAlreadyExistsException: com.zaxxer.hikari:name=dataSource,type=HikariDataSource
        at com.sun.jmx.mbeanserver.Repository.addMBean(Repository.java:437) ~[na:1.8.0_241]
        at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerWithRepository(DefaultMBeanServerInterceptor.java:1898) ~[na:1.8.0_241]
        at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerDynamicMBean(DefaultMBeanServerInterceptor.java:966) ~[na:1.8.0_241]
        at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerObject(DefaultMBeanServerInterceptor.java:900) ~[na:1.8.0_241]
        at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerMBean(DefaultMBeanServerInterceptor.java:324) ~[na:1.8.0_241]
        at com.sun.jmx.mbeanserver.JmxMBeanServer.registerMBean(JmxMBeanServer.java:522) ~[na:1.8.0_241]
        at org.springframework.jmx.support.MBeanRegistrationSupport.doRegister(MBeanRegistrationSupport.java:138) ~[spring-context-5.1.14.RELEASE.jar:5.1.14.RELEASE]
        at org.springframework.jmx.export.MBeanExporter.registerBeanInstance(MBeanExporter.java:672) ~[spring-context-5.1.14.RELEASE.jar:5.1.14.RELEASE]
        at org.springframework.jmx.export.MBeanExporter.registerBeanNameOrInstance(MBeanExporter.java:616) ~[spring-context-5.1.14.RELEASE.jar:5.1.14.RELEASE]
        ... 27 common frames omitted

2020-04-01 18:53:11.551  WARN 19792 --- [com-startStop-1] o.s.b.f.support.DisposableBeanAdapter    : Invocation of destroy method failed on bean with name 'tomcatMetricsBinder': java.lang.NullPointerException
2020-04-01 18:53:11.552  INFO 19792 --- [com-startStop-1] o.s.s.c.ThreadPoolTaskScheduler          : Shutting down ExecutorService
2020-04-01 18:53:11.553  INFO 19792 --- [com-startStop-1] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'
01-Apr-2020 18:53:11.555 SEVERE [video.aquapick-event.com-startStop-1] org.apache.catalina.core.ContainerBase.addChildInternal ContainerBase.addChild: start:
        org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[video.aquapick-event.com].StandardContext[]]
                at org.apache.catalina.util.LifecycleBase.handleSubClassException(LifecycleBase.java:440)
                at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:198)
                at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:743)
                at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:719)
                at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:705)
                at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:970)
                at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1841)
                at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
                at java.util.concurrent.FutureTask.run(FutureTask.java:266)
                at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
                at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
                at java.lang.Thread.run(Thread.java:748)
        Caused by: org.springframework.jmx.export.UnableToRegisterMBeanException: Unable to register MBean [HikariDataSource (null)] with key 'dataSource'; nested exception is javax.management.InstanceAlreadyExistsException: com.zaxxer.hikari:name=dataSource,type=HikariDataSource
                at org.springframework.jmx.export.MBeanExporter.registerBeanNameOrInstance(MBeanExporter.java:626)
                at org.springframework.jmx.export.MBeanExporter.lambda$registerBeans$2(MBeanExporter.java:552)
                at java.util.HashMap.forEach(HashMap.java:1289)
                at org.springframework.jmx.export.MBeanExporter.registerBeans(MBeanExporter.java:552)
                at org.springframework.jmx.export.MBeanExporter.afterSingletonsInstantiated(MBeanExporter.java:435)
                at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:864)
                at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877)
                at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549)
                at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:141)
                at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:744)
                at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:391)
                at org.springframework.boot.SpringApplication.run(SpringApplication.java:312)
                at org.springframework.boot.web.servlet.support.SpringBootServletInitializer.run(SpringBootServletInitializer.java:151)
                at org.springframework.boot.web.servlet.support.SpringBootServletInitializer.createRootApplicationContext(SpringBootServletInitializer.java:131)
                at org.springframework.boot.web.servlet.support.SpringBootServletInitializer.onStartup(SpringBootServletInitializer.java:91)
                at org.springframework.web.SpringServletContainerInitializer.onStartup(SpringServletContainerInitializer.java:172)
                at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5154)
                at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
                ... 10 more
        Caused by: javax.management.InstanceAlreadyExistsException: com.zaxxer.hikari:name=dataSource,type=HikariDataSource
                at com.sun.jmx.mbeanserver.Repository.addMBean(Repository.java:437)
                at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerWithRepository(DefaultMBeanServerInterceptor.java:1898)
                at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerDynamicMBean(DefaultMBeanServerInterceptor.java:966)
                at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerObject(DefaultMBeanServerInterceptor.java:900)
                at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerMBean(DefaultMBeanServerInterceptor.java:324)
                at com.sun.jmx.mbeanserver.JmxMBeanServer.registerMBean(JmxMBeanServer.java:522)
                at org.springframework.jmx.support.MBeanRegistrationSupport.doRegister(MBeanRegistrationSupport.java:138)
                at org.springframework.jmx.export.MBeanExporter.registerBeanInstance(MBeanExporter.java:672)
                at org.springframework.jmx.export.MBeanExporter.registerBeanNameOrInstance(MBeanExporter.java:616)
                ... 27 more
01-Apr-2020 18:53:11.562 SEVERE [video.aquapick-event.com-startStop-1] org.apache.catalina.startup.HostConfig.deployWAR Error deploying web application archive [/home/1004lucifer/apps/apache-tomcat-8.5.51/webapps/urban/event/ROOT.war]
        java.lang.IllegalStateException: ContainerBase.addChild: start: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[video.aquapick-event.com].StandardContext[]]
                at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:747)
                at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:719)
                at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:705)
                at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:970)
                at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1841)
                at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
                at java.util.concurrent.FutureTask.run(FutureTask.java:266)
                at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
                at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
                at java.lang.Thread.run(Thread.java:748)
01-Apr-2020 18:53:11.563 INFO [video.aquapick-event.com-startStop-1] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [/home/1004lucifer/apps/apache-tomcat-8.5.51/webapps/urban/event/ROOT.war] has finished in [5,277] ms
01-Apr-2020 18:53:11.564 WARNING [main] org.apache.catalina.mapper.MapperListener.findDefaultHost Unknown default host [localhost] for service [StandardService[Catalina]]. Tomcat will not be able process HTTP/1.0 requests that do not specify a host name.
01-Apr-2020 18:53:11.569 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"]
01-Apr-2020 18:53:11.585 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["https-jsse-nio-8443"]
01-Apr-2020 18:53:11.588 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in 16541 ms






원인

스택오버플로우 확인 시 jmx 충돌로 인해 발생하는 문제로 보여졌다.


그러고보니 문제가 발생한 시점이..

1개의 서버에 아래와 같이 두개의 애플리케이션이 올라가 있는 상태에서..
 - Spring Boot Admin (Server)
 - Spring Boot Admin (Client)

신규 Spring Boot Admin (Client) 를 추가하고 바로 발생했다.



1004lucifer


해결방법

JMX 이름을 다르게해서 다시 배포를 하니 정상적으로 동작되는 것을 확인 할 수 있었다.
(Spring Boot 2.1.13 기준)


spring:
  jmx:
    unique-names: true
management:
  endpoints:
    jmx:
      domain: co.kr.1004lucifer





참고:
 - https://stackoverflow.com/questions/51798003/javax-management-instancealreadyexistsexception-com-zaxxer-hikariname-datasour/51798043


2020년 3월 11일 수요일

[Let's Encrypt] 우분투 Tomcat 에 무료 SSL (와일드카드)인증서 설치방법





톰캣에 설치할 무료 SSL인증서를 알아보니 대부분 3개월 기간으로만 제공을 하는데..
Let's Encrypt에서는 Cron을 이용하여 자동연장이 가능하기에 작업을 시작했다.

공식 사이트: https://letsencrypt.org/ko/


실습환경: Ubuntu 18.04


1. certbot 설치


ubuntu@instance-1004lucifer:~$
ubuntu@instance-1004lucifer:~$ certbot
# certbot이 설치되지 않은상태
Command 'certbot' not found, but can be installed with:

sudo apt install certbot

ubuntu@instance-1004lucifer:~$
ubuntu@instance-1004lucifer:~$
ubuntu@instance-1004lucifer:~$
ubuntu@instance-1004lucifer:~$ sudo apt-get update
Hit:1 http://ap-seoul-1-ad-1.clouds.archive.ubuntu.com/ubuntu bionic InRelease
Get:2 http://security.ubuntu.com/ubuntu bionic-security InRelease [88.7 kB]
Hit:3 http://ppa.launchpad.net/certbot/certbot/ubuntu bionic InRelease
Get:4 http://ap-seoul-1-ad-1.clouds.archive.ubuntu.com/ubuntu bionic-updates InRelease [88.7 kB]
Get:5 http://ap-seoul-1-ad-1.clouds.archive.ubuntu.com/ubuntu bionic-backports InRelease [74.6 kB]
Get:6 http://ap-seoul-1-ad-1.clouds.archive.ubuntu.com/ubuntu bionic-updates/main amd64 Packages [871 kB]
Get:7 http://ap-seoul-1-ad-1.clouds.archive.ubuntu.com/ubuntu bionic-updates/universe amd64 Packages [1055 kB]
Get:8 http://ap-seoul-1-ad-1.clouds.archive.ubuntu.com/ubuntu bionic-updates/universe Translation-en [326 kB]
Fetched 2505 kB in 3s (798 kB/s)
Reading package lists... Done
ubuntu@instance-1004lucifer:~$
ubuntu@instance-1004lucifer:~$
ubuntu@instance-1004lucifer:~$
ubuntu@instance-1004lucifer:~$ sudo apt-get install software-properties-common
Reading package lists... Done
Building dependency tree
Reading state information... Done
software-properties-common is already the newest version (0.96.24.32.12).
The following package was automatically installed and is no longer required:
  grub-pc-bin
Use 'sudo apt autoremove' to remove it.
0 upgraded, 0 newly installed, 0 to remove and 42 not upgraded.
ubuntu@instance-1004lucifer:~$
ubuntu@instance-1004lucifer:~$
ubuntu@instance-1004lucifer:~$
ubuntu@instance-1004lucifer:~$ sudo add-apt-repository ppa:certbot/certbot
 This is the PPA for packages prepared by Debian Let's Encrypt Team and backported for Ubuntu.

Note: Packages are only provided for currently supported Ubuntu releases.
 More info: https://launchpad.net/~certbot/+archive/ubuntu/certbot
Press [ENTER] to continue or Ctrl-c to cancel adding it.

Hit:1 http://security.ubuntu.com/ubuntu bionic-security InRelease
Hit:2 http://ppa.launchpad.net/certbot/certbot/ubuntu bionic InRelease
Hit:3 http://ap-seoul-1-ad-1.clouds.archive.ubuntu.com/ubuntu bionic InRelease
Hit:4 http://ap-seoul-1-ad-1.clouds.archive.ubuntu.com/ubuntu bionic-updates InRelease
Hit:5 http://ap-seoul-1-ad-1.clouds.archive.ubuntu.com/ubuntu bionic-backports InRelease
Reading package lists... Done
ubuntu@instance-1004lucifer:~$
ubuntu@instance-1004lucifer:~$
ubuntu@instance-1004lucifer:~$
ubuntu@instance-1004lucifer:~$ sudo apt-get install certbot
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following package was automatically installed and is no longer required:
  grub-pc-bin
Use 'sudo apt autoremove' to remove it.
The following additional packages will be installed:
  python3-acme python3-certbot python3-configargparse python3-future python3-icu python3-josepy python3-mock python3-ndg-httpsclient python3-parsedatetime python3-pbr python3-requests-toolbelt python3-rfc3339 python3-tz
  python3-zope.component python3-zope.event python3-zope.hookable
Suggested packages:
  python3-certbot-apache python3-certbot-nginx python-certbot-doc python-acme-doc python-future-doc python-mock-doc
The following NEW packages will be installed:
  certbot python3-acme python3-certbot python3-configargparse python3-future python3-icu python3-josepy python3-mock python3-ndg-httpsclient python3-parsedatetime python3-pbr python3-requests-toolbelt python3-rfc3339 python3-tz
  python3-zope.component python3-zope.event python3-zope.hookable
0 upgraded, 17 newly installed, 0 to remove and 42 not upgraded.
Need to get 1113 kB of archives.
After this operation, 5884 kB of additional disk space will be used.
Do you want to continue? [Y/n] y
Get:1 http://ap-seoul-1-ad-1.clouds.archive.ubuntu.com/ubuntu bionic/main amd64 python3-pbr all 3.1.1-3ubuntu3 [53.8 kB]
Get:2 http://ppa.launchpad.net/certbot/certbot/ubuntu bionic/main amd64 python3-josepy all 1.1.0-2+ubuntu18.04.1+certbot+1 [27.8 kB]

  생략...

Setting up python3-josepy (1.1.0-2+ubuntu18.04.1+certbot+1) ...
Setting up python3-tz (2018.3-2) ...
Setting up python3-parsedatetime (2.4-3+ubuntu18.04.1+certbot+3) ...
Setting up python3-rfc3339 (1.0-4) ...
Setting up python3-zope.component (4.3.0-1+ubuntu18.04.1+certbot+3) ...
Setting up python3-acme (0.31.0-2+ubuntu18.04.3+certbot+2) ...
Setting up python3-certbot (0.31.0-1+ubuntu18.04.1+certbot+1) ...
Setting up certbot (0.31.0-1+ubuntu18.04.1+certbot+1) ...
Created symlink /etc/systemd/system/timers.target.wants/certbot.timer → /lib/systemd/system/certbot.timer.
certbot.service is a disabled or a static unit, not starting it.
Processing triggers for man-db (2.8.3-2ubuntu0.1) ...
ubuntu@instance-1004lucifer:~$
ubuntu@instance-1004lucifer:~$
ubuntu@instance-1004lucifer:~$ certbot
#정상적으로 설치완료
The following error was encountered:
[Errno 13] Permission denied: '/var/log/letsencrypt'
Either run as root, or set --config-dir, --work-dir, and --logs-dir to writeable paths.
ubuntu@instance-1004lucifer:~$






2. 인증서 생성


ubuntu@instance-1004lucifer:~$ sudo certbot certonly --manual --preferred-challenges dns -d "1004lucifer.co.kr" -d "*.1004lucifer.co.kr"
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator manual, Installer None
Obtaining a new certificate
Performing the following challenges:
dns-01 challenge for 1004lucifer.co.kr

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NOTE: The IP of this machine will be publicly logged as having requested this
certificate. If you're running certbot in manual mode on a machine that is not
your server, please ensure you're okay with that.

Are you OK with your IP being logged?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: y

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name
_acme-challenge.1004lucifer.co.kr with the following value:

M7dc7nmfVKAXWdDZ5b2H1vIw8iAD82FKdfPGAi0YOCA

Before continuing, verify the record is deployed.
# 이부분에서 DNS의 TXT에 해당 도메인과 문자열을 넣어주고 잠시 후 엔터를 입력한다.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue
Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/1004lucifer.co.kr/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/1004lucifer.co.kr/privkey.pem
   Your cert will expire on 2020-06-08. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot
   again. To non-interactively renew *all* of your certificates, run
   "certbot renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

ubuntu@instance-1004lucifer:~$
ubuntu@instance-1004lucifer:~$
ubuntu@instance-1004lucifer:~$ ll /etc/letsencrypt/
total 40
drwxr-xr-x  9 root root 4096 Mar 11 17:12 ./
drwxr-xr-x 97 root root 4096 Mar 11 19:57 ../
drwx------  3 root root 4096 Mar  9 20:16 accounts/
drwx------  3 root root 4096 Mar 10 13:47 archive/
-rw-r--r--  1 root root  121 Feb 10  2019 cli.ini
drwxr-xr-x  2 root root 4096 Mar 11 16:36 csr/
drwx------  2 root root 4096 Mar 11 16:36 keys/
drwx------  3 root root 4096 Mar 10 13:47 live/
drwxr-xr-x  2 root root 4096 Mar 11 16:36 renewal/
drwxr-xr-x  5 root root 4096 Mar  9 20:16 renewal-hooks/
ubuntu@instance-1004lucifer:~$
# 인증서 디렉토리 root 외 사용자에게 읽기권한 부여
ubuntu@instance-1004lucifer:~$ sudo chmod 755 /etc/letsencrypt/live/
ubuntu@instance-1004lucifer:~$
ubuntu@instance-1004lucifer:~$
ubuntu@instance-1004lucifer:~$ ll /etc/letsencrypt/
total 40
drwxr-xr-x  9 root root 4096 Mar 11 17:12 ./
drwxr-xr-x 97 root root 4096 Mar 11 19:57 ../
drwx------  3 root root 4096 Mar  9 20:16 accounts/
drwx------  3 root root 4096 Mar 10 13:47 archive/
-rw-r--r--  1 root root  121 Feb 10  2019 cli.ini
drwxr-xr-x  2 root root 4096 Mar 11 16:36 csr/
drwx------  2 root root 4096 Mar 11 16:36 keys/
drwx--x--x  3  711 root 4096 Mar 10 13:47 live/
drwxr-xr-x  2 root root 4096 Mar 11 16:36 renewal/
drwxr-xr-x  5 root root 4096 Mar  9 20:16 renewal-hooks/
ubuntu@instance-1004lucifer:~$
ubuntu@instance-1004lucifer:~$
ubuntu@instance-1004lucifer:~$
ubuntu@instance-1004lucifer:~$ ll /etc/letsencrypt/live/1004lucifer.co.kr/
total 28
drwxr-xr-x 2 root root 4096 Mar 11 16:36 ./
drwxr-xr-x 3  755 root 4096 Mar 10 13:47 ../
-rw-r--r-- 1 root root  692 Mar 10 13:47 README
lrwxrwxrwx 1 root root   41 Mar 11 16:36 cert.pem -> ../../archive/1004lucifer.co.kr/cert2.pem
lrwxrwxrwx 1 root root   42 Mar 11 16:36 chain.pem -> ../../archive/1004lucifer.co.kr/chain2.pem
lrwxrwxrwx 1 root root   46 Mar 11 16:36 fullchain.pem -> ../../archive/1004lucifer.co.kr/fullchain2.pem
lrwxrwxrwx 1 root root   44 Mar 11 16:36 privkey.pem -> ../../archive/1004lucifer.co.kr/privkey2.pem

ubuntu@instance-1004lucifer:~$



Cafe24에서 다음과 같이 작업을 했다.
(PS. 처음에 호스트명에 입력란이 보여지지 않아 Cafe24에 문의를 해보니 첫 등록 시 상담게시판을 이용하여 등록요청을 후 한번 등록이 되면 그 이후에 입력란이 보여진다.)






3. 톰캣용 인증서 생성


ubuntu@instance-1004lucifer:~$
ubuntu@instance-1004lucifer:~$ sudo bash
root@instance-1004lucifer:~#
root@instance-1004lucifer:~#
root@instance-1004lucifer:~# cd /etc/letsencrypt/live/1004lucifer.co.kr/
root@instance-1004lucifer:/etc/letsencrypt/live/1004lucifer.co.kr#
root@instance-1004lucifer:/etc/letsencrypt/live/1004lucifer.co.kr#
root@instance-1004lucifer:/etc/letsencrypt/live/1004lucifer.co.kr# openssl pkcs12 -export -in fullchain.pem -inkey privkey.pem -out cert_and_key.p12 -CAfile chain.pem -caname root -passout pass:password -name root
root@instance-1004lucifer:/etc/letsencrypt/live/1004lucifer.co.kr#
root@instance-1004lucifer:/etc/letsencrypt/live/1004lucifer.co.kr# ll
total 20
drwxr-xr-x 2 root root 4096 Mar 11 21:42 ./
drwxr-xr-x 3  755 root 4096 Mar 10 13:47 ../
-rw-r--r-- 1 root root  692 Mar 10 13:47 README
lrwxrwxrwx 1 root root   41 Mar 11 16:36 cert.pem -> ../../archive/1004lucifer.co.kr/cert2.pem
-rw------- 1 root root 4294 Mar 11 21:42 cert_and_key.p12
lrwxrwxrwx 1 root root   42 Mar 11 16:36 chain.pem -> ../../archive/1004lucifer.co.kr/chain2.pem
lrwxrwxrwx 1 root root   46 Mar 11 16:36 fullchain.pem -> ../../archive/1004lucifer.co.kr/fullchain2.pem
lrwxrwxrwx 1 root root   44 Mar 11 16:36 privkey.pem -> ../../archive/1004lucifer.co.kr/privkey2.pem
root@instance-1004lucifer:/etc/letsencrypt/live/1004lucifer.co.kr#
root@instance-1004lucifer:/etc/letsencrypt/live/1004lucifer.co.kr#
root@instance-1004lucifer:/etc/letsencrypt/live/1004lucifer.co.kr# keytool -delete -alias root -keystore keyStore.jks -importkeystore -srcstorepass password -destkeystore keyStore.jks -srckeystore cert_and_key.p12 -srcstoretype PKCS12 -storepass password
Importing keystore cert_and_key.p12 to keyStore.jks...
Entry for alias root successfully imported.
Import command completed:  1 entries successfully imported, 0 entries failed or cancelled

Warning:
The JKS keystore uses a proprietary format. It is recommended to migrate to PKCS12 which is an industry standard format using "keytool -importkeystore -srckeystore keyStore.jks -destkeystore keyStore.jks -deststoretype pkcs12".
root@instance-1004lucifer:/etc/letsencrypt/live/1004lucifer.co.kr#
root@instance-1004lucifer:/etc/letsencrypt/live/1004lucifer.co.kr#
root@instance-1004lucifer:/etc/letsencrypt/live/1004lucifer.co.kr# ll
total 24
drwxr-xr-x 2 root root 4096 Mar 11 21:42 ./
drwxr-xr-x 3  755 root 4096 Mar 10 13:47 ../
-rw-r--r-- 1 root root  692 Mar 10 13:47 README
lrwxrwxrwx 1 root root   41 Mar 11 16:36 cert.pem -> ../../archive/1004lucifer.co.kr/cert2.pem
-rw------- 1 root root 4294 Mar 11 21:42 cert_and_key.p12
lrwxrwxrwx 1 root root   42 Mar 11 16:36 chain.pem -> ../../archive/1004lucifer.co.kr/chain2.pem
lrwxrwxrwx 1 root root   46 Mar 11 16:36 fullchain.pem -> ../../archive/1004lucifer.co.kr/fullchain2.pem
-rw-r--r-- 1 root root 3932 Mar 11 21:42 keyStore.jks
lrwxrwxrwx 1 root root   44 Mar 11 16:36 privkey.pem -> ../../archive/1004lucifer.co.kr/privkey2.pem
root@instance-1004lucifer:/etc/letsencrypt/live/1004lucifer.co.kr#






4. 톰캣에 인증서 연동


ubuntu@instance-1004lucifer:~$
ubuntu@instance-1004lucifer:~$ cat apps/apache-tomcat-8.5.51/conf/server.xml
<?xml version="1.0" encoding="UTF-8"?>

    생략...

    <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
               maxThreads="150" SSLEnabled="true">
        <SSLHostConfig>
        <Certificate certificateKeystoreFile="/etc/letsencrypt/live/1004lucifer.co.kr/keyStore.jks"
                 certificateKeystorePassword="password"
                         type="RSA" />
        </SSLHostConfig>
    </Connector>

    생략...

</Server>
ubuntu@instance-1004lucifer:~$


여기까지 작업 후 톰캣을 재구동하면 HTTPS 접속이 될 것이다.


아래와 같이 사이트 접속 후 인증서가 적용됨을 확인 할 수 있다.












4. 인증서 자동갱신 셋팅

Let's Encrypt 의 인증서는 3달마다 갱신을 해줘야 한다.
리눅스 명령어를 이용해 인증서를 발급받았으니..
쉘을 만들어서 주기적으로 자동갱신을 하도록 만들어 주자.

정상적으로 갱신되는지 여부 확인해보지 못함.
추후 확인예정


ubuntu@instance-1004lucifer:~$
ubuntu@instance-1004lucifer:~$ sudo bash
root@instance-1004lucifer:~#
root@instance-1004lucifer:~#
root@instance-1004lucifer:~#
# 스크립트를 생성한다.
root@instance-1004lucifer:~# cat > /etc/letsencrypt/live/1004lucifer.co.kr/extend.sh
#!/bin/bash

cd /etc/letsencrypt/live/1004lucifer.co.kr/

certbot renew --quiet

openssl pkcs12 -export -in fullchain.pem -inkey privkey.pem -out cert_and_key.p12 -CAfile chain.pem -caname root -passout pass:password -name root

keytool -delete -alias root -storepass password -keystore keyStore.jks

keytool -importkeystore -srcstorepass password -destkeystore keyStore.jks -srckeystore cert_and_key.p12 -srcstoretype PKCS12 -storepass password

^C  (Ctrl + C)
root@instance-1004lucifer:~#
root@instance-1004lucifer:~#
root@instance-1004lucifer:~#
root@instance-1004lucifer:~# cat /etc/letsencrypt/live/1004lucifer.co.kr/extend.sh
#!/bin/bash

cd /etc/letsencrypt/live/1004lucifer.co.kr/

certbot renew --quiet

openssl pkcs12 -export -in fullchain.pem -inkey privkey.pem -out cert_and_key.p12 -CAfile chain.pem -caname root -passout pass:password -name root

keytool -delete -alias root -storepass password -keystore keyStore.jks


keytool -importkeystore -srcstorepass password -destkeystore keyStore.jks -srckeystore cert_and_key.p12 -srcstoretype PKCS12 -storepass password

root@instance-1004lucifer:~#
root@instance-1004lucifer:~#
root@instance-1004lucifer:~#
# 크론에 스크립트를 등록해 준다.
root@instance-1004lucifer:~# crontab -e
crontab: installing new crontab
root@instance-1004lucifer:~#
root@instance-1004lucifer:~#
root@instance-1004lucifer:~# crontab -l
# Edit this file to introduce tasks to be run by cron.
#
# Each task to run has to be defined through a single line
# indicating with different fields when the task will be run
# and what command to run for the task
#
# To define the time you can provide concrete values for
# minute (m), hour (h), day of month (dom), month (mon),
# and day of week (dow) or use '*' in these fields (for 'any').#
# Notice that tasks will be started based on the cron's system
# daemon's notion of time and timezones.
#
# Output of the crontab jobs (including errors) is sent through
# email to the user the crontab file belongs to (unless redirected).
#
# For example, you can run a backup of all your user accounts
# at 5 a.m every week with:
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
#
# For more information see the manual pages of crontab(5) and cron(8)
#
# m h  dom mon dow   command

# 1004lucifer
# LetsEncrypt SSL
0 0 1 * * /etc/letsencrypt/live/1004lucifer.co.kr/extend.sh
root@instance-1004lucifer:~#
root@instance-1004lucifer:~#



Ubuntu, 우분투, Tomcat, 톰캣, SSL, https, 인증서, 와일드카드, wildcard, 생성, 설치


참고
 - https://hiseon.me/server/letsencrypt-wildcard-certificate/
 - https://partnerjun.tistory.com/60