SpringBoot-Google⼆步验证
SpringBoot-Google⼆步验证
概念:Google⾝份验证器Google Authenticator是⾕歌推出的基于时间的⼀次性密码(Time-based One-time Password,简称TOTP),只需要在⼿机上安装该APP,就可以⽣成⼀个随着时间变化的⼀次性密码,⽤于帐户验证。
Google⾝份验证器是⼀款基于时间与哈希的⼀次性密码算法的两步验证软件令牌,此软件⽤于Google的认证服务。此项服务所使⽤的算法已列于RFC 6238和RFC 4226中。
⼀、流程
⽤户请求服务器⽣成密钥
服务器⽣成⼀个密钥并与⽤户信息进⾏关联,并返回密钥(类似:XX57HWC7D2FA4X4GLOHOASTGPMVI5EFA)和⼀个⼆维码信息(此步骤还没有绑定)
⽤户把返回的⼆维码信息传给服务器,⽣成⼀个⼆维码
信息⼤概长这样的:otpauth://totp/https%3A%2F%p%3Arstyro?
secret=XX57HWC7D2FA4X4GLOHOASTGPMVI5EFA&issuer=https%3A%2F%p
⽤户通过⾝份验证器扫描⼆维码即可⽣成⼀个动态的验证码
⽤户传当前动态的验证码和密钥给服务器,校验密钥的正确性(此密钥与⽤户真正的绑定)
上⾯有些步骤不必须的,看需求,可以简化为两步。
1、⽣成密钥
2、扫码
上⾯那么多只是为了准确性⽽已
⼆、安装⾝份验证器
IOS 版本:
可以在App Store搜索google authenticator
安卓:
客户端每30秒就会⽣成新的验证码
界⾯⼤概如下:
三、代码实现
1、前⾔
为了⽐较真实所以添加了注册和登录接⼝
为了⽅便集成了Swagger-ui和全部是GET请求,不会⽤Swagger-ui,就直接地址栏请求或者Postman都可
注册⽤户全部放Redis
登录与Google校验,也以注解⽅式实现
登录以token⽅式,所以请求其他接⼝的时候都要带上token,可以把token放在header⾥⾯
2、代码
UserController
控制层接⼝
流程从上往下执⾏即可
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import lecheck.annotation.NeedLogin;
import lecheck.base.BaseController;
import lecheckmon.Result;
import lecheck.dto.GoogleDTO;
import lecheck.dto.LoginDTO;
import lecheck.service.UserService;
import lecheck.utils.QRCodeUtil;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.OutputStream;
@Controller
@RequestMapping("/user")
@Api(tags = "⽤户模块")
public class UserController extends BaseController {
@Autowired
private UserService userService;
@GetMapping("/register")
@ApiOperation("注册")
@ResponseBody
public Result register(LoginDTO dto) throws Exception {
ister(dto);
}
@GetMapping("/login")
@ApiOperation("登录")
@ResponseBody
public Result login(LoginDTO dto)throws Exception{
return userService.login(dto);
}
@GetMapping("/generateGoogleSecret")
@ResponseBody
@NeedLogin
@ApiOperation("⽣成google密钥")
public Result generateGoogleSecret()throws Exception{
User());
}
/**
* 显⽰⼀个⼆维码图⽚
* @param secretQrCode generateGoogleSecret接⼝返回的:secretQrCode
* @param response
* @throws Exception
*/
@GetMapping("/genQrCode")
@ApiOperation("⽣成⼆维码")
public void genQrCode(String secretQrCode, HttpServletResponse response) throws Exception{ response.setContentType("image/png");
OutputStream stream = OutputStream();
}
@GetMapping("/bindGoogle")
@ResponseBody
@NeedLogin
@ApiOperation("绑定google验证")
public Result bindGoogle(GoogleDTO dto)throws Exception{
return userService.bindGoogle(User(),Request());
}
@GetMapping("/googleLogin")
@ResponseBody
@NeedLogin
@ApiOperation("google登录")
public Result googleLogin(Long code) throws Exception{
leLogin(User(),Request());
}
@GetMapping("/getData")
@NeedLogin(google = true)
@ApiOperation("获取⽤户数据与token数据,前提需要google认证才能访问")
@ResponseBody
public Result getData()throws Exception{
Data();
}
}
UserService
服务层,业务代码
import org.springframework.beans.factory.annotation.Autowired;
import org.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import ption.MDUtil;
import lecheckmon.*;
import lecheck.dto.GoogleDTO;
import lecheck.dto.LoginDTO;
import ity.User;
import lecheck.utils.GoogleAuthenticator;
import lecheck.utils.Tools;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import urrent.TimeUnit;
@Service
public class UserService {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
/**
* 获取缓存中的数据
* @return
*/
public Result getData(){
Map<String,Object> data = new HashMap<>();
setData(CacheKey.REGISTER_USER_KEY,data);
setData(CacheKey.TOKEN_KEY_LOGIN_KEY,data);
return Result.ok(data);
}
public void setData(String keyword,Map<String,Object> data){
Set<String> keys = redisTemplate.keys(keyword);
Iterator<String> iterator = keys.iterator();
while (iterator.hasNext()){
String key = ();
data.put(key,redisTemplate.opsForValue().get(key));
}
}
/**
* 注册
* @param dto
* @return
* @throws Exception
*/
public Result register(LoginDTO dto) throws Exception {
User user = new User();
user.UUID());
user.Username());
user.setPassword(MDUtil.Password()));
addUser(user);
return Result.ok();
}
/
/获取⽤户
public User getUser(String username){
User cacheUser = (User) redisTemplate.opsForValue().get(String.format(CacheKey.REGISTER_USER, username));
return cacheUser;
}
//添加注册⽤户
public void addUser(User user){
if(user == null) throw new ApiException(ApiResultEnum.ERROR_NULL);
User isRepeat = Username());
if(isRepeat != null ){
throw new ApiException(ApiResultEnum.USER_IS_EXIST);
}
redisTemplate.opsForValue().set(String.format(CacheKey.REGISTER_USER, Username()),user,1, TimeUnit.DAYS); }
//更新token⽤户
public void updateUser(User user,HttpServletRequest request){
if(user == null) throw new ApiException(ApiResultEnum.ERROR_NULL);
redisTemplate.opsForValue().TokenKey(request,CacheEnum.LOGIN),user,1, TimeUnit.DAYS);
}
/**
* 登录
* @param dto
* @return
* @throws Exception
*/
public Result login(LoginDTO dto) throws Exception {
User user = Username());
if(user == null){
throw new ApiException(ApiResultEnum.USER_NOT_EXIST);
}
if(!Password().equals(MDUtil.Password()))){
throw new ApiException(ApiResultEnum.USERNAME_OR_PASSWORD_IS_WRONG);
}
/
/随机⽣成token
String token = UUID();
redisTemplate.opsForValue().set(String.format(CacheKey.TOKEN_KEY_LOGIN,token),user,1,TimeUnit.DAYS);
Map<String,Object> data = new HashMap<>();
data.put(Consts.TOKEN,token);
return Result.ok(data);
}
/**
* ⽣成Google 密钥
* secret:密钥
* secretQrCode:Google Authenticator 扫描条形码的内容
* @param user
* @return
*/
public Result generateGoogleSecret(User user){
//Google密钥
String randomSecretKey = RandomSecretKey();
String googleAuthenticatorBarCode = GoogleAuthenticatorBarCode(randomSecretKey, Username(), "p"); Map<String,Object> data = new HashMap<>();
//Google密钥
data.put("secret",randomSecretKey);
//⽤户⼆维码内容
data.put("secretQrCode",googleAuthenticatorBarCode);
return Result.ok(data);
}
/**
* 绑定Google
* @param dto
* @param user
* @return
*/
public Result bindGoogle(GoogleDTO dto, User user, HttpServletRequest request){
if(!StringUtils.GoogleSecret())){
throw new ApiException(ApiResultEnum.GOOGLE_IS_BIND);
}
boolean isTrue = GoogleAuthenticator.check_Secret(), Code(), System.currentTimeMillis());
if(!isTrue){
throw new ApiException(ApiResultEnum.GOOGLE_CODE_NOT_MATCH);
}
User cacheUser = Username());
cacheUser.Secret());
updateUser(cacheUser,request);
return Result.ok();
}
/**
* Google登录
* @param code
* @param user
* @return
*/
public Result googleLogin(Long code,User user,HttpServletRequest request){
if(StringUtils.GoogleSecret())){
throw new ApiException(ApiResultEnum.GOOGLE_NOT_BIND);
}
boolean isTrue = GoogleAuthenticator.check_GoogleSecret(), code, System.currentTimeMillis());
if(!isTrue){
throw new ApiException(ApiResultEnum.GOOGLE_CODE_NOT_MATCH);
}
redisTemplate.opsForValue().TokenKey(request,CacheEnum.GOOGLE),Consts.SUCCESS,1,TimeUnit.DAYS);
return Result.ok();
}
}
GoogleAuthenticator
Google⾝份验证器⼯具类
le.zxing.BarcodeFormat;
le.zxing.MultiFormatWriter;
le.zxing.WriterException;
le.zxing.client.j2se.MatrixToImageWriter;
le.zxingmon.BitMatrix;
import dec.binary.Base32;
import dec.binary.Hex;
pto.Mac;
pto.spec.SecretKeySpec;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public class GoogleAuthenticator {
public static String getRandomSecretKey() {
SecureRandom random = new SecureRandom();
byte[] bytes = new byte[20];
Base32 base32 = new Base32();
String secretKey = deToString(bytes);
// make the secret key more human-readable by lower-casing and
/
/ inserting spaces between each group of 4 characters
UpperCase(); // .replaceAll("(.{4})(?=.{4})", "$1 ");
}
public static String getTOTPCode(String secretKey) {
String normalizedBase32Key = place(" ", "").toUpperCase();
Base32 base32 = new Base32();
byte[] bytes = base32.decode(normalizedBase32Key);
String hexKey = deHexString(bytes);springboot推荐算法
long time = (System.currentTimeMillis() / 1000) / 30;
String hexTime = HexString(time);
ateTOTP(hexKey, hexTime, "6");
}
public static String getGoogleAuthenticatorBarCode(String secretKey,
String account, String issuer) {
String normalizedBase32Key = place(" ", "").toUpperCase();
try {
return "otpauth://totp/"
+ de(issuer + ":" + account, "UTF-8")
.replace("+", "%20")
+ "?secret="
+ de(normalizedBase32Key, "UTF-8").replace(
"+", "%20") + "&issuer="
+ de(issuer, "UTF-8").replace("+", "%20");
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException(e);
}
}
public static void createQRCode(String barCodeData, String filePath,
int height, int width) throws WriterException, IOException {
BitMatrix matrix = new MultiFormatWriter().encode(barCodeData,
BarcodeFormat.QR_CODE, width, height);
try (FileOutputStream out = new FileOutputStream(filePath)) {
MatrixToImageWriter.writeToStream(matrix, "png", out);
}
}
static int window_size = 3; // default 3 - max 17 (from google docs)最多可偏移的时间 /**
* set the windows size. This is an integer value representing the number of
* 30 second windows we allow The bigger the window, the more tolerant of
* clock skew we are.
*
* @param s
* window size - must be >=1 and <=17. Other values are ignored
*/
public static void setWindowSize(int s) {
if (s >= 1 && s <= 17)
window_size = s;
}
/**
* Check the code entered by the user to see if it is valid
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论