1
1
.
.
1
1
0
0
.
.
2
2
S
S
t
t
e
e
p
p
2
2
:
:
R
R
e
e
g
g
i
i
s
s
t
t
e
e
r
r
I
I
n
n
f
f
o
o
[
[
G
G
]
]
This tutorial shows how to create Endpoint that
registers User for Google 2FA Service
displays HTML Page that shows Google QR Code (needed to initialize Mobile App to display Temporary Code)
Application Schema [Results]
SecurityConfig
AccountService
AccountLoader
AccountRepository
Account
http://localhost:8080/Configure
Tomcat
configure()
Browser
MyController
Configure.html
SecurityBeans
S
S
h
h
o
o
w
w
Q
Q
R
R
C
C
o
o
d
d
e
e
Edit File: pom.xml (add Maven Dependency for googleauth)
Edit Class: Account.java (add Properties related to Google 2FA)
Create Class: GoogleCredentialRepository.java (inside package config)
Create Class: SecurityBeans.java (inside package config)
Edit Class: MyController.java (inside package controllers)
Create File: Configure.html (inside directory resources/templates)
pom.xml
<dependency>
<groupId>com.warrenstrange</groupId>
<artifactId>googleauth</artifactId>
<version>1.5.0</version>
</dependency>
Account.java
package com.ivoronline.springboot_security_2fa.entities;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer id;
public String username;
public String password;
public String role;
public String google2faSecretKey;
public Boolean google2faEnabled = false;
public Boolean google2faAuthenticated = false;
}
GoogleCredentialRepository.java
package com.ivoronline.springboot_security_2fa.config;
import com.ivoronline.springboot_security_2fa.entities.Account;
import com.ivoronline.springboot_security_2fa.repositories.AccountRepository;
import com.warrenstrange.googleauth.ICredentialRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.List;
@RequiredArgsConstructor
@Component
public class GoogleCredentialRepository implements ICredentialRepository {
private final AccountRepository accountRepository;
//======================================================================
// SAVE USER CREDENTIALS (STORE SECRET KEY)
//======================================================================
@Override
public void saveUserCredentials(String userName, String secretKey, int validationCode, List<Integer>
scratchCodes) {
//GET ACCOUNT
Account account = accountRepository.findByUsername(userName);
account.google2faSecretKey = secretKey;
account.google2faEnabled = true;
//STORE ACCOUNT
accountRepository.save(account);
}
//======================================================================
// GET SECRET KEY
//======================================================================
@Override
public String getSecretKey(String userName) {
//GET ACCOUNT
Account account = accountRepository.findByUsername(userName);
//RETURN SECRET KEY
return account.google2faSecretKey;
}
}
SecurityBeans.java
package com.ivoronline.springboot_security_2fa.config;
import com.warrenstrange.googleauth.GoogleAuthenticator;
import com.warrenstrange.googleauth.GoogleAuthenticatorConfig;
import com.warrenstrange.googleauth.ICredentialRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
@Configuration
public class SecurityBeans {
@Bean
public GoogleAuthenticator googleAuthenticator(ICredentialRepository credentialRepository){
GoogleAuthenticatorConfig.GoogleAuthenticatorConfigBuilder configBuilder = new
GoogleAuthenticatorConfig.GoogleAuthenticatorConfigBuilder();
configBuilder
.setTimeStepSizeInMillis(TimeUnit.SECONDS.toMillis(30))
.setWindowSize(10)
.setNumberOfScratchCodes(0);
GoogleAuthenticator googleAuthenticator = new GoogleAuthenticator(configBuilder.build());
googleAuthenticator.setCredentialRepository(credentialRepository);
return googleAuthenticator;
}
}
MyController.java
package com.ivoronline.springboot_security_2fa.controllers;
import com.ivoronline.springboot_security_2fa.repositories.AccountRepository;
import com.warrenstrange.googleauth.GoogleAuthenticator;
import com.warrenstrange.googleauth.GoogleAuthenticatorKey;
import com.warrenstrange.googleauth.GoogleAuthenticatorQRGenerator;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequiredArgsConstructor
public class MyController {
private final AccountRepository accountRepository;
private final GoogleAuthenticator googleAuthenticator;
//======================================================================
// CONFIGURE (SERVICE & MOBILE APP)
//======================================================================
@RequestMapping("/Configure")
public String configure(Model model){
//GET USERNAME
User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String username = user.getUsername();
//CREATES NEW KEY IN DB (everytime it is called)
GoogleAuthenticatorKey key = googleAuthenticator.createCredentials(username);
//CONSTRUCT URL FOR QRCODE IMAGE
String googleURL = GoogleAuthenticatorQRGenerator.getOtpAuthURL("something", username, key);
//ADD GOOGLE URL AS INPUT PARAMETER TO HTML PAGE
model.addAttribute("googleURL", googleURL);
//RETURN CONFIRMATION HTML PAGE
return "Configure";
}
//======================================================================
// Hello
//======================================================================
@ResponseBody
@RequestMapping("/hello")
public String sayHello() {
return "Hello from Controller";
}
}
Configure.html
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<p> <img th:src="${googleURL}"/> </p>
R
R
e
e
s
s
u
u
l
l
t
t
s
s
http://localhost:8080/Configure
You get redirected to http://localhost:8080/login
Username: john
Password: johnpassword
Sign in
You get redirected back to http://localhost:8080/Configure (shows QRCode)
Scan QRCode with Google Authenticator (Mobile Phone App)
It should show a Temporary Code: 267 189
http://localhost:8080/login - john - johnpassword
http://localhost:8080/Configure Scanning with Google Authenticator Mobile App
Application Structure
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.warrenstrange</groupId>
<artifactId>googleauth</artifactId>
<version>1.5.0</version>
</dependency>
</dependencies>