pom.xml
 <?xml version="1.0" encoding="UTF-8"?> 
 4.0.0

 org.springframework.boot
 spring-boot-starter-parent
 2.1.4.RELEASE


 top.lrshuai
 google-check
 0.0.1-SNAPSHOT
 SpringBoot-GoogleCheck
 google check<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    <swagger2.version>2.7.0</swagger2.version>
</properties>

<dependencies>
    <!-- spring boot -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.6</version>
    </dependency>

    <!-- redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>

    <!-- json -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.56</version>
    </dependency>

    <!-- swagger2 -->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
        <version>${swagger2.version}</version>
        <exclusions>
            <exclusion>
                <artifactId>mapstruct</artifactId>
                <groupId>org.mapstruct</groupId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>${swagger2.version}</version>
    </dependency>

    <!-- 加密工具 -->
    <dependency>
        <groupId>top.lrshuai.encryption</groupId>
        <artifactId>encryption-tools</artifactId>
        <version>1.0.0</version>
    </dependency>
    <!--google qrcode -->
    <dependency>
        <groupId>com.google.zxing</groupId>
        <artifactId>javase</artifactId>
        <version>3.2.1</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build> annotaion package top.lrshuai.googlecheck.annotation; 
import java.lang.annotation.*;
/**
• 是否需要登录注解
• 
*/
 @Documented
 @Inherited
 @Target({ElementType.METHOD,ElementType.TYPE})
 @Retention(RetentionPolicy.RUNTIME)
 public @interface NeedLogin {
 // 是否有效,如果注解在类上,又想要某个方法上不生效,可用这个配置
 boolean isValid() default true;
 //登录注解
 boolean login() default true;
 //google验证注解
 boolean google() default false;
 }
 basecontroller
 package top.lrshuai.googlecheck.base;import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.web.context.request.RequestContextHolder;
 import org.springframework.web.context.request.ServletRequestAttributes;
 import top.lrshuai.googlecheck.common.ApiException;
 import top.lrshuai.googlecheck.common.ApiResultEnum;
 import top.lrshuai.googlecheck.common.CacheEnum;
 import top.lrshuai.googlecheck.entity.User;
 import top.lrshuai.googlecheck.utils.Tools;import javax.servlet.http.HttpServletRequest;
/**
• 基类
 */
 public class BaseController {
@Autowired
 private RedisTemplate<String,Object> redisTemplate;
public HttpServletRequest getRequest() {
 HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
 .getRequest();
 return request;
 }
/**• 从token 中获取用户信息
• @return
 */
 protected User getUser(){
 String tokenKey = Tools.getTokenKey(this.getRequest(), CacheEnum.LOGIN);
 User user = (User) redisTemplate.opsForValue().get(tokenKey);
 if(user == null) throw new ApiException(ApiResultEnum.AUTH_LGOIN_NOT_VALID);
 return user;
 }
 }
 common
 package top.lrshuai.googlecheck.common;import lombok.Data;
/**
• 自定义的api异常
• @author rstyro
• 
*/
 @Data
 public class ApiException extends RuntimeException{
 private static final long serialVersionUID = 1L;
 private String status;
 private String message;
 private Object data;
 private Exception exception;
 public ApiException() {
 super();
 }
 public ApiException(String status, String message, Object data, Exception exception) {
 this.status = status;
 this.message = message;
 this.data = data;
 this.exception = exception;
 }
 public ApiException(ApiResultEnum apiResultEnum) {
 this(apiResultEnum.getStatus(),apiResultEnum.getMessage(),null,null);
 }
 public ApiException(ApiResultEnum apiResultEnum, Object data) {
 this(apiResultEnum.getStatus(),apiResultEnum.getMessage(),data,null);
 }
 public ApiException(ApiResultEnum apiResultEnum, Object data, Exception exception) {
 this(apiResultEnum.getStatus(),apiResultEnum.getMessage(),data,exception);
 }}
 package top.lrshuai.googlecheck.common;public enum ApiResultEnum {
 SUCCESS(“200”,“ok”),
 FAILED(“400”,“请求失败”),
 ERROR(“500”,“不知名错误”),
 ERROR_NULL(“501”,“空指针异常”),
 ERROR_CLASS_CAST(“502”,“类型转换异常”),
 ERROR_RUNTION(“503”,“运行时异常”),
 ERROR_IO(“504”,“上传文件异常”),
 ERROR_MOTHODNOTSUPPORT(“505”,“请求方法错误”),AUTH_LGOIN_NOT_VALID("10000","用户未登录,或token过期"),
AUTH_GOOGLE_NOT_FOUND("10001","未Goole校验,无法访问"),
USER_IS_EXIST("10003","用户已存在"),
USER_NOT_EXIST("10004","用户不存在"),
USERNAME_OR_PASSWORD_IS_WRONG("10005","用户名密码错误"),
GOOGLE_CODE_NOT_MATCH("10006","Google验证码不匹配"),
GOOGLE_IS_BIND("10007","Google已绑定,不能重复绑定"),
GOOGLE_NOT_BIND("10008","Google未绑定,请先进行绑定"),

;

private String message;
private String status;

public String getMessage() {
	return message;
}

public String getStatus() {
	return status;
}
private ApiResultEnum(String status, String message) {
	this.message = message;
	this.status = status;
}}
 package top.lrshuai.googlecheck.common;public enum CacheEnum {
 LOGIN,GOOGLE
 }package top.lrshuai.googlecheck.common;
public class CacheKey {
 /**
 * 登录生成的token key
 */
 public static final String TOKEN_KEY_LOGIN = “TOKEN_KEY_LOGIN-%s”;/**
 * google验证保存的状态 key
 */
public static final String TOKEN_KEY_GOOGLE = "TOKEN_KEY_GOOGLE-%s";


/**
 * 注册的用户全部放redis缓存中
 */
public static final String REGISTER_USER = "REGISTER_USER_%s";

public static final String REGISTER_USER_KEY = "REGISTER_USER_*";
public static final String TOKEN_KEY_LOGIN_KEY = "TOKEN_KEY_LOGIN*";}
package top.lrshuai.googlecheck.common;
public class Consts {
 /**
 * 前端传上来的 token 参数
 */
 public static final String TOKEN = “token”;public static final String SUCCESS = "SUCCESS";}
package top.lrshuai.googlecheck.common;
import java.util.HashMap;
public class Result extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;

public Result() {
	put("status", 200);
	put("message", "ok");
}

public static Result error() {
	return error("500", "系统错误,请联系管理员");
}

public static Result error(String msg) {
	return error("500", msg);
}

public static Result error(String status, String msg) {
	Result r = new Result();
	r.put("status", status);
	r.put("message", msg);
	return r;
}

public static Result error(ApiResultEnum resultEnum) {
	Result r = new Result();
	r.put("status", resultEnum.getStatus());
	r.put("message", resultEnum.getMessage());
	return r;
}
public static Result ok(Object data) {
	Result r = new Result();
	r.put("data",data);
	return r;
}

public static Result ok() {
	return new Result();
}

@Override
public Result put(String key, Object value) {
	super.put(key, value);
	return this;
}}
 config
 package top.lrshuai.googlecheck.config;import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.web.HttpRequestMethodNotSupportedException;
 import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.RestControllerAdvice;
 import top.lrshuai.googlecheck.common.ApiException;
 import top.lrshuai.googlecheck.common.ApiResultEnum;
 import top.lrshuai.googlecheck.common.Result;import java.io.IOException;
/**
• 全局异常捕获
• @author rstyro
• @since 2019-03-12
 */
 @RestControllerAdvice
 public class GlobalExceptionHandler {
private Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(NullPointerException.class)
 public Result NullPointer(NullPointerException ex){
 logger.error(ex.getMessage(),ex);
 return Result.error(ApiResultEnum.ERROR_NULL);
 }
@ExceptionHandler(ClassCastException.class)
 public Result ClassCastException(ClassCastException ex){
 logger.error(ex.getMessage(),ex);
 return Result.error(ApiResultEnum.ERROR_CLASS_CAST);
 }
@ExceptionHandler(IOException.class)
 public Result IOException(IOException ex){
 logger.error(ex.getMessage(),ex);
 return Result.error(ApiResultEnum.ERROR_IO);
 }
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
 public Result HttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException ex){
 logger.error(ex.getMessage(),ex);
 return Result.error(ApiResultEnum.ERROR_MOTHODNOTSUPPORT);
 }
@ExceptionHandler(ApiException.class)
 public Result ApiException(ApiException ex) {
 logger.error(ex.getMessage(),ex);
 return Result.error(ex.getStatus(),ex.getMessage());
 }
@ExceptionHandler(RuntimeException.class)
 public Result RuntimeException(RuntimeException ex){
 logger.error(ex.getMessage(),ex);
 return Result.error(ApiResultEnum.ERROR_RUNTION);
 }
@ExceptionHandler(Exception.class)
 public Result exception(Exception ex){
 logger.error(ex.getMessage(),ex);
 return Result.error(ApiResultEnum.ERROR);
 }}
package top.lrshuai.googlecheck.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
 import com.fasterxml.jackson.annotation.PropertyAccessor;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.springframework.cache.CacheManager;
 import org.springframework.cache.annotation.CachingConfigurerSupport;
 import org.springframework.cache.interceptor.KeyGenerator;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.data.redis.cache.RedisCacheManager;
 import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.StringRedisSerializer;import javax.annotation.Resource;
 import java.lang.reflect.Method;
 import java.util.HashSet;
 import java.util.Set;/**
 *• @author rstyro
• @time 2017-07-31
• 
*/
 @Configuration
 //@EnableCaching // 开启缓存支持
 public class RedisConfig extends CachingConfigurerSupport {
 @Resource
 private LettuceConnectionFactory lettuceConnectionFactory;@Bean
public KeyGenerator keyGenerator() {
    return new KeyGenerator() {
        @Override
        public Object generate(Object target, Method method, Object... params) {
            StringBuffer sb = new StringBuffer();
            sb.append(target.getClass().getName());
            sb.append(method.getName());
            for (Object obj : params) {
                sb.append(obj.toString());
            }
            return sb.toString();
        }
    };
}


// 缓存管理器
@Bean
public CacheManager cacheManager() {
    RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.RedisCacheManagerBuilder
            .fromConnectionFactory(lettuceConnectionFactory);
    @SuppressWarnings("serial")
    Set<String> cacheNames = new HashSet<String>() {
        {
            add("codeNameCache");
        }
    };
    builder.initialCacheNames(cacheNames);
    return builder.build();
}


/**
 * RedisTemplate配置
 */
@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
    // 设置序列化
    Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(
            Object.class);
    ObjectMapper om = new ObjectMapper();
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    jackson2JsonRedisSerializer.setObjectMapper(om);
    // 配置redisTemplate
    RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
    redisTemplate.setConnectionFactory(lettuceConnectionFactory);
    RedisSerializer<?> stringSerializer = new StringRedisSerializer();
    redisTemplate.setKeySerializer(stringSerializer);// key序列化
    redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);// value序列化
    redisTemplate.setHashKeySerializer(stringSerializer);// Hash key序列化
    redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);// Hash value序列化
    redisTemplate.afterPropertiesSet();
    return redisTemplate;
}}
 package top.lrshuai.googlecheck.config;import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 import springfox.documentation.builders.ApiInfoBuilder;
 import springfox.documentation.builders.ParameterBuilder;
 import springfox.documentation.builders.RequestHandlerSelectors;
 import springfox.documentation.schema.ModelRef;
 import springfox.documentation.service.ApiInfo;
 import springfox.documentation.service.Parameter;
 import springfox.documentation.spi.DocumentationType;
 import springfox.documentation.spring.web.plugins.Docket;
 import springfox.documentation.swagger2.annotations.EnableSwagger2;import java.util.ArrayList;
 import java.util.List;@Configuration
 @EnableSwagger2
 public class SwaggerConfig implements WebMvcConfigurer {
 @Bean
 public Docket ProductApi() {
 //可以添加多个header或参数
 ParameterBuilder aParameterBuilder = new ParameterBuilder();
 aParameterBuilder.parameterType(“header”) //参数类型支持header, cookie, body, query etc
 .name(“token”) //参数名
 .defaultValue("") //默认值
 .description(“用户token”)
 .modelRef(new ModelRef(“string”))//指定参数值的类型
 .required(false).build(); //非必需,这里是全局配置,然而在登陆的时候是不用验证的
 List aParameters = new ArrayList();
 aParameters.add(aParameterBuilder.build());
 return new Docket(DocumentationType.SWAGGER_2).apiInfo(productApiInfo())
 .groupName(“v1”).select()
 .apis(RequestHandlerSelectors.basePackage(“top.lrshuai”))
 .build().globalOperationParameters(aParameters);
 }private ApiInfo productApiInfo() {
    ApiInfo apiInfo = new ApiInfoBuilder()
            .title("接口文档")
            .version("1.0.0")
            .build();
    return apiInfo;
}



 /**
 * 防止@EnableMvc把默认的静态资源路径覆盖了,手动设置的方式
 * @param registry
 */
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("swagger-ui.html")
            .addResourceLocations("classpath:/META-INF/resources/");

    registry.addResourceHandler("/webjars/**")
            .addResourceLocations("classpath:/META-INF/resources/webjars/");
    registry.addResourceHandler("/**")
            .addResourceLocations("classpath:/static/");
}}
 package top.lrshuai.googlecheck.config;import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 import top.lrshuai.googlecheck.interceptor.LoginIntercept;@Slf4j
 @Component
 public class WebMvcConfig implements WebMvcConfigurer {@Autowired
private LoginIntercept loginIntercept;

@Override
public void addInterceptors(InterceptorRegistry registry) {
    log.info("添加拦截");
    registry.addInterceptor(loginIntercept);
}}
 controller
 package top.lrshuai.googlecheck.controller;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 top.lrshuai.googlecheck.annotation.NeedLogin;
 import top.lrshuai.googlecheck.base.BaseController;
 import top.lrshuai.googlecheck.common.Result;
 import top.lrshuai.googlecheck.dto.GoogleDTO;
 import top.lrshuai.googlecheck.dto.LoginDTO;
 import top.lrshuai.googlecheck.service.UserService;
 import top.lrshuai.googlecheck.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("1、注册")
@ResponseBody
public Result register(LoginDTO dto) throws Exception {
    return userService.register(dto);
}


@GetMapping("/login")
@ApiOperation("2、登录")
@ResponseBody
public Result login(LoginDTO dto)throws Exception{
    return userService.login(dto);
}


@GetMapping("/generateGoogleSecret")
@ResponseBody
@NeedLogin
@ApiOperation("3、生成google密钥")
public Result generateGoogleSecret()throws Exception{
    return userService.generateGoogleSecret(this.getUser());
}

/**
 * 注意:这个需要地址栏请求,因为返回的是一个流
 * 注意:这个需要地址栏请求,因为返回的是一个流
 * 注意:这个需要地址栏请求,因为返回的是一个流
 * 显示一个二维码图片
 * @param secretQrCode   generateGoogleSecret接口返回的:secretQrCode
 * @param response
 * @throws Exception
 */
@GetMapping("/genQrCode")
@ApiOperation("4、生成二维码,这个去地址栏请求,不要用Swagger-ui请求")
public void genQrCode(String secretQrCode, HttpServletResponse response) throws Exception{
    response.setContentType("image/png");
    OutputStream stream = response.getOutputStream();
    QRCodeUtil.encode(secretQrCode,stream);
}


@GetMapping("/bindGoogle")
@ResponseBody
@NeedLogin
@ApiOperation("5、绑定google验证")
public Result bindGoogle(GoogleDTO dto)throws Exception{
    return userService.bindGoogle(dto,this.getUser(),this.getRequest());
}

@GetMapping("/googleLogin")
@ResponseBody
@NeedLogin
@ApiOperation("6、google登录")
public Result googleLogin(Long code) throws Exception{
    return userService.googleLogin(code,this.getUser(),this.getRequest());
}


@GetMapping("/getData")
@NeedLogin(google = true)
@ApiOperation("7、获取数据")
@ResponseBody
public Result getData()throws Exception{
    return userService.getData();
}}
 dto
 package top.lrshuai.googlecheck.dto;import lombok.Data;
@Data
 public class GoogleDTO {
 /**
 * google密钥
/
 private String secret;
 /*
 * 手机上显示的验证码
 */
 private Long code;
 }
 package top.lrshuai.googlecheck.dto;import lombok.Data;
@Data
 public class LoginDTO {
 private String username;
 private String password;
 }entity
 package top.lrshuai.googlecheck.entity;import lombok.Data;
/**
• 简单的用户类
• 
/
 @Data
 public class User {
 /*
 * 用户ID ,唯一标识
/
 private String userId;
 /*
 * 用户名
/
 private String username;
 /*
 * 登录密码
 */
 private String password;/**
 * google 验证的 密钥
 */
private String googleSecret;}
 intercept
 package top.lrshuai.googlecheck.interceptor;import com.alibaba.fastjson.JSONObject;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Component;
 import org.springframework.web.method.HandlerMethod;
 import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
 import top.lrshuai.googlecheck.annotation.NeedLogin;
 import top.lrshuai.googlecheck.common.ApiResultEnum;
 import top.lrshuai.googlecheck.common.CacheEnum;
 import top.lrshuai.googlecheck.common.Consts;
 import top.lrshuai.googlecheck.common.Result;
 import top.lrshuai.googlecheck.entity.User;
 import top.lrshuai.googlecheck.utils.Tools;import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Method;/**
• 登录拦截
 */
 @Slf4j
 @Component
 public class LoginIntercept extends HandlerInterceptorAdapter {
@Autowired
 private RedisTemplate<String,Object> redisTemplate;
@Override
 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
 /**
 * isAssignableFrom() 判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口
 * isAssignableFrom 与instanceof 区别
 * isAssignableFrom()方法是判断是否为某个类的父类
 * instanceof 关键字是判断是否某个类的子类
 */
 if(handler.getClass().isAssignableFrom(HandlerMethod.class)){
 //HandlerMethod 封装方法定义相关的信息,如类,方法,参数等
 HandlerMethod handlerMethod = (HandlerMethod) handler;
 Method method = handlerMethod.getMethod();
 NeedLogin needLogin = getTagAnnotation(method, NeedLogin.class);
 if(needLogin != null){
 //登录校验
 if(needLogin.login() && !isLogin(request,CacheEnum.LOGIN)){
 resonseOut(response,Result.error(ApiResultEnum.AUTH_LGOIN_NOT_VALID));
 return false;
 }//google校验
         if(needLogin.google() && !isGoogle(request,CacheEnum.GOOGLE)){
             resonseOut(response,Result.error(ApiResultEnum.AUTH_GOOGLE_NOT_FOUND));
             return false;
         }
     }

 }
 return super.preHandle(request, response, handler);}
/**
• 检查是否 登录或者google验证
• @param request
• @param cacheEnum
• @return
 */
 public boolean isGoogle(HttpServletRequest request,CacheEnum cacheEnum){
 String tokenKey = Tools.getTokenKey(request,cacheEnum);
 String googleStatus = (String) redisTemplate.opsForValue().get(tokenKey);
 if(googleStatus != null && googleStatus.equalsIgnoreCase(Consts.SUCCESS)){
 return true;
 }
 return false;
 }/**
• 是否登录
• @param request
• @param cacheEnum
• @return
 */
 public boolean isLogin(HttpServletRequest request,CacheEnum cacheEnum){
 String tokenKey = Tools.getTokenKey(request,cacheEnum);
 User user = (User) redisTemplate.opsForValue().get(tokenKey);
 if(user == null){
 return false;
 }
 return true;
 }/**
• 获取目标注解
• 如果方法上有注解就返回方法上的注解配置,否则类上的
• @param method
• @param annotationClass
• @param 
• @return
 */
 public  A getTagAnnotation(Method method, Class annotationClass) {
 // 获取方法中是否包含注解
 Annotation methodAnnotate = method.getAnnotation(annotationClass);
 //获取 类中是否包含注解,也就是controller 是否有注解
 Annotation classAnnotate = method.getDeclaringClass().getAnnotation(annotationClass);
 return (A) (methodAnnotate!= null?methodAnnotate:classAnnotate);
 }/**
• 回写给客户端
• @param response
• @param result
• @throws IOException
 */
 private void resonseOut(HttpServletResponse response, Result result) throws IOException {
 response.setCharacterEncoding(“UTF-8”);
 response.setContentType(“application/json; charset=utf-8”);
 PrintWriter out = null ;
 String json = JSONObject.toJSON(result).toString();
 out = response.getWriter();
 out.append(json);
 }
 }
 filter
 package top.lrshuai.fli.filter;import java.io.IOException;
import javax.servlet.Filter;
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 import javax.servlet.annotation.WebFilter;/**
 *• 使用注解标注过滤器
• @WebFilter将一个实现了javax.servlet.Filter接口的类定义为过滤器
• 属性filterName 声明过滤器的名称,可选
• 属性urlPatterns指定要过滤 的URL模式,这是一个数组参数,可以指定多个。也可使用属性value来声明.(指定要过滤的URL模式是必选属性)
/
 @WebFilter(filterName=“myFilter”,urlPatterns={"/"})
 public class MyFilter implements Filter{
@Override
 public void destroy() {
 System.out.println(“myfilter 的 销毁方法”);
 }
@Override
 public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain chain)
 throws IOException, ServletException {
 System.out.println(“myfilter 的 过滤方法。这里可以执行过滤操作”);
 //继续下一个拦截器
 chain.doFilter(arg0, arg1);
 }
@Override
 public void init(FilterConfig arg0) throws ServletException {
 System.out.println(“myfilter 的 初始化方法”);
 }}
 service
 package top.lrshuai.googlecheck.service;import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
 import org.springframework.util.StringUtils;
 import top.lrshuai.encryption.MDUtil;
 import top.lrshuai.googlecheck.common.*;
 import top.lrshuai.googlecheck.dto.GoogleDTO;
 import top.lrshuai.googlecheck.dto.LoginDTO;
 import top.lrshuai.googlecheck.entity.User;
 import top.lrshuai.googlecheck.utils.GoogleAuthenticator;
 import top.lrshuai.googlecheck.utils.Tools;import javax.servlet.http.HttpServletRequest;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.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 = iterator.next();
        data.put(key,redisTemplate.opsForValue().get(key));
    }
}

/**
 * 注册
 * @param dto
 * @return
 * @throws Exception
 */
public Result register(LoginDTO dto) throws Exception {
    User user = new User();
    user.setUserId(Tools.getUUID());
    user.setUsername(dto.getUsername());
    user.setPassword(MDUtil.bcMD5(dto.getPassword()));
    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 = getUser(user.getUsername());
    if(isRepeat != null ){
        throw new ApiException(ApiResultEnum.USER_IS_EXIST);
    }
    redisTemplate.opsForValue().set(String.format(CacheKey.REGISTER_USER, user.getUsername()),user,1, TimeUnit.DAYS);
}

//更新token用户
public void updateUser(User user,HttpServletRequest request){
    if(user == null) throw new ApiException(ApiResultEnum.ERROR_NULL);
    redisTemplate.opsForValue().set(Tools.getTokenKey(request,CacheEnum.LOGIN),user,1, TimeUnit.DAYS);
}


/**
 * 登录
 * @param dto
 * @return
 * @throws Exception
 */
public Result login(LoginDTO dto) throws Exception {
    User user = getUser(dto.getUsername());
    if(user == null){
        throw new ApiException(ApiResultEnum.USER_NOT_EXIST);
    }
    if(!user.getPassword().equals(MDUtil.bcMD5(dto.getPassword()))){
        throw new ApiException(ApiResultEnum.USERNAME_OR_PASSWORD_IS_WRONG);
    }
    //随机生成token
    String token = Tools.getUUID();
    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 = GoogleAuthenticator.getRandomSecretKey();
    String googleAuthenticatorBarCode = GoogleAuthenticator.getGoogleAuthenticatorBarCode(randomSecretKey, user.getUsername(), "https://www.lrshuai.top");
    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.isEmpty(user.getGoogleSecret())){
        throw new ApiException(ApiResultEnum.GOOGLE_IS_BIND);
    }
    boolean isTrue = GoogleAuthenticator.check_code(dto.getSecret(), dto.getCode(), System.currentTimeMillis());
    if(!isTrue){
        throw new ApiException(ApiResultEnum.GOOGLE_CODE_NOT_MATCH);
    }
    User cacheUser = getUser(user.getUsername());
    cacheUser.setGoogleSecret(dto.getSecret());
    updateUser(cacheUser,request);
    return Result.ok();
}

/**
 * Google登录
 * @param code
 * @param user
 * @return
 */
public Result googleLogin(Long code,User user,HttpServletRequest request){
    if(StringUtils.isEmpty(user.getGoogleSecret())){
        throw new ApiException(ApiResultEnum.GOOGLE_NOT_BIND);
    }
    boolean isTrue = GoogleAuthenticator.check_code(user.getGoogleSecret(), code, System.currentTimeMillis());
    if(!isTrue){
        throw new ApiException(ApiResultEnum.GOOGLE_CODE_NOT_MATCH);
    }
    redisTemplate.opsForValue().set(Tools.getTokenKey(request,CacheEnum.GOOGLE),Consts.SUCCESS,1,TimeUnit.DAYS);
    return Result.ok();
}}
 uitls
 package top.lrshuai.googlecheck.utils;import com.google.zxing.BarcodeFormat;
 import com.google.zxing.MultiFormatWriter;
 import com.google.zxing.WriterException;
 import com.google.zxing.client.j2se.MatrixToImageWriter;
 import com.google.zxing.common.BitMatrix;
 import org.apache.commons.codec.binary.Base32;
 import org.apache.commons.codec.binary.Hex;import javax.crypto.Mac;
 import javax.crypto.spec.SecretKeySpec;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.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];
 random.nextBytes(bytes);
 Base32 base32 = new Base32();
 String secretKey = base32.encodeToString(bytes);
 // make the secret key more human-readable by lower-casing and
 // inserting spaces between each group of 4 characters
 return secretKey.toUpperCase(); // .replaceAll("(.{4})(?=.{4})", "$1 ");
 }public static String getTOTPCode(String secretKey) {
	String normalizedBase32Key = secretKey.replace(" ", "").toUpperCase();
	Base32 base32 = new Base32();
	byte[] bytes = base32.decode(normalizedBase32Key);
	String hexKey = Hex.encodeHexString(bytes);
	long time = (System.currentTimeMillis() / 1000) / 30;
	String hexTime = Long.toHexString(time);
	return TOTP.generateTOTP(hexKey, hexTime, "6");
}

public static String getGoogleAuthenticatorBarCode(String secretKey,
		String account, String issuer) {
	String normalizedBase32Key = secretKey.replace(" ", "").toUpperCase();
	try {
		return "otpauth://totp/"
				+ URLEncoder.encode(issuer + ":" + account, "UTF-8")
						.replace("+", "%20")
				+ "?secret="
				+ URLEncoder.encode(normalizedBase32Key, "UTF-8").replace(
						"+", "%20") + "&issuer="
				+ URLEncoder.encode(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
 * 
 * @param secret
 *            The users secret.
 * @param code
 *            The code displayed on the users device
 * @param timeMsec
 *            The time in msec (System.currentTimeMillis() for example)
 * @return
 */
public static boolean check_code(String secret, long code, long timeMsec) {
	Base32 codec = new Base32();
	byte[] decodedKey = codec.decode(secret);
	// convert unix msec time into a 30 second "window"
	// this is per the TOTP spec (see the RFC for details)
	long t = (timeMsec / 1000L) / 30L;
	// Window is used to check codes generated in the near past.
	// You can use this value to tune how far you're willing to go.
	for (int i = -window_size; i <= window_size; ++i) {
		long hash;
		try {
			hash = verify_code(decodedKey, t + i);
		} catch (Exception e) {
			// Yes, this is bad form - but
			// the exceptions thrown would be rare and a static
			// configuration problem
			// e.printStackTrace();
			throw new RuntimeException(e.getMessage());
			// return false;
		}
		if (hash == code) {
			return true;
		}
	}
	// The validation code is invalid.
	return false;
}

private static int verify_code(byte[] key, long t)
		throws NoSuchAlgorithmException, InvalidKeyException {
	byte[] data = new byte[8];
	long value = t;
	for (int i = 8; i-- > 0; value >>>= 8) {
		data[i] = (byte) value;
	}
	SecretKeySpec signKey = new SecretKeySpec(key, "HmacSHA1");
	Mac mac = Mac.getInstance("HmacSHA1");
	mac.init(signKey);
	byte[] hash = mac.doFinal(data);
	int offset = hash[20 - 1] & 0xF;
	// We're using a long because Java hasn't got unsigned int.
	long truncatedHash = 0;
	for (int i = 0; i < 4; ++i) {
		truncatedHash <<= 8;
		// We are dealing with signed bytes:
		// we just keep the first byte.
		truncatedHash |= (hash[offset + i] & 0xFF);
	}
	truncatedHash &= 0x7FFFFFFF;
	truncatedHash %= 1000000;
	return (int) truncatedHash;
}}
 package top.lrshuai.googlecheck.utils;import java.awt.BasicStroke;
 import java.awt.Graphics;
 import java.awt.Graphics2D;
 import java.awt.Image;
 import java.awt.Shape;
 import java.awt.geom.RoundRectangle2D;
 import java.awt.image.BufferedImage;
 import java.io.File;
 import java.io.OutputStream;
 import java.util.Hashtable;
 import java.util.Random;import javax.imageio.ImageIO;
import com.google.zxing.BarcodeFormat;
 import com.google.zxing.BinaryBitmap;
 import com.google.zxing.DecodeHintType;
 import com.google.zxing.EncodeHintType;
 import com.google.zxing.MultiFormatReader;
 import com.google.zxing.MultiFormatWriter;
 import com.google.zxing.Result;
 import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
 import com.google.zxing.common.BitMatrix;
 import com.google.zxing.common.HybridBinarizer;
 import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;/**
• 二维码工具类
• 
*/
 public class QRCodeUtil {
 private static final String CHARSET = “utf-8”;
 private static final String FORMAT = “JPG”;
 // 二维码尺寸
 private static final int QRCODE_SIZE = 300;
 // LOGO宽度
 private static final int LOGO_WIDTH = 60;
 // LOGO高度
 private static final int LOGO_HEIGHT = 60;private static BufferedImage createImage(String content, String logoPath, boolean needCompress) throws Exception {
    Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>();
    hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
    hints.put(EncodeHintType.CHARACTER_SET, CHARSET);
    hints.put(EncodeHintType.MARGIN, 1);
    BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE,
            hints);
    int width = bitMatrix.getWidth();
    int height = bitMatrix.getHeight();
    BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    for (int x = 0; x < width; x++) {
        for (int y = 0; y < height; y++) {
            image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
        }
    }
    if (logoPath == null || "".equals(logoPath)) {
        return image;
    }
    // 插入图片
    QRCodeUtil.insertImage(image, logoPath, needCompress);
    return image;
}

/**
 * 插入LOGO
 *
 * @param source
 *            二维码图片
 * @param logoPath
 *            LOGO图片地址
 * @param needCompress
 *            是否压缩
 * @throws Exception
 */
private static void insertImage(BufferedImage source, String logoPath, boolean needCompress) throws Exception {
    File file = new File(logoPath);
    if (!file.exists()) {
        throw new Exception("logo file not found.");
    }
    Image src = ImageIO.read(new File(logoPath));
    int width = src.getWidth(null);
    int height = src.getHeight(null);
    if (needCompress) { // 压缩LOGO
        if (width > LOGO_WIDTH) {
            width = LOGO_WIDTH;
        }
        if (height > LOGO_HEIGHT) {
            height = LOGO_HEIGHT;
        }
        Image image = src.getScaledInstance(width, height, Image.SCALE_SMOOTH);
        BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics g = tag.getGraphics();
        g.drawImage(image, 0, 0, null); // 绘制缩小后的图
        g.dispose();
        src = image;
    }
    // 插入LOGO
    Graphics2D graph = source.createGraphics();
    int x = (QRCODE_SIZE - width) / 2;
    int y = (QRCODE_SIZE - height) / 2;
    graph.drawImage(src, x, y, width, height, null);
    Shape shape = new RoundRectangle2D.Float(x, y, width, width, 6, 6);
    graph.setStroke(new BasicStroke(3f));
    graph.draw(shape);
    graph.dispose();
}

/**
 * 生成二维码(内嵌LOGO)
 * 二维码文件名随机,文件名可能会有重复
 *
 * @param content
 *            内容
 * @param logoPath
 *            LOGO地址
 * @param destPath
 *            存放目录
 * @param needCompress
 *            是否压缩LOGO
 * @throws Exception
 */
public static String encode(String content, String logoPath, String destPath, boolean needCompress) throws Exception {
    BufferedImage image = QRCodeUtil.createImage(content, logoPath, needCompress);
    mkdirs(destPath);
    String fileName = new Random().nextInt(99999999) + "." + FORMAT.toLowerCase();
    ImageIO.write(image, FORMAT, new File(destPath + "/" + fileName));
    return fileName;
}

/**
 * 生成二维码(内嵌LOGO)
 * 调用者指定二维码文件名
 *
 * @param content
 *            内容
 * @param logoPath
 *            LOGO地址
 * @param destPath
 *            存放目录
 * @param fileName
 *            二维码文件名
 * @param needCompress
 *            是否压缩LOGO
 * @throws Exception
 */
public static String encode(String content, String logoPath, String destPath, String fileName, boolean needCompress) throws Exception {
    BufferedImage image = QRCodeUtil.createImage(content, logoPath, needCompress);
    mkdirs(destPath);
    fileName = fileName.substring(0, fileName.indexOf(".")>0?fileName.indexOf("."):fileName.length())
            + "." + FORMAT.toLowerCase();
    ImageIO.write(image, FORMAT, new File(destPath + "/" + fileName));
    return fileName;
}

/**
 * 当文件夹不存在时,mkdirs会自动创建多层目录,区别于mkdir.
 * (mkdir如果父目录不存在则会抛出异常)
 * @param destPath
 *            存放目录
 */
public static void mkdirs(String destPath) {
    File file = new File(destPath);
    if (!file.exists() && !file.isDirectory()) {
        file.mkdirs();
    }
}

/**
 * 生成二维码(内嵌LOGO)
 *
 * @param content
 *            内容
 * @param logoPath
 *            LOGO地址
 * @param destPath
 *            存储地址
 * @throws Exception
 */
public static String encode(String content, String logoPath, String destPath) throws Exception {
    return QRCodeUtil.encode(content, logoPath, destPath, false);
}

/**
 * 生成二维码
 *
 * @param content
 *            内容
 * @param destPath
 *            存储地址
 * @param needCompress
 *            是否压缩LOGO
 * @throws Exception
 */
public static String encode(String content, String destPath, boolean needCompress) throws Exception {
    return QRCodeUtil.encode(content, null, destPath, needCompress);
}

/**
 * 生成二维码
 *
 * @param content
 *            内容
 * @param destPath
 *            存储地址
 * @throws Exception
 */
public static String encode(String content, String destPath) throws Exception {
    return QRCodeUtil.encode(content, null, destPath, false);
}

/**
 * 生成二维码(内嵌LOGO)
 *
 * @param content
 *            内容
 * @param logoPath
 *            LOGO地址
 * @param output
 *            输出流
 * @param needCompress
 *            是否压缩LOGO
 * @throws Exception
 */
public static void encode(String content, String logoPath, OutputStream output, boolean needCompress)
        throws Exception {
    BufferedImage image = QRCodeUtil.createImage(content, logoPath, needCompress);
    ImageIO.write(image, FORMAT, output);
}

/**
 * 生成二维码
 *
 * @param content
 *            内容
 * @param output
 *            输出流
 * @throws Exception
 */
public static void encode(String content, OutputStream output) throws Exception {
    QRCodeUtil.encode(content, null, output, false);
}

/**
 * 解析二维码
 *
 * @param file
 *            二维码图片
 * @return
 * @throws Exception
 */
public static String decode(File file) throws Exception {
    BufferedImage image;
    image = ImageIO.read(file);
    if (image == null) {
        return null;
    }
    BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(image);
    BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
    Result result;
    Hashtable<DecodeHintType, Object> hints = new Hashtable<DecodeHintType, Object>();
    hints.put(DecodeHintType.CHARACTER_SET, CHARSET);
    result = new MultiFormatReader().decode(bitmap, hints);
    String resultStr = result.getText();
    return resultStr;
}

/**
 * 解析二维码
 *
 * @param path
 *            二维码图片地址
 * @return
 * @throws Exception
 */
public static String decode(String path) throws Exception {
    return QRCodeUtil.decode(new File(path));
}

public static void main(String[] args) throws Exception {
    String text = "http://www.lrshuai.top?kw=哈哈|新年快乐||哈哈";
    //不含Logo
    //QRCodeUtil.encode(text, null, "e:\\", true);
    //含Logo,不指定二维码图片名
    //QRCodeUtil.encode(text, "e:\\csdn.jpg", "e:\\", true);
    //含Logo,指定二维码图片名
    QRCodeUtil.encode(text, "E:\\lrs_img\\16.png", "e:\\", "qrcode", true);
}}
package top.lrshuai.googlecheck.utils;
import org.springframework.util.StringUtils;
 import top.lrshuai.googlecheck.common.CacheEnum;
 import top.lrshuai.googlecheck.common.CacheKey;
 import top.lrshuai.googlecheck.common.Consts;import javax.servlet.http.HttpServletRequest;
 import java.util.UUID;/**
• 工具类
• 
*/
 public class Tools {public static String getUUID(){
    return UUID.randomUUID().toString().replaceAll("-","");
}
/**
 *  获取 token
 * @param request
 * @return
 */
public static String getToken(HttpServletRequest request){
    String token = request.getHeader(Consts.TOKEN);
    if(StringUtils.isEmpty(token)){
        token = request.getParameter(Consts.TOKEN);
    }
    return token;
}

/**
 *  获取 tokenKey
 * @param request
 * @return
 */
public static String getTokenKey(HttpServletRequest request, CacheEnum cacheEnum){
    String token = getToken(request);
    System.out.println("token="+token);
    if(cacheEnum.equals(CacheEnum.GOOGLE)){
        token =  String.format(CacheKey.TOKEN_KEY_GOOGLE,token);
    }else if(cacheEnum.equals(CacheEnum.LOGIN)){
        token =  String.format(CacheKey.TOKEN_KEY_LOGIN,token);
    }
    return token;
}}
 package top.lrshuai.googlecheck.utils;import javax.crypto.Mac;
 import javax.crypto.spec.SecretKeySpec;
 import java.lang.reflect.UndeclaredThrowableException;
 import java.math.BigInteger;
 import java.security.GeneralSecurityException;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.TimeZone;/**
• This is an example implementation of the OATH TOTP algorithm. Visit
• www.openauthentication.org for more information.
• 
• @author Johan Rydell, PortWise, Inc.
 */public class TOTP {
private TOTP() {
}

/**
 * This method uses the JCE to provide the crypto algorithm. HMAC computes a
 * Hashed Message Authentication Code with the crypto hash algorithm as a
 * parameter.
 *
 * @param crypto
 *            : the crypto algorithm (HmacSHA1, HmacSHA256, HmacSHA512)
 * @param keyBytes
 *            : the bytes to use for the HMAC key
 * @param text
 *            : the message or text to be authenticated
 */
private static byte[] hmac_sha(String crypto, byte[] keyBytes, byte[] text) {
	try {
		Mac hmac;
		hmac = Mac.getInstance(crypto);
		SecretKeySpec macKey = new SecretKeySpec(keyBytes, "RAW");
		hmac.init(macKey);
		return hmac.doFinal(text);
	} catch (GeneralSecurityException gse) {
		throw new UndeclaredThrowableException(gse);
	}
}

/**
 * This method converts a HEX string to Byte[]
 *
 * @param hex
 *            : the HEX string
 *
 * @return: a byte array
 */

private static byte[] hexStr2Bytes(String hex) {
	// Adding one byte to get the right conversion
	// Values starting with "0" can be converted
	byte[] bArray = new BigInteger("10" + hex, 16).toByteArray();

	// Copy all the REAL bytes, not the "first"
	byte[] ret = new byte[bArray.length - 1];
	for (int i = 0; i < ret.length; i++)
		ret[i] = bArray[i + 1];
	return ret;
}

private static final int[] DIGITS_POWER
// 0 1 2 3 4 5 6 7 8
= { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 };

/**
 * This method generates a TOTP value for the given set of parameters.
 *
 * @param key
 *            : the shared secret, HEX encoded
 * @param time
 *            : a value that reflects a time
 * @param returnDigits
 *            : number of digits to return
 *
 * @return: a numeric String in base 10 that includes
 *          {@link truncationDigits} digits
 */

public static String generateTOTP(String key, String time,
		String returnDigits) {
	return generateTOTP(key, time, returnDigits, "HmacSHA1");
}

/**
 * This method generates a TOTP value for the given set of parameters.
 *
 * @param key
 *            : the shared secret, HEX encoded
 * @param time
 *            : a value that reflects a time
 * @param returnDigits
 *            : number of digits to return
 *
 * @return: a numeric String in base 10 that includes
 *          {@link truncationDigits} digits
 */

public static String generateTOTP256(String key, String time,
		String returnDigits) {
	return generateTOTP(key, time, returnDigits, "HmacSHA256");
}

/**
 * This method generates a TOTP value for the given set of parameters.
 *
 * @param key
 *            : the shared secret, HEX encoded
 * @param time
 *            : a value that reflects a time
 * @param returnDigits
 *            : number of digits to return
 *
 * @return: a numeric String in base 10 that includes
 *          {@link truncationDigits} digits
 */

public static String generateTOTP512(String key, String time,
		String returnDigits) {
	return generateTOTP(key, time, returnDigits, "HmacSHA512");
}

/**
 * This method generates a TOTP value for the given set of parameters.
 *
 * @param key
 *            : the shared secret, HEX encoded
 * @param time
 *            : a value that reflects a time
 * @param returnDigits
 *            : number of digits to return
 * @param crypto
 *            : the crypto function to use
 *
 * @return: a numeric String in base 10 that includes
 *          {@link truncationDigits} digits
 */

public static String generateTOTP(String key, String time,
		String returnDigits, String crypto) {
	int codeDigits = Integer.decode(returnDigits).intValue();
	String result = null;

	// Using the counter
	// First 8 bytes are for the movingFactor
	// Compliant with base RFC 4226 (HOTP)
	while (time.length() < 16)
		time = "0" + time;

	// Get the HEX in a Byte[]
	byte[] msg = hexStr2Bytes(time);
	byte[] k = hexStr2Bytes(key);
	byte[] hash = hmac_sha(crypto, k, msg);

	// put selected bytes into result int
	int offset = hash[hash.length - 1] & 0xf;

	int binary = ((hash[offset] & 0x7f) << 24)
			| ((hash[offset + 1] & 0xff) << 16)
			| ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff);

	int otp = binary % DIGITS_POWER[codeDigits];

	result = Integer.toString(otp);
	while (result.length() < codeDigits) {
		result = "0" + result;
	}
	return result;
}

public static void main(String[] args) {
	// Seed for HMAC-SHA1 - 20 bytes
	String seed = "3132333435363738393031323334353637383930";
	// Seed for HMAC-SHA256 - 32 bytes
	String seed32 = "3132333435363738393031323334353637383930"
			+ "313233343536373839303132";
	// Seed for HMAC-SHA512 - 64 bytes
	String seed64 = "3132333435363738393031323334353637383930"
			+ "3132333435363738393031323334353637383930"
			+ "3132333435363738393031323334353637383930" + "31323334";
	long T0 = 0;
	long X = 30;
	long testTime[] = { 59L, 1111111109L, 1111111111L, 1234567890L,
			2000000000L, 20000000000L };

	String steps = "0";
	DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	df.setTimeZone(TimeZone.getTimeZone("UTC"));
	try {
		System.out.println("+---------------+-----------------------+"
				+ "------------------+--------+--------+");
		System.out.println("|  Time(sec)    |   Time (UTC format)   "
				+ "| Value of T(Hex)  |  TOTP  | Mode   |");
		System.out.println("+---------------+-----------------------+"
				+ "------------------+--------+--------+");

		for (int i = 0; i < testTime.length; i++) {
			long T = (testTime[i] - T0) / X;
			steps = Long.toHexString(T).toUpperCase();
			while (steps.length() < 16)
				steps = "0" + steps;
			String fmtTime = String.format("%1$-11s", testTime[i]);
			String utcTime = df.format(new Date(testTime[i] * 1000));
			System.out.print("|  " + fmtTime + "  |  " + utcTime + "  | "
					+ steps + " |");
			System.out.println(generateTOTP(seed, steps, "8", "HmacSHA1")
					+ "| SHA1   |");
			System.out.print("|  " + fmtTime + "  |  " + utcTime + "  | "
					+ steps + " |");
			System.out.println(generateTOTP(seed32, steps, "8",
					"HmacSHA256") + "| SHA256 |");
			System.out.print("|  " + fmtTime + "  |  " + utcTime + "  | "
					+ steps + " |");
			System.out.println(generateTOTP(seed64, steps, "8",
					"HmacSHA512") + "| SHA512 |");

			System.out.println("+---------------+-----------------------+"
					+ "------------------+--------+--------+");
		}
	} catch (final Exception e) {
		System.out.println("Error : " + e);
	}
}}
package top.lrshuai.jwt.util;
import sun.misc.BASE64Decoder;
 import sun.misc.BASE64Encoder;
 import top.lrshuai.jwt.entity.RSA256Key;import java.security.Key;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.interfaces.RSAPrivateKey;
 import java.security.interfaces.RSAPublicKey;
 import java.util.HashMap;
 import java.util.Map;/**
• 参考地址:
 */
 public class CreateSecrteKey {
public static final String KEY_ALGORITHM = “RSA”;
 private static final String PUBLIC_KEY = “RSAPublicKey”;
 private static final String PRIVATE_KEY = “RSAPrivateKey”;
private static RSA256Key rsa256Key;
//获得公钥
 public static String getPublicKey(Map<String, Object> keyMap) throws Exception {
 //获得map中的公钥对象 转为key对象
 Key key = (Key) keyMap.get(PUBLIC_KEY);
 //byte[] publicKey = key.getEncoded();
 //编码返回字符串
 return encryptBASE64(key.getEncoded());
 }
 public static String getPublicKey(RSA256Key rsa256Key) throws Exception {
 //获得map中的公钥对象 转为key对象
 Key key = rsa256Key.getPublicKey();
 //byte[] publicKey = key.getEncoded();
 //编码返回字符串
 return encryptBASE64(key.getEncoded());
 }
//获得私钥
 public static String getPrivateKey(Map<String, Object> keyMap) throws Exception {
 //获得map中的私钥对象 转为key对象
 Key key = (Key) keyMap.get(PRIVATE_KEY);
 //byte[] privateKey = key.getEncoded();
 //编码返回字符串
 return encryptBASE64(key.getEncoded());
 }
 //获得私钥
 public static String getPrivateKey(RSA256Key rsa256Key) throws Exception {
 //获得map中的私钥对象 转为key对象
 Key key = rsa256Key.getPrivateKey();
 //byte[] privateKey = key.getEncoded();
 //编码返回字符串
 return encryptBASE64(key.getEncoded());
 }
//解码返回byte
 public static byte[] decryptBASE64(String key) throws Exception {
 return (new BASE64Decoder()).decodeBuffer(key);
 }
//编码返回字符串
 public static String encryptBASE64(byte[] key) throws Exception {
 return (new BASE64Encoder()).encodeBuffer(key);
 }
//map对象中存放公私钥
 public static Map<String, Object> initKey() throws Exception {
 // /** RSA算法要求有一个可信任的随机数源 */
 //获得对象 KeyPairGenerator 参数 RSA 1024个字节
 KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
 keyPairGen.initialize(1024);
 //通过对象 KeyPairGenerator 生成密匙对 KeyPair
 KeyPair keyPair = keyPairGen.generateKeyPair();//通过对象 KeyPair 获取RSA公私钥对象RSAPublicKey RSAPrivateKey
 RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
 RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
 //公私钥对象存入map中
 Map<String, Object> keyMap = new HashMap<String, Object>(2);
 keyMap.put(PUBLIC_KEY, publicKey);
 keyMap.put(PRIVATE_KEY, privateKey);
 return keyMap;}
/**
• 获取公私钥
• @return
• @throws Exception
 */
 public static synchronized RSA256Key getRSA256Key() throws Exception {
 if(rsa256Key == null){
 synchronized (RSA256Key.class){
 if(rsa256Key == null) {
 rsa256Key = new RSA256Key();
 Map<String, Object> map = initKey();
 rsa256Key.setPrivateKey((RSAPrivateKey) map.get(CreateSecrteKey.PRIVATE_KEY));
 rsa256Key.setPublicKey((RSAPublicKey) map.get(CreateSecrteKey.PUBLIC_KEY));
 }
 }
 }
 return rsa256Key;
 }public static void main(String[] args) {
 Map<String, Object> keyMap;
 try {
 keyMap = initKey();
 String publicKey = getPublicKey(keyMap);
 System.out.println(“公钥:\n”+publicKey);
 String privateKey = getPrivateKey(keyMap);
 System.out.println(“私钥:\n”+privateKey);
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
 }
 package top.lrshuai.jwt.util;import java.sql.Timestamp;
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.GregorianCalendar;public class DateUtils {
 private final static SimpleDateFormat sdfYear = new SimpleDateFormat(“yyyy”);
 private final static SimpleDateFormat sdfDay = new SimpleDateFormat(
 “yyyy-MM-dd”);
 private final static SimpleDateFormat sdfDays = new SimpleDateFormat(
 “yyyyMMdd”);
 private final static SimpleDateFormat sdfTime = new SimpleDateFormat(
 “yyyy-MM-dd HH:mm:ss”);
 /**
 * 获取当天的开始时间
 * @return
/
 public static Date getDayBegin() {
 Calendar cal = new GregorianCalendar();
 cal.set(Calendar.HOUR_OF_DAY, 0);
 cal.set(Calendar.MINUTE, 0);
 cal.set(Calendar.SECOND, 0);
 cal.set(Calendar.MILLISECOND, 0);
 return cal.getTime();
 }
 /*
 * 获取当天的结束时间
 * @return
/
 public static Date getDayEnd() {
 Calendar cal = new GregorianCalendar();
 cal.set(Calendar.HOUR_OF_DAY, 23);
 cal.set(Calendar.MINUTE, 59);
 cal.set(Calendar.SECOND, 59);
 return cal.getTime();
 }
 /*
 * 获取昨天的开始时间
 * @return
/
 public static Date getBeginDayOfYesterday() {
 Calendar cal = new GregorianCalendar();
 cal.setTime(getDayBegin());
 cal.add(Calendar.DAY_OF_MONTH, -1);
 return cal.getTime();
 }
 /*
 * 获取昨天的结束时间
 * @return
 */
 public static Date getEndDayOfYesterDay() {
 Calendar cal = new GregorianCalendar();
 cal.setTime(getDayEnd());
 cal.add(Calendar.DAY_OF_MONTH, -1);
 return cal.getTime();
 }/**
 *  获取明天的开始时间
 * @return
 */
public static Date getBeginDayOfTomorrow() {
    Calendar cal = new GregorianCalendar();
    cal.setTime(getDayBegin());
    cal.add(Calendar.DAY_OF_MONTH, 1);
    return cal.getTime();
}
/**
 *  获取明天的结束时间
 * @return
 */
public static Date getEndDayOfTomorrow() {
    Calendar cal = new GregorianCalendar();
    cal.setTime(getDayEnd());
    cal.add(Calendar.DAY_OF_MONTH, 1);
    return cal.getTime();
}

/**
 * 获取本周的开始时间
 * @return
 */
public static Date getBeginDayOfWeek() {
    Date date = new Date();
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    int dayofweek = cal.get(Calendar.DAY_OF_WEEK);
    if (dayofweek == 1) {
        dayofweek += 7;
    }
    cal.add(Calendar.DATE, 2 - dayofweek);
    return getDayStartTime(cal.getTime());
}
/**
 *  获取本周的结束时间
 * @return
 */
public static Date getEndDayOfWeek() {
    Calendar cal = Calendar.getInstance();
    cal.setTime(getBeginDayOfWeek());
    cal.add(Calendar.DAY_OF_WEEK, 6);
    Date weekEndSta = cal.getTime();
    return getDayEndTime(weekEndSta);
}

/**
 *  获取本月的开始时间
 * @return
 */
public static Date getBeginDayOfMonth() {
    Calendar calendar = Calendar.getInstance();
    calendar.set(getNowYear(), getNowMonth() - 1, 1);
    return getDayStartTime(calendar.getTime());
}
/**
 *  获取本月的结束时间
 * @return
 */
public static Date getEndDayOfMonth() {
    Calendar calendar = Calendar.getInstance();
    calendar.set(getNowYear(), getNowMonth() - 1, 1);
    int day = calendar.getActualMaximum(5);
    calendar.set(getNowYear(), getNowMonth() - 1, day);
    return getDayEndTime(calendar.getTime());
}

/**
 *  获取今年是哪一年
 * @return
 */
public static Integer getNowYear() {
    Date date = new Date();
    GregorianCalendar gc = (GregorianCalendar) Calendar.getInstance();
    gc.setTime(date);
    return Integer.valueOf(gc.get(1));
}
/**
 *  获取本月是哪一月
 * @return
 */
public static int getNowMonth() {
    Date date = new Date();
    GregorianCalendar gc = (GregorianCalendar) Calendar.getInstance();
    gc.setTime(date);
    return gc.get(2) + 1;
}

/**
 *  获取某个日期的开始时间
 * @param d
 * @return
 */
public static Timestamp getDayStartTime(Date d) {
    Calendar calendar = Calendar.getInstance();
    if (null != d)
        calendar.setTime(d);
    calendar.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH), 0,
            0, 0);
    calendar.set(Calendar.MILLISECOND, 0);
    return new Timestamp(calendar.getTimeInMillis());
}
/**
 *  获取某个日期的结束时间
 * @param d
 * @return
 */
public static Timestamp getDayEndTime(Date d) {
    Calendar calendar = Calendar.getInstance();
    if (null != d)
        calendar.setTime(d);
    calendar.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH), 23,
            59, 59);
    calendar.set(Calendar.MILLISECOND, 999);
    return new Timestamp(calendar.getTimeInMillis());
}
/**
 * 日期时间偏移
 * offset = 1,date=2018-11-02 16:47:00
 * 结果:2018-11-03 16:47:00
 * @param date
 * @param offset
 * @return
 */
public static Date offset(Date date, int offset) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    cal.add(Calendar.DAY_OF_MONTH, offset);
    return cal.getTime();
}

// 日期偏移
public static Date offset(Date date, int offset,int calendar) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    cal.add(calendar, offset);
    return cal.getTime();
}}
 启动类
 package top.lrshuai.googlecheck;import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
 public class SpringBootGoogleCheckApplication {public static void main(String[] args) {
    SpringApplication.run(SpringBootGoogleCheckApplication.class, args);
}}