需求
二次认证,本来的需求是完成一个二次认证,对于登录到制定系统的中的用户进行二次验证,本来是只有某一个功能要用的,也是按照某一个功能设计的接口情况,但是在这个接口上线之前要求改成通用性的。
处理
首先是简单的处理,只有某一个功能用的话就涉及三个接口,第一个接口去感知这个用户是否二次认证过,第二个接口是用来发送验证码,第三个接口是将获得验证码输入进行匹配。我是这么想的也是这么实现的。
发送验证码
@Override
public Map SendVerificationCodeRetCode(Map<String, Object> params) {
Map<String, Object> ret = new HashMap<>();
String usernumber = String.valueOf(params.get("usernumber"));
if(StringUtils.isBlank(usernumber)){
ret.put("status","1");
ret.put("msg","参数不全");
return ret;
}
boolean existslogin = redisUtils.exists(getForbiddenToLoginKey(usernumber));
if(existslogin){
ret.put("status","2");
ret.put("msg","验证码输入错误次数超过5次,请两小时后重试。");
return ret;
}
boolean exists = redisUtils.exists(getSendTimeKey(usernumber));
if(exists){
ret.put("status","3");
ret.put("msg","发送过验证码,请稍后再试");
return ret;
}else {
String generate = generate();
redisUtils.set(getSendTimeKey(usernumber),generate,SendVerificationCodeTimeEffective);
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedDateTime = now.format(formatter);
String text = "由XXX发送,验证码:"+generate+",验证码有效期1分钟,切勿将验证码泄露于他人。发送时间:"+formattedDateTime;
params.put("msgcontent",text);
params.put("domainOrPayload",2);
secondaryValidation(params);
ret.put("status","4");
ret.put("data",generate);
ret.put("msg","验证码发送成功");
ret.put("text",text);
return ret;
}
}
验证码验证
@Override
public Map VerifyVerificationCode(Map<String, Object> params, HttpServletResponse res) {
Map<String, Object> ret = new HashMap<>();
String usernumber = String.valueOf(params.get("usernumber"));
String code = String.valueOf(params.get("code"));
if(StringUtils.isBlank(usernumber) || StringUtils.isBlank(code)){
ret.put("status","1");
ret.put("msg","参数不全");
return ret;
}
boolean existslogin = redisUtils.exists(getForbiddenToLoginKey(usernumber));
if(existslogin){
ret.put("status","2");
ret.put("msg","验证码输入错误次数超过5次,请两小时后重试");
return ret;
}
boolean exists = redisUtils.exists(getSendTimeKey(usernumber));
if(exists){
String usernumberCode = String.valueOf(redisUtils.get(getSendTimeKey(usernumber)));
if(usernumberCode.equals(code)){
redisUtils.set(getVerificationCodeKey(usernumber),"1",loginTime);
boolean existsFalseTime = redisUtils.exists(getFalseTimeKey(usernumber));
if(existsFalseTime){
redisUtils.remove(getFalseTimeKey(usernumber));
}
ret.put("status","3");
ret.put("msg","验证通过");
String secondary_validation = UUID.randomUUID().toString();
Cookie cookie = new Cookie("secondary_validation", secondary_validation);
cookie.setPath("/");
cookie.setDomain("");
cookie.setHttpOnly(Boolean.TRUE);
res.addCookie(cookie);
redisUtils.set(getVerificationCodeKeyCookie(unifastContext.getUser().getStaffId()),secondary_validation,loginTime);
return ret;
}else{
boolean existsFalseTime = redisUtils.exists(getFalseTimeKey(usernumber));
if(existsFalseTime){
int falseTime = (int) redisUtils.get(getFalseTimeKey(usernumber));
falseTime = falseTime + 1;
if(falseTime > 5){
redisUtils.remove(getFalseTimeKey(usernumber));
redisUtils.set(getForbiddenToLoginKey(usernumber),"1",ForbiddenToLoginTime);
ret.put("status","4");
ret.put("msg","验证码输入不正确,请重新输入。");
return ret;
}else {
redisUtils.set(getFalseTimeKey(usernumber),falseTime);
ret.put("status","5");
ret.put("msg","验证码输入不正确,请重新输入。");
return ret;
}
}else{
redisUtils.set(getFalseTimeKey(usernumber),1,falseTimeTime);
ret.put("status","6");
ret.put("msg","验证码输入不正确,请重新输入。");
return ret;
}
}
}else{
ret.put("status","7");
ret.put("msg","验证码已失效,请重新发送。");
return ret;
}
}
判断是否登录
@Override
public Map WhetherToValidate(Map<String, Object> params) {
Map<String, Object> ret = new HashMap<>();
String usernumber = String.valueOf(params.get("usernumber"));
if(StringUtils.isBlank(usernumber)){
ret.put("status","1");
ret.put("msg","参数不全");
return ret;
}
if(redisUtils.exists(getVerificationCodeKey(usernumber))){
ret.put("status","2");
ret.put("msg","已验证");
return ret;
}else{
ret.put("status","3");
ret.put("msg","未验证");
return ret;
}
}
三个接口写完了,感觉是完事了,但是存在一个问题,如果被人直接访问了呢,直接选择跳过前台的二次验证判断接口,于是就想写一个拦截器,直接在每一个请求之前进行判断,思路的话就是在的登录之后,在token中存一个cookie中存一个key用于表示
import cn.chinaunicom.sdsi.cloud.auth.MallUser;
import cn.chinaunicom.sdsi.cloud.system.permission.entity.SysPermissionPO;
import cn.chinaunicom.sdsi.framework.utils.RedisUtils;
import cn.chinaunicom.sdsi.framework.utils.UnifastContext;
import cn.chinaunicom.sdsi.system.service.SecondaryValidationMenuService;
import cn.chinaunicom.sdsi.talent.secondaryValidation.service.SecondaryValidationService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
@Primary
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@ConfigurationProperties(prefix = "unifast.cloud.resource")
public class SecondaryValidationFilter implements HandlerInterceptor {
@Autowired
private SecondaryValidationMenuService secondaryValidationService;
@Autowired
private UnifastContext unifastContext;
@Resource
private RedisUtils redisUtils;
public static final String SECONDARY_VALIDATION = "secondary_validation";
@Getter
@Setter
private List<String> whiteList;
/**
* 在请求处理之前调用-Controller方法调用之前调用
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
AntPathMatcher pathMatcher = new AntPathMatcher();
for (String s : whiteList) {
if (pathMatcher.match(s, request.getRequestURI())) {
return true;
}
}
MallUser user = unifastContext.getUser();
//获取需要验证的接口
List<SysPermissionPO> permissionList = secondaryValidationService.getMoreVerify();
for (SysPermissionPO permission : permissionList) {
if(permission.getCheckCode().contains(request.getRequestURI())){
String cellphone = user.getCellphone();
Boolean isCode = redisUtils.exists("verification_code:" + cellphone);
if(isCode){
return true;
}else{
final Cookie[] cookies = request.getCookies();
if (cookies != null && cookies.length > 0) {
for (Cookie cookie : cookies) {
if ((SECONDARY_VALIDATION+":"+user.getStaffId()).equals(cookie.getName())) {
//获取redis中的token并判断正确性
String secondaryCalidationCode = (String) redisUtils.get(SECONDARY_VALIDATION + ":" + user.getStaffId());
if(secondaryCalidationCode.equals(cookie.getValue())){
return true;
}else{
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Type","application/json;charset=utf-8");
String json = "{\"message\": \"需要二次认证\",\"code\": \"0\",\"data\": \"null\",\"success\": false}";
try {
response.getWriter().write(json);
} catch (IOException e) {
throw new RuntimeException(e);
}
return false;
}
}
}
}
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Type","application/json;charset=utf-8");
String json = "{\"message\": \"需要二次认证\",\"code\": \"0\",\"data\": \"null\",\"success\": false}";
try {
response.getWriter().write(json);
} catch (IOException e) {
throw new RuntimeException(e);
}
return false;
}
}
}
return true;
}
/**
* 请求处理之后调用,但是在视图被渲染之前-Controller方法调用之后
*
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
}
/**
* 在整个请求调用之后被调用,也就是在DispatcherServlet渲染了对应的视图之后执行 主要是用于进行资源清理工作
*
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
}
import cn.chinaunicom.sdsi.filter.SecondaryValidationFilter;
import com.google.common.collect.Lists;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import javax.annotation.Resource;
import java.util.List;
@Order(100)
@Configuration
/**
* 二次验证拦截器
*/
public class SecondaryValidation extends WebMvcConfigurerAdapter {
@Resource
private SecondaryValidationFilter loginLoginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
List<String> excludePathList = Lists.newArrayList();
registry.addInterceptor(loginLoginInterceptor).addPathPatterns("/*/**").excludePathPatterns(excludePathList.toArray(new String[0]))
.excludePathPatterns("/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**");
super.addInterceptors(registry);
}
}
实话说这块不是我写的,但是我大概能够看的懂,但是我自己写可能还是有差距,持续学习吧