경력에 대해 고민하는 이들에게 도움이 되었길.
'logbook' 카테고리의 다른 글
| 20160717: DDD Start, KSUG & 지앤선 북콘서트 (0) | 2016.07.17 |
|---|---|
| 20160714; RecruitingDay: Developer (0) | 2016.07.15 |
| (0).5 M/M 의 신화 (0) | 2016.05.11 |
| 20160411 - 퇴사 (2) | 2016.04.12 |
| 사업에 대한 조언하는 엔지니어가 되려면 (0) | 2016.03.31 |
| 20160717: DDD Start, KSUG & 지앤선 북콘서트 (0) | 2016.07.17 |
|---|---|
| 20160714; RecruitingDay: Developer (0) | 2016.07.15 |
| (0).5 M/M 의 신화 (0) | 2016.05.11 |
| 20160411 - 퇴사 (2) | 2016.04.12 |
| 사업에 대한 조언하는 엔지니어가 되려면 (0) | 2016.03.31 |
스프링 시큐리티를 이용해서 애플리케이션의 로그인처리를 하는 예제는 많이 있다. 거기에 OTP(One Time Password) 를 함께 사용하여 보안을 조금 더 강화해보도록 하겠다. 보통 TOTP(Time based One-time Password) 라고도 하는데 사용자에게 할당된 비밀키SecretKey와 OTP 킷에서 시간을 기준으로 생성하는 검증코드(Verification Code) 를 조합하여 인증가능한지 여부를 확인하는 것이다. 이때 사용자에게 할당되는 비밀키는 일회성으로 요청에 따라 새롭게 발급하는 식으로 운영이 된다.
보통은 issuer + username + secretKey 를 이용하여 등록가능한 QR코드로 애플리케이션에 등록하는 형식을 취하게 된다.
QR리더를 이용해서 담겨있는 내용을 읽어보면 다음과 같다:
otpauth://totp/{issuer}:{username}?secret={secretKey}&issuer={issuer}의 형식을 취한다. 이때 생성되는 secretKey는 일회성이다. 만약 외부에 노출될 경우에는 새롭게 secretKey를 발급받고 기존에 등록하 OTP 정보를 삭제하고 다시 등록해줘야 한다.
사용자에게 비밀번호를 변경하라(?)는 부담을 주지 않아도 된다. OTP 용 비밀키만 재발급하면 된다. 응?
2단계 인증과 관련한 내용은 구글의 2단계인증 과정에 대한 설명을 살펴보시면 더욱 좋을 듯 하다.
그럼 예제를 살펴보도록 하자.
Spring security 2step verification
Spring Boot: Spring Data JPA + Spring Security + Spring Web + Thymeleaf + Google Auth
h2databse 는 기본구성이 메모리로 실행된다.
application.yml 설정spring:
datasource:
initialize: true # data.sql 을 이용한 DB 초기화 작업
mail:
default-encoding: UTF-8
username: #${username}
password: #${password}
host: smtp.gmail.com
port: 587
protocol: smtp
properties:
mail.smtp.starttls.enable: true
mail.smtp.auth: true
h2: # jdbc ur: jdbc:h2:mem:testdb
console: # http://localhost:8080/h2-console
enabled: trueUserServiceImpl@Override
public User insert(User user) {
GoogleAuthenticatorKey key = googleAuthenticator.createCredentials();
sendTOTPRegistrationMail(user, key);
user.setOtpSecretKey(key.getKey());
encodePassword(user);
return repository.save(user);
}
private void sendTOTPRegistrationMail(User user, GoogleAuthenticatorKey key) {
String qrCodeUrl = GoogleAuthenticatorQRGenerator.getOtpAuthURL(ISSUER, user.getUsername(), key);
Map<String, Object> attributes = new HashMap<>();
attributes.put("qrCodeUrl", qrCodeUrl);
MailMessage mailMessage = MailMessage.builder()
.templateName(MailMessage.OTP_REGISTRATION)
.to(new String[]{user.getUsername()})
.subject("Honeymon TOTP Registration mail")
.attributes(attributes)
.build();
mailService.send(mailMessage);
}위의 코드를 보면 알겠지만 사용자 엔티티를 생성하는 순간에 인증키를 생성하여 secretKey를 사용자 엔티티에 할당하고, 그와 동시에 등록된 사용자 계정으로 메일을 발송한다.
사용자 계정생성 -> OTP 비밀키 생성 및 할당 -> OTP 등록 QR코드를 내장한 메일 발송 -> 사용자: OTP 디바이스에 등록 -> 로그인시 사용자명/비밀번호/OTP 검증코드
로그인때 입력하는 검증코드는 일반적으로 6자리를 사용하며 일정시간동안만 유효성을 가진다.
그 결과는 다음과 같으며,
이 QR코드를 Google OTP 앱에서 읽어들이면 다음과 같이 추가된다.
이제 OTP 코드는 일정간격으로 갱신된다.
갱신되는 타이밍이 맞지 않으면 인증실패가 발생한다.
정상적으로 로그인하면 다음과 같은 화면을 볼 수 있다.
2단계 인증은 비밀번호와 단말기(스마트폰, OTP 킷)가 있어야 로그인을 할 수 있다. 서버쪽에서 계정정보가 유출된다면, 바로 OTP용 비밀키를 초기화하고 사용자에게 재등록하도록 안내를 하면 계정이 도용되는 사례를 쉽게 막을 수 있는 장점을 제공한다.
2단계 인증을… 제대로 활용할 수 있다면 꽤 쓸만할 듯 하다.
구글에서 공개한 C로 작성된 Google Authenticator 를 자바로 구현한 오픈소스 프로젝트다.
| untitled Preview java.lang.IllegalStateException: Cannot call sendRedirect() after the response has been committed (0) | 2016.09.11 |
|---|---|
| 20160806 Spring MVC 에서 엑셀다운로드 기능 구현하기 (0) | 2016.08.06 |
| [thymeleaf] dialect 정의하기 (0) | 2016.06.10 |
| [스프링] 컨트롤러 내에서 NullPointException 발생, @ControllerAdvice로 잡은 것이 안먹히네 (0) | 2016.01.13 |
| [스프링] 스프링 프레임워크 이야기... (1) | 2015.11.06 |
타임리프의 확장은 쉽다. 방언(다이얼렉트,Dialect)를 생성하고 템플릿엔진에 추가하면 된다.
타임리프 다이얼렉트는 템플릿에서 사용할 수 있는 기능이다.
다음과 같이 동작하는 다이얼렉트를 작성해보자.
Hi ya!
모든 다이얼렉트 는 IDialect 를 구현해야한다. 이를 용이하게 할 수 있도록AbstractDialect 을 이용한다. IProcessorDialect 는 실제로 Dialect 가
HelloDialect| 뭔가~ 다른 것들을 추가할 수 있을 것 같지 않은가?? |
그럼 이제 이 다이얼렉트가 호출하여 실체 로직처리를 수행할 프로세스를 작성해보자.
SayToProcessor작성한 다이얼렉트 를 템플릿엔진에 추가하자.
@Beanpublic SpringTemplateEngine {SpringTemplateEngine engine = new SpringTemplateEngine();engine.;engine.;engine.;return engine;}
을 구현하고 나면!! 끝이 난다. 실제로 화면을 불러와 보면
<p>Hello, Jake!</p>
으로 출력되는 것을 볼 수 있을 것이다.
| 20160806 Spring MVC 에서 엑셀다운로드 기능 구현하기 (0) | 2016.08.06 |
|---|---|
| [spring security] 스프링 시큐리티 + OTP 를 이용한 2단계 인증 예제 (3) | 2016.06.19 |
| [스프링] 컨트롤러 내에서 NullPointException 발생, @ControllerAdvice로 잡은 것이 안먹히네 (0) | 2016.01.13 |
| [스프링] 스프링 프레임워크 이야기... (1) | 2015.11.06 |
| asciidoc 으로 작성해본 스프링 애플리케이션 개발 프로세스 및 분석 방법 (0) | 2015.11.02 |
최근 팀 커뮤니케이션으로 많은 사람들의 사랑을 받고 있는 슬랙(Slack).
슬랙에서는 외부에서 슬랙채널에 메시지를 보낼 수 있는 WebHook API를 제공하고 있다. 웹훅은 슬랙으로 데이터를 보내는 Incoming WebHook 과 특정조건에 부합되었을 때 외부의 데이터를 가져오는 Outgoing WebHook 이 있다.
웹애플리케이션에서 슬랙채널로 메시지를 보내는 것은 Incoming WebHook을 이용하게 된다.
그러기 위해서는 우선 팀슬랙에 Incomming WebHook을 설정한다.
NOTE |
|
작업을 진행하기에 앞서서 채널을 하나 개설한다. 그후 통합Integration 으로 이동하여 'incoming webhook' 을 검색하여 설치하고 채널을 지정한다. 필요하다면 아이콘을 변경하는 작업을 한다. 화면에 나오는 웹훅 URL 을 복사해둔다.
스프링부트 프로젝트를 생성한다.
build.gradlebuildscript {
ext {
springBootVersion = '1.3.5.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'spring-boot'
jar {
baseName = 'slack-incoming-webhook'
version = '0.0.1-SNAPSHOT'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile('org.projectlombok:lombok:1.16.8')
compile('org.springframework.boot:spring-boot-starter-web')
compile('com.google.guava:guava:19.0')
testCompile('org.springframework.boot:spring-boot-starter-test')
}WebConfigurationpackage io.honeymon.springboot.slack.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class WebConfiguration {
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
}SlackNotifierpackage io.honeymon.springboot.slack.integration;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* Slack Notifier
*
* @author honeymon
*
*/
@Slf4j
@Component
public class SlackNotifier {
@Autowired
private RestTemplate restTemplate;
public enum SlackTarget {
// TODO webHookUrl 은 자신의 슬랙 IncomingWebHookAPI로 변경하세요.
CH_INCOMING("https://hooks.slack.com/services/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", "incoming");
String webHookUrl;
String channel;
SlackTarget(String webHookUrl, String channel) {
this.webHookUrl = webHookUrl;
this.channel = channel;
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static class SlackMessageAttachement {
private String color;
private String pretext;
private String title;
private String title_link;
private String text;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static class SlackMessage {
private String text;
private String channel;
private List<SlackMessageAttachement> attachments;
void addAttachment(SlackMessageAttachement attachement) {
if (this.attachments == null) {
this.attachments = Lists.newArrayList();
}
this.attachments.add(attachement);
}
}
public boolean notify(SlackTarget target, SlackMessageAttachement message) {
log.debug("Notify[target: {}, message: {}]", target, message);
SlackMessage slackMessage = SlackMessage.builder().channel(target.channel)
.attachments(Lists.newArrayList(message)).build();
try {
restTemplate.postForEntity(target.webHookUrl, slackMessage, String.class);
return true;
} catch (Exception e) {
log.error("Occur Exception: {}", e);
return false;
}
}
}SlackSenderControllerpackage io.honeymon.springboot.slack.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import io.honeymon.springboot.slack.integration.SlackNotifier;
import io.honeymon.springboot.slack.integration.SlackNotifier.SlackMessageAttachement;
import io.honeymon.springboot.slack.integration.SlackNotifier.SlackTarget;
@RestController
public class SlackSendController {
@Autowired
private SlackNotifier slackNotifier;
@RequestMapping(value = "/", method = RequestMethod.POST)
public ResponseEntity<Boolean> send(@RequestBody SlackMessageAttachement message) { (1)
return ResponseEntity.ok(slackNotifier.notify(SlackTarget.CH_INCOMING, message));
}
}POST ` 방식으로 전송을 할 때 `@RequestBody 로 클래스를 정의하면 자동으로 매핑된다.
slack-incoming-webhook 실행$ git clone https://github.com/ihoneymon/slack-incoming-webhook
$ cd slack-incoming-webhook
$ ./gradlew springboot팀채널로 많이 사용하는 슬랙.
배포한 앱에서 중요한 사항(항상 상태를 체크해야하는 상황)에 대해서 슬랙 채널로 공지하도록 하는 기능을 간단하게 구현해봤다. @_@)> 생각보다 쉽다. 많이.
예제에서는 컨트롤러에서 요청을 받아서 처리하는 방식으로 구현했다.
| 부트 스프링 부트(Boot Spring Boot) 쓰기 시작 (2) | 2016.08.07 |
|---|---|
| [springboot] 스프링부트 개발가이드 작성 시작 (0) | 2016.07.21 |
| [springboot] 앱 프로세스ID(pid) 관리 (0) | 2016.04.07 |
| [스프링부트] 빌드시 깃 커밋버전 정보 포함시키기 (0) | 2016.02.26 |
| [스프링부트] 생성물 위치 (0) | 2016.02.24 |
이클립스에서 스프링부트 기반으로 개발하던 환경을 벗어나니 너무너무 낯설기만 하다.
지금 진행하고 있는 프로젝트를 war 로 배포하려고 하면서 전과는 다른 개발방식 때문에 이런저런 새로운 상황들이 벌어져서 나를 당황하도록 만든다.
@Entity 선언한 엔티티 객체를 생성한 후 테스트를 위해 실행하려는 순간 다음과 같은 문제가 발생한다.
이 에러를 뱉으며 애플리케이션이 실행되지 않는다. 이와 관련된 문제를 인터넷을 통해 찾아본 결과, 이 문제의 답은 역시나 스택오버플로우에서 찾았다.
이와 관련해서 스프링부트 프로젝트에서도 이슈로 등록되어 논의가 있었지만…
인텔리제이의 버그로 정리가 된 듯 싶다. @_@)>
스프링부트를 https://start.spring.io 를 통해서 프로젝트를 생성하면 프로젝트에서 기본내장컨테이너가providedRuntime('org.springframework.boot:spring-boot-starter-tomcat')로 선언이 된다. providedCompile 는 compile, runtime 에서는 동일한 스코프로 적용이 되지만 war 로 빌드될 때는 제외된다.
providedXXX는 이행성 설정이다. 어떤 라이브러리가 provided로 설정되면 그것이 의존하는 다른 라이브러리도 자동으로 provided가 된다. 강제로 compile 로 지정해도 상관없다.
providedCompile 로 정의가 된 spring-boot-starter-tomcat 의존성과 관련된 부분들이 war 에서 제외되는 상황이 생긴다.
org.apache.tomcat.embedtomcat-embed-coreorg.apache.tomcat.embedtomcat-embed-elorg.apache.tomcat.embedtomcat-embed-logging-juliorg.apache.tomcat.embedtomcat-embed-websocket
위의 내용은 org.springframework.boot : spring-boot-starter-tomcat : 1.3.5.RELEASE 에서 확인가능하다.
providedRuntime('org.springframework.boot:spring-boot-starter-tomcat')으로 인해서 tomcat-embed-el 도 제외가 되어 el 처리를 위한 구현체가 없어서 하이버네이트 validator 에서 예외를 뱉는 것이다. ㅡ_-)== 해결방법
compile("javax.el:javax.el-api:2.2.5")
을 추가하면 정상적으로 실행이 된다…. 뭘까? ㅡ_-)?
혹은
providedRuntime('org.springframework.boot:spring-boot-starter-tomcat')
를 제거하면 된다. javax.el-api 혹은 org.apache.tomcat.embed:tomcat-embed-el 를 추가하는 것으러 처리가 될 것이다.
흠냐릿!!
| [ide] intellij 에서 Java “lambda expressions not supported at this language level” 표현이 뜰때 (0) | 2016.05.22 |
|---|