新设备登录时增加二次认证功能
diff --git a/backend/src/main/java/com/supwisdom/dlpay/framework/security/SmsAuthenticationException.java b/backend/src/main/java/com/supwisdom/dlpay/framework/security/SmsAuthenticationException.java
new file mode 100644
index 0000000..987644c
--- /dev/null
+++ b/backend/src/main/java/com/supwisdom/dlpay/framework/security/SmsAuthenticationException.java
@@ -0,0 +1,13 @@
+package com.supwisdom.dlpay.framework.security;
+
+import org.springframework.security.core.AuthenticationException;
+
+public class SmsAuthenticationException extends AuthenticationException {
+  public SmsAuthenticationException(String msg, Throwable t) {
+    super(msg, t);
+  }
+
+  public SmsAuthenticationException(String msg) {
+    super(msg);
+  }
+}
diff --git a/backend/src/main/java/com/supwisdom/dlpay/framework/security/auth/SmsAuthenticationFilter.java b/backend/src/main/java/com/supwisdom/dlpay/framework/security/auth/SmsAuthenticationFilter.java
new file mode 100644
index 0000000..dba4dc8
--- /dev/null
+++ b/backend/src/main/java/com/supwisdom/dlpay/framework/security/auth/SmsAuthenticationFilter.java
@@ -0,0 +1,95 @@
+package com.supwisdom.dlpay.framework.security.auth;
+
+import org.springframework.security.authentication.AuthenticationServiceException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+import org.springframework.util.Assert;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class SmsAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
+  /**
+   * form表单中手机号码的字段name
+   */
+  public static final String SPRING_SECURITY_FORM_MOBILE_KEY = "phone";
+  public static final String SPRING_SECURITY_FORM_CODE_KEY = "code";
+  public static final String SMS_AUTH_URL = "/mobileapi/sms/login";
+
+  private String mobileParameter = SPRING_SECURITY_FORM_MOBILE_KEY;
+  private String codeParameter = SPRING_SECURITY_FORM_CODE_KEY;
+  /**
+   * 是否仅 POST 方式
+   */
+  private boolean postOnly = true;
+
+  public SmsAuthenticationFilter() {
+    // 短信登录的请求 post 方式的 /sms/login
+    super(new AntPathRequestMatcher(SMS_AUTH_URL, "POST"));
+  }
+
+  @Override
+  public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
+    if (postOnly && !request.getMethod().equals("POST")) {
+      throw new AuthenticationServiceException(
+          "Authentication method not supported: " + request.getMethod());
+    }
+
+    String mobile = obtainMobile(request);
+    String code = obtainCode(request);
+
+    if (mobile == null) {
+      mobile = "";
+    }
+
+    if (code == null) {
+      code = "";
+    }
+
+    mobile = mobile.trim();
+    code = code.trim();
+
+    SmsAuthenticationToken authRequest = new SmsAuthenticationToken(mobile, code);
+
+    // Allow subclasses to set the "details" property
+    setDetails(request, authRequest);
+
+    return this.getAuthenticationManager().authenticate(authRequest);
+  }
+
+  protected String obtainMobile(HttpServletRequest request) {
+    return request.getParameter(mobileParameter);
+  }
+
+  protected String obtainCode(HttpServletRequest request) {
+    return request.getParameter(codeParameter);
+  }
+
+  protected void setDetails(HttpServletRequest request, SmsAuthenticationToken authRequest) {
+    authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
+  }
+
+  public String getMobileParameter() {
+    return mobileParameter;
+  }
+
+  public String getCodeParameter() {
+    return codeParameter;
+  }
+
+  public void setMobileParameter(String mobileParameter) {
+    Assert.hasText(mobileParameter, "Mobile parameter must not be empty or null");
+    this.mobileParameter = mobileParameter;
+  }
+
+  public void setCodeParameter(String codeParameter) {
+    Assert.hasText(codeParameter, "Code parameter must not be empty or null");
+    this.codeParameter = codeParameter;
+  }
+
+  public void setPostOnly(boolean postOnly) {
+    this.postOnly = postOnly;
+  }
+}
diff --git a/backend/src/main/java/com/supwisdom/dlpay/framework/security/auth/SmsAuthenticationProvider.java b/backend/src/main/java/com/supwisdom/dlpay/framework/security/auth/SmsAuthenticationProvider.java
new file mode 100644
index 0000000..6f9adcd
--- /dev/null
+++ b/backend/src/main/java/com/supwisdom/dlpay/framework/security/auth/SmsAuthenticationProvider.java
@@ -0,0 +1,67 @@
+package com.supwisdom.dlpay.framework.security.auth;
+
+import com.supwisdom.dlpay.framework.security.SmsAuthenticationException;
+import com.supwisdom.dlpay.mobile.service.MobileUserService;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.stereotype.Component;
+
+import java.util.Collection;
+
+@Component
+public class SmsAuthenticationProvider implements AuthenticationProvider {
+  @Autowired
+  private MobileUserService mobileUserService;
+  @Autowired
+  @Qualifier("userPasswordEncoder")
+  private BCryptPasswordEncoder passwordEncoder;
+  @Autowired
+  private RedisTemplate<String, String> redisTemplate;
+  /**
+   * @Description 认证处理,返回一个Authentication的实现类则代表认证成功,返回null则代表认证失败
+   * @Date 2019/7/5 15:19
+   * @Version  1.0
+   */
+  @Override
+  public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+    //测试,正式环境应该根据登录认证的通道来进行认证
+    String phone = authentication.getName();
+    String smsCode = (String) authentication.getCredentials();
+    if(StringUtils.isBlank(phone)){
+      throw new SmsAuthenticationException("手机号不能为空");
+    }
+    if(StringUtils.isBlank(smsCode)){
+      throw new SmsAuthenticationException("验证码不可以为空");
+    }
+    //获取用户信息
+    UserDetails user = mobileUserService.loadUserByUsername(phone);
+    //比较前端传入的密码明文和数据库中加密的密码是否相等
+    String redisCode = redisTemplate.opsForValue().get(phone);
+    if (!smsCode.equals(redisCode)) {
+      //发布密码不正确事件
+      throw new SmsAuthenticationException("验证码错误");
+    }
+    //获取用户权限信息
+    Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
+    return new SmsAuthenticationToken(user, smsCode, authorities);
+
+  }
+  /**
+   * @Description 如果该AuthenticationProvider支持传入的Authentication对象,则返回true
+   * @Date 2019/7/5 15:18
+   * @Version  1.0
+   */
+  @Override
+  public boolean supports(Class<?> aClass) {
+    return aClass.equals(SmsAuthenticationToken.class);
+  }
+
+}
diff --git a/backend/src/main/java/com/supwisdom/dlpay/framework/security/auth/SmsAuthenticationToken.java b/backend/src/main/java/com/supwisdom/dlpay/framework/security/auth/SmsAuthenticationToken.java
new file mode 100644
index 0000000..5e457a6
--- /dev/null
+++ b/backend/src/main/java/com/supwisdom/dlpay/framework/security/auth/SmsAuthenticationToken.java
@@ -0,0 +1,66 @@
+package com.supwisdom.dlpay.framework.security.auth;
+
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.SpringSecurityCoreVersion;
+
+import java.util.Collection;
+
+public class SmsAuthenticationToken extends AbstractAuthenticationToken {
+
+  private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
+
+  /**
+   * 在 UsernamePasswordAuthenticationToken 中该字段代表登录的用户名,
+   * 在这里就代表登录的手机号码
+   */
+  private final Object principal;
+  private Object credentials;
+
+  /**
+   * 构建一个没有鉴权的 SmsAuthenticationToken
+   */
+  public SmsAuthenticationToken(Object principal, Object credentials) {
+    super(null);
+    this.principal = principal;
+    this.credentials = credentials;
+    setAuthenticated(false);
+  }
+
+  /**
+   * 构建拥有鉴权的 SmsAuthenticationToken
+   */
+  public SmsAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
+    super(authorities);
+    this.principal = principal;
+    this.credentials = credentials;
+    // must use super, as we override
+    super.setAuthenticated(true);
+  }
+
+  @Override
+  public Object getCredentials() {
+    return this.credentials;
+  }
+
+  @Override
+  public Object getPrincipal() {
+    return this.principal;
+  }
+
+  @Override
+  public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
+    if (isAuthenticated) {
+      throw new IllegalArgumentException(
+          "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
+    }
+
+    super.setAuthenticated(false);
+  }
+
+  @Override
+  public void eraseCredentials() {
+    super.eraseCredentials();
+    credentials = null;
+  }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/supwisdom/dlpay/medicine/util/MedicalClient.java b/backend/src/main/java/com/supwisdom/dlpay/medicine/util/MedicalClient.java
index 801fe76..ef1cf7f 100644
--- a/backend/src/main/java/com/supwisdom/dlpay/medicine/util/MedicalClient.java
+++ b/backend/src/main/java/com/supwisdom/dlpay/medicine/util/MedicalClient.java
@@ -90,7 +90,7 @@
   public AppointmentResponse confirmAppointment(ConfirmRequestBean bean) {
     String url = getMedicalUrl();
     Map<String, Object> param = new HashMap<>(8);
-    param.put("organizationId", bean.getOrganizationid());
+    param.put("organizationId", bean.getHospitalcode());
     param.put("departmentId", bean.getDepartmentid());
     param.put("ghrq", bean.getGhrq());
     param.put("zblb", bean.getZblb());
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/medicine/MedicineApi.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/medicine/MedicineApi.kt
index 166c520..6733055 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/medicine/MedicineApi.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/medicine/MedicineApi.kt
@@ -60,7 +60,7 @@
      * 删除就诊卡
      */
     @PostMapping("/medicalcard/delete/{cardid}")
-    fun deleteMedicalCard(hospitalcode: String, @PathVariable cardid: String): JsonResult? {
+    fun deleteMedicalCard(@PathVariable cardid: String): JsonResult? {
         try {
             val p = SecurityContextHolder.getContext().authentication
             val user = mobileApiService.findUserById(p.name)
@@ -87,7 +87,7 @@
             medicineService.addPatient(bean)
             return JsonResult.ok()
         } catch (e: Exception) {
-            logger.error(e.message)
+            logger.error("系统异常",e)
             return JsonResult.error("系统异常,请稍后重试")
         }
     }
@@ -101,7 +101,7 @@
             val data = medicineService.getArrangeInfo(hospitalcode, date)
             JsonResult.ok().put("data", data)
         } catch (e: Exception) {
-            logger.error(e.message)
+            logger.error("系统异常",e)
             JsonResult.error("系统异常,请稍后重试")
         }
     }
@@ -119,7 +119,7 @@
             medicineService.confirmAppointment(bean)
             return JsonResult.ok()
         } catch (e: Exception) {
-            logger.error(e.message)
+            logger.error("系统异常",e)
             return JsonResult.error("系统异常,请稍后重试")
         }
     }
@@ -157,15 +157,15 @@
      * 获取待支付列表
      */
     @GetMapping("/unpayed/list")
-    fun getUnPayedList(organizationid: String, cardid: String): JsonResult? {
+    fun getUnPayedList(hospitalcode: String, cardid: String): JsonResult? {
         try {
             val p = SecurityContextHolder.getContext().authentication
             val user = mobileApiService.findUserById(p.name)
                     ?: return JsonResult.error("用户不存在,请注册")
-            val data = medicineService.getUnPayedList(user.uid, organizationid, cardid)
+            val data = medicineService.getUnPayedList(user.uid, hospitalcode, cardid)
             return JsonResult.ok().put("data", data)
         } catch (e: Exception) {
-            logger.error(e.message)
+            logger.error("系统异常",e)
             return JsonResult.error("系统异常,请稍后重试")
         }
     }
@@ -174,15 +174,15 @@
      * 获取已支付列表
      */
     @GetMapping("/payed/list")
-    fun getPayedList(organizationid: String, cardid: String): JsonResult? {
+    fun getPayedList(hospitalcode: String, cardid: String): JsonResult? {
         try {
             val p = SecurityContextHolder.getContext().authentication
             val user = mobileApiService.findUserById(p.name)
                     ?: return JsonResult.error("用户不存在,请注册")
-            val data = medicineService.getPayedList(user.uid, organizationid, cardid)
+            val data = medicineService.getPayedList(user.uid, hospitalcode, cardid)
             return JsonResult.ok().put("data", data)
         } catch (e: Exception) {
-            logger.error(e.message)
+            logger.error("系统异常",e)
             return JsonResult.error("系统异常,请稍后重试")
         }
     }
@@ -200,7 +200,7 @@
             return checkResult
         }
         val cardNo = checkResult["data"] as String
-        medicineService.medicalPayPreInit(user.uid, bean,cardNo)
+        medicineService.medicalPayPreInit(user.uid, bean, cardNo)
 //        medicineService.medicalPayInit()
 //        medicineService.medicalPayConfirm()
         return JsonResult.ok()
@@ -234,6 +234,6 @@
         if (!cardResponse.card.signed) {
             return JsonResult.error("请签约银行卡")
         }
-        return JsonResult.ok().put("data",userInfo.cardno)
+        return JsonResult.ok().put("data", userInfo.cardno)
     }
 }
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/medicine/bean/ConfirmRequestBean.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/medicine/bean/ConfirmRequestBean.kt
index ab28c78..9400367 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/medicine/bean/ConfirmRequestBean.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/medicine/bean/ConfirmRequestBean.kt
@@ -1,7 +1,7 @@
 package com.supwisdom.dlpay.medicine.bean
 
 class ConfirmRequestBean {
-    var organizationid = ""
+    var hospitalcode = ""
     var departmentid = ""
     var departmentname = ""
     var ghrq = 0
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/medicine/bean/PaymentRequestBean.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/medicine/bean/PaymentRequestBean.kt
index 7371a5c..70270cb 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/medicine/bean/PaymentRequestBean.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/medicine/bean/PaymentRequestBean.kt
@@ -1,7 +1,7 @@
 package com.supwisdom.dlpay.medicine.bean
 
 class PaymentRequestBean {
-    var organizationid = ""
+    var hospitalcode = ""
     var cardid = ""
     var feeno = ""
     var admnumber = ""
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/medicine/service/impl/MedicineServiceImpl.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/medicine/service/impl/MedicineServiceImpl.kt
index baeddcf..6f97b7b 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/medicine/service/impl/MedicineServiceImpl.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/medicine/service/impl/MedicineServiceImpl.kt
@@ -115,6 +115,8 @@
                     }
                     medicalCardDao.save(medicalCard)
                 }
+            } else {
+                throw MedicineException("新增就诊卡失败")
             }
         }
     }
@@ -228,7 +230,7 @@
                 ?: throw MedicineException("未找到该就诊卡")
         //1.从HIS中找到待支付流水
         //1.1 根据患者id查询未支付账单
-        val unPayedResponse = getUnPayedResponse(bean.organizationid, medicalCard.patientid)
+        val unPayedResponse = getUnPayedResponse(bean.hospitalcode, medicalCard.patientid)
         var mergingItems: MergingItems? = null
         unPayedResponse.medicalInformation.forEach { medicalInformation ->
             if (medicalInformation.admNumber == bean.admnumber) {
@@ -252,13 +254,13 @@
 
         val preFeeRequest = PreFeeRequest().apply {
             this.boilSign = mergingItems!!.boilSign
-            this.organizationId = bean.organizationid
+            this.organizationId = bean.hospitalcode
             this.feeRecords = feeRecords
         }
         val preFeeResponse = medicalClient.getPreCalculatedFee(preFeeRequest)
         //查看本地是否有该条流水记录
         val localMedicalDtl = medicalDtlDao.findByAdmnumberAndFeenoAndOrganizationidAndPaystatusNot(
-                bean.admnumber, bean.organizationid, mergingItems!!.feeNo, MedicalConstant.DTL_STATUS_FAIL)
+                bean.admnumber, bean.hospitalcode, mergingItems!!.feeNo, MedicalConstant.DTL_STATUS_FAIL)
         if (localMedicalDtl != null) {
             //锁住该条流水
             medicalDtlDao.findByBillnoForUpdate(localMedicalDtl.billno)
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/AuthLoginHandler.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/AuthLoginHandler.kt
index 67a5d81..ae7b0ce 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/AuthLoginHandler.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/AuthLoginHandler.kt
@@ -8,6 +8,8 @@
 import com.supwisdom.dlpay.framework.core.JwtTokenUtil
 import com.supwisdom.dlpay.framework.domain.JwtRedis
 import com.supwisdom.dlpay.framework.redisrepo.ApiJwtRepository
+import com.supwisdom.dlpay.framework.security.SmsAuthenticationException
+import com.supwisdom.dlpay.framework.security.auth.SmsAuthenticationFilter
 import com.supwisdom.dlpay.framework.service.SystemUtilService
 import com.supwisdom.dlpay.framework.util.*
 import com.supwisdom.dlpay.mobile.dao.MobileUserDao
@@ -48,12 +50,34 @@
 
     override fun onAuthenticationSuccess(request: HttpServletRequest, response: HttpServletResponse, authentication: Authentication) {
         val platform = request.getParameter("platform")
+        val deviceId = request.getParameter("deviceid")
+        var context: String? = request.contextPath
+        if (context == null || "" == context.trim { it <= ' ' }) {
+            context = "/"
+        }
+        var url = request.requestURI
+        if ("/" != context) {
+            url = url.replace(context, "")
+        }
+        var smsAuth = false
+        //短信认证不用校验非常用设备
+        if (SmsAuthenticationFilter.SMS_AUTH_URL == url) {
+            smsAuth = true
+        }
         logger.error(platform)
         val temp = authentication.principal as TBMobileUser
         val user = mobileApiService.findUserById(temp.uid)
         val exp = systemUtilService.getSysparaValueAsInt(SysparaUtil.MOBILE_LOGIN_EXPIRE_IN_SECONDS,60*60*24*3)
         jwtConfig.expiration = exp.toLong()
         if (user != null) {
+            if (null != user.lastlogindeviceid && deviceId != user.lastlogindeviceid && !smsAuth) {
+                //非常用登录设备
+                response.status = HttpStatus.OK.value()
+                response.contentType = "application/json;charset=UTF-8"
+                response.writer.write(objectMapper.writeValueAsString(JsonResult.ok()
+                        .put("needcheck", true)))
+                return
+            }
             //TODO 从数据取jwtConfig.expiration
             val token = JwtTokenUtil(jwtConfig).generateToken(
                     mapOf("uid" to user.uid, "issuer" to "payapi",
@@ -78,6 +102,7 @@
             }
             user.lastlogin = DateUtil.getNow()
             user.lastloginplatform = platform
+            user.lastlogindeviceid = deviceId
             user.jti = jwt.jti
             mobileApiService.saveUser(user)
             var payseted = false
@@ -135,6 +160,7 @@
                     ?.put("citizenCardNo",citizenCardNo)
                     ?.put("bankCardNo",bankCardNo)
                     ?.put("idno",idno)
+                    ?.put("needcheck",false)
                     ?.put("userid",if(user.userid.isNullOrEmpty()) "" else user.userid)))
         } else {
             throw UserLoginFailException("登录错误")
@@ -158,6 +184,7 @@
         val errmsg = when (exception) {
             is BadCredentialsException -> "手机号或密码错误"
             is LockedException -> "账户被锁定"
+            is SmsAuthenticationException -> "验证码错误"
             else -> exception.message!!
         }
         val temp = request.getParameter("username")
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt
index 3c3e0fc..961c853 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt
@@ -103,7 +103,7 @@
                 return JsonResult.error("该手机号已注册,请登录或找回密码")
             }
         } else {
-            if ("find" == type) {
+            if ("find" == type || "check" == type) {
                 if (user == null) {
                     return JsonResult.error("手机号不存在,请注册")
                 }
@@ -493,8 +493,7 @@
     }
 
     @RequestMapping("/checkcode")
-    fun check(@RequestParam code: String, @RequestParam personid: String?
-    ): JsonResult {
+    fun check(@RequestParam code: String, @RequestParam personid: String?): JsonResult {
         val p = SecurityContextHolder.getContext().authentication
         val user = mobileApiService.findUserById(p.name)
                 ?: return JsonResult.error("用户不存在,请注册")
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/domain/TBMobileUser.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/domain/TBMobileUser.kt
index 7f84e2c..7ec41ec 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/domain/TBMobileUser.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/domain/TBMobileUser.kt
@@ -9,7 +9,7 @@
 import javax.validation.constraints.NotNull
 
 @Entity
-@Table(name = "TB_MOBILE_USER",indexes = [Index(name = "mobile_user_loginid_idx", columnList = "loginid", unique = true)])
+@Table(name = "TB_MOBILE_USER", indexes = [Index(name = "mobile_user_loginid_idx", columnList = "loginid", unique = true)])
 class TBMobileUser : UserDetails {
     override fun getAuthorities(): Collection<GrantedAuthority>? {
         return this.auths
@@ -32,7 +32,7 @@
     }
 
     override fun isAccountNonExpired(): Boolean {
-        if(expiredate.isNullOrEmpty()){
+        if (expiredate.isNullOrEmpty()) {
             return true
         }
         return this.expiredate!! >= DateUtil.getNow("yyyyMMdd")
@@ -107,6 +107,11 @@
     var lastloginplatform: String? = null
 
     /**
+     * 最后登录设备id
+     */
+    @Column(name = "lastlogindeviceid", length = 50)
+    var lastlogindeviceid: String? = null
+    /**
      * 状态
      * */
     @Column(name = "status", length = 16)
@@ -167,7 +172,7 @@
     var secertkey: String? = null
 
     @Column(name = "tenantid", length = 32)
-    var tenantid:String? = null
+    var tenantid: String? = null
 
     @Column(name = "expiredate", length = 8)
     var expiredate: String? = null
@@ -181,7 +186,7 @@
     @Column(name = "lastsignintime", length = 14)
     var lastsignintime: String? = null
 
-    fun checkLoginpwdtime():Int{
+    fun checkLoginpwdtime(): Int {
         if (this.loginpwderror >= 5 && (System.currentTimeMillis() - this.loginpwderrortime!!) < 1000 * 60 * 30) {
             return -1
         } else if (this.loginpwderror >= 5 && (System.currentTimeMillis() - this.loginpwderrortime!!) > 1000 * 60 * 30) {
@@ -211,7 +216,7 @@
         }
     }
 
-    fun checkPaypwdtime():Int{
+    fun checkPaypwdtime(): Int {
         if (this.paypwderror >= 5 && (System.currentTimeMillis() - this.paypwderrortime!!) < 1000 * 60 * 30) {
             return -1
         } else if (this.paypwderror >= 5 && (System.currentTimeMillis() - this.paypwderrortime!!) > 1000 * 60 * 30) {
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/security.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/security.kt
index 716dd2d..cc591c3 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/security.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/security.kt
@@ -4,10 +4,11 @@
 import com.supwisdom.dlpay.framework.core.JwtTokenUtil
 import com.supwisdom.dlpay.framework.core.PasswordBCryptConfig
 import com.supwisdom.dlpay.framework.redisrepo.ApiJwtRepository
+import com.supwisdom.dlpay.framework.security.auth.SmsAuthenticationFilter
+import com.supwisdom.dlpay.framework.security.auth.SmsAuthenticationProvider
 import com.supwisdom.dlpay.framework.service.OperatorDetailService
 import com.supwisdom.dlpay.framework.tenant.TenantContext
 import com.supwisdom.dlpay.framework.util.Constants
-import com.supwisdom.dlpay.framework.util.StringUtil
 import com.supwisdom.dlpay.framework.util.TradeDict
 import com.supwisdom.dlpay.mobile.AuthLoginFailHandler
 import com.supwisdom.dlpay.mobile.AuthLoginSuccessHandler
@@ -375,6 +376,8 @@
             lateinit var userDetailsService: MobileUserService
             @Autowired
             lateinit var mobileSecurityFilter: MobileSecurityFilter
+            @Autowired
+            lateinit var smsAuthenticationProvider: SmsAuthenticationProvider
 
 
             override fun configure(auth: AuthenticationManagerBuilder) {
@@ -400,6 +403,12 @@
             }
 
             override fun configure(http: HttpSecurity) {
+                val smsCodeAuthenticationFilter = SmsAuthenticationFilter()
+                smsCodeAuthenticationFilter.setAuthenticationManager(super.authenticationManagerBean())
+                smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(successHandler)
+                smsCodeAuthenticationFilter.setAuthenticationFailureHandler(failureHandler)
+                http.authenticationProvider(smsAuthenticationProvider)
+                        .addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter::class.java)
                 http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                         .and()
                         .cors()
diff --git a/config/application-devel-pg.properties b/config/application-devel-pg.properties
index 323cd35..67f03e9 100644
--- a/config/application-devel-pg.properties
+++ b/config/application-devel-pg.properties
@@ -5,17 +5,17 @@
 spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false
 # Postgresql settings
 spring.datasource.platform=postgresql
-#spring.datasource.url=jdbc:postgresql://ykt.supwisdom.com:15432/payapidev
-spring.datasource.url=jdbc:postgresql://172.28.201.70:15432/portal
+#spring.datasource.url=jdbc:postgresql://172.28.201.70:15432/portal
+spring.datasource.url=jdbc:postgresql://localhost:5432/portal
 spring.datasource.username=payapi
 spring.datasource.password=123456
 spring.datasource.continue-on-error=true
 spring.datasource.initialization-mode=always
 # Redis settings
 #spring.redis.host=ykt.supwisdom.com
-spring.redis.host=172.28.201.70
+spring.redis.host=localhost
 spring.redis.port=6379
-spring.redis.password=
+spring.redis.password=system
 spring.redis.database=2
 #port
 server.port=8089