코딩마을방범대
Java에서 QR 코드 만들기 본문
QR 이란?
- 흑백 격자무늬 패턴으로 정보를 나타내는 매트릭스 형식의 이차원 바코드를 의미
- QR코드는 주로 한국,일본,중국,영국,미국 등에서 많이 사용되며,
명칭은 덴소 웨이브의 등록 상표 'Quick Response' 에서 유래됨
특징
- 종래에 많이 쓰이던 바코드의 용량 제한을 극복하고 그 형식과 내용을 확장한 2차원의 바코드로
종횡의 정보를 가져서 숫자 외에 문자의 데이터를 저장할 수 있음 - 숫자 최대 7089자, 영문자와 숫자 최대 4296자, 이진 8비트 최대 2953바이트, 한자 1817자를 담을 수 있음
QR과 바코드의 차이점
QR | 바코드 | |
장점 |
|
|
단점 |
|
|
QR코드의 원리
1. 포지셔닝 감지 마커 (Positioning detection markers)
- 감지 마커를 이용하여 QR 코드를 인식하여 빠른 속도로 스캔 가능
- 스캐너로 QR 코드를 스캔했을 때 주변 사물에서 QR 코드가 어디에 있는지 확인 가능
- QR 코드를 똑바로 스캔하지 않아도 방향 인식 가능
2. 정렬 표시 (Alignment markings)
- QR 코드가 구부러진 표면 위에 있어도 바르게 읽을 수 있게 해줌
- 더 많은 정보를 QR 코드에 저장할 수록 더 큰 Alignment Pattern이 필요함
3. 타이밍 패턴 (Timing pattern)
- QR 스캐너는 반복되는 검정색/흰색 패턴을 확인하여
QR 코드의 데이터 영역이 얼마나 큰지 파악함
4. 버전 정보 (Version information)
- 40개의 QR 코드 버전이 존재함
- 해당 마크로 어떤 버전이 사용되고 있는지 확인함
5. 형식 정보 (Format information)
- 에러 오차, 데이터 마스크 패턴 정보를 포함하고 있어
QR 코드를 더 쉽게 스캔할 수 있게 해줌
6. 데이터 및 오류 수정 키 (Data and Error correction keys)
- 에러 정정 메커니즘은 QR 코드의 데이터 영역에 모두 포함되어 있음
- QR 코드의 30%가 훼손되어도 에러 정정 블락을 사용하여 스캔 가능
7. 조용한 지역? (Quiet Zone)
- QR 코드를 둘러싸고 있는 영역
- QR 코드를 주변 사물로부터 QR코드의 영역을 확보하게 해줌
- 필수로 필요한 부분임
QR 코드를 생성할 수 있는 방법에는 여러가지가 있지만,
Java에서 GoogleAuthenticator을 이용해 QR 코드를 생성하는 방법을 알아볼 것이다!
환경 설정
build.gradle
// https://mvnrepository.com/artifact/com.warrenstrange/googleauth
implementation 'com.warrenstrange:googleauth:1.5.0'
Configuration
@RequiredArgsConstructor
@Configuration
public class GAuthenticatorConfig {
// ICredentialRepository를 상속받은 클래스
private final CredentialBiz credentialBiz;
@Bean
public GoogleAuthenticator googleAuthenticator()
GoogleAuthenticatorConfigBuilder googleAuthenticatorConfigBuilder = new GoogleAuthenticatorConfigBuilder()
// 인증코드 유효시간 설정
.setTimeStepSizeInMillis( TimeUnit.SECONDS.toMillis( 60 ) )
// window size를 유지할 시간
.setWindowSize(3)
// verification code 자릿수 설정
.setCodeDigits(6))
// 스크래치 코드 수 설정
.setNumberOfScratchCodes(5);
// 설정한 cofing 대로 GoogleAuthenticator 생성
GoogleAuthenticator gAuth = new GoogleAuthenticator(googleAuthenticatorConfigBuilder.build());
gAuth.setCredentialRepository(credentialBiz);
return gAuth;
}
}
ICredentialRepository를 상속받는 비즈니스 로직 클래스
@RequiredArgsConstructor
@Component
public class CredentialBiz implements ICredentialRepository {
private final CredentialRepository credentialRepository;
private final CredentialScratchRepository credentialScratchRepository;
@Override
public void saveUserCredentials(String userKey, String secretKey, int validationCode, List<Integer> scratchCodes) {
}
@Override
public String getSecretKey(String userKey) {
}
}
saveUserCredentials()
GoogleAuth 자격 인증서를 만들기 위해 필요한 GoogleAuthenticator 클래스를 살펴보면,
createCredentials 메소드를 호출할 때 인자를 받지 않는 메소드와 인자로 userName을 받는 메소드 두 개가 존재한다.
두 메소드의 역할 차이는
인자가 빈 메소드는 GoogleAuthenticatorKey를 만들어 리턴하고,
userName을 받는 메소드는 인자가 빈 메소드를 호출해 Key값을 받은 후 repository에 저장 후 리턴해준다.
여기서 repository가 바로 ICredentialRepository 를 상속받은 클래스다.
Credentials을 저장할 Entity를 만들어 프로젝트에 맞게 저장해주는 로직을 짜면 된다!
@Override
public GoogleAuthenticatorKey createCredentials(){
// Allocating a buffer sufficiently large to hold the bytes required by
// the secret key.
int bufferSize = config.getSecretBits() / 8;
byte[] buffer = new byte[bufferSize];
secureRandom.nextBytes(buffer);
// Extracting the bytes making up the secret key.
byte[] secretKey = Arrays.copyOf(buffer, bufferSize);
String generatedKey = calculateSecretKey(secretKey);
// Generating the verification code at time = 0.
int validationCode = calculateValidationCode(secretKey);
// Calculate scratch codes
List<Integer> scratchCodes = calculateScratchCodes();
return new GoogleAuthenticatorKey
.Builder(generatedKey)
.setConfig(config)
.setVerificationCode(validationCode)
.setScratchCodes(scratchCodes)
.build();
}
@Override
public GoogleAuthenticatorKey createCredentials(String userName){
// Further validation will be performed by the configured provider.
if (userName == null){
throw new IllegalArgumentException("User name cannot be null.");
}
GoogleAuthenticatorKey key = createCredentials();
ICredentialRepository repository = getValidCredentialRepository();
repository.saveUserCredentials(
userName,
key.getKey(),
key.getVerificationCode(),
key.getScratchCodes());
return key;
}
getSecretKey()
위와 비슷하게 repository를 통해 SecretKey를 가져오는 로직을 짜면 된다.
GoogleAuthenticator를 통해 검증할 때 사용한다. ( 아래 코드 참고 )
@Override
public boolean authorizeUser(String userName, int verificationCode, long time){
ICredentialRepository repository = getValidCredentialRepository();
return authorize(repository.getSecretKey(userName), verificationCode, time);
}
@Override
public boolean authorize(String secret, int verificationCode, long time){
// Checking user input and failing if the secret key was not provided.
if (secret == null){
throw new IllegalArgumentException("Secret cannot be null.");
}
// Checking if the verification code is between the legal bounds.
if (verificationCode <= 0 || verificationCode >= this.config.getKeyModulus()){
return false;
}
// Checking the validation code using the current UNIX time.
return checkCode(
secret,
verificationCode,
time,
this.config.getWindowSize());
}
QR URL 생성하기
StringBuilder issuer = new StringBuilder();
issuer.append("발급자 정보")
.append("&변수명=")
.append("담고싶은 문자열");
GoogleAuthenticatorKey authenticatorKey = googleAuthenticator.createCredentials();
String url = GoogleAuthenticatorQRGenerator.getOtpAuthTotpURL(issuer.toString(), "유니크한 아이디", authenticatorKey);
메소드 | 인자 | 설명 |
getOtpAuthTotpURL | String issuer, String accountName, GoogleAuthenticatorKey credentials |
이메일, QR 코드 또는 기타 방법을 통해 사용자에게 전송될 수 있습니다. 이 URI에는 비밀이 포함되어 있으므로 보안 전송을 사용하십시오. |
getOtpAuthURL | Google Authenticator 애플리케이션에 로드할 QR 바코드를 생성하기 위해 Google Chart API 호출의 URL을 반환합니다. 사용자는 스마트폰의 애플리케이션으로 이 바코드를 스캔하거나 수동으로 암호를 입력합니다. |
인증서를 만들 때 인수로 userName 을 주면 위에서 설정한 바와 같이 repository에 저장된다.
이후 서버에서 생성한 URL을 기반으로 프론트 화면에 표시해주면 된다.
리액트를 사용할 경우 qrcode.react 패키지를 설치해주면 된다.
아래 같은 방식으로 value에 URL을 넣어주면 된다.
<QRCode className='qr'
value={qrUrl}
size={128}
/>
QR 검증
QR 검증을 위해선 모바일에서 Google Auth 앱을 설치해줘야한다고 한다.
※ 앱 개발 시에 Google 서비스에 등록하면 Google Auth 앱 의존성을 추가할 수 있다.
Google Auth 앱에서 생성된 QR 코드를 인식하여 생성되는 code 값을 통해 검증할 수 있다.
혹은
repository를 통해 저장된 code값으로 검증해도 된다!
참고사이트
QR코드 발명 이유와 원리, 장점, 단점, 바코드 비교
'💡 백엔드 > Java' 카테고리의 다른 글
[Java] String, StringBuffer, StringBuilder의 차이점 (0) | 2023.07.28 |
---|---|
자바에서 유니크한 값 가져오기 ( UUID / random ) (0) | 2023.07.27 |
Java의 Reflection 기능을 사용하는 방법 (0) | 2023.07.26 |
Java에서 OS의 시스템에 접근하기 (0) | 2023.07.25 |
SpringBoot에서 Slack Webhoook 사용하기 (0) | 2023.07.25 |