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>