<관리자 MTS 본인인증 기능 추가>

[Base]
(1) Base/Base.csproj
 - AOCommon.cs 추가
(2) Base/Controller/AOCommon.cs
 - 관리자 본인인증 기능 class 추가
(3) Base/Lib/ENUM.cs
 - IpHostSkipGb 추가
(4) Base/Lib/Helpers.cs
 - IsSkipIPorHost() 매서드추가 : ip스킵여부 체크
[BO]
(1) BO/BO.csproj
 - Partial\MobileChk.cshtml 추가 (sms체크 레이어팝업)
(2) BO/Spring/Controllers.xml
 - aocommon bean추가 : 본인인증
(3) BO/Views/Account/Index.cshtml
 - index수정 (본인인증기능추가)
(4) BO/Views/Shared/Partial/MobileChk.cshtml
 - 본인인증 layer팝업
[Dao]
(1) Dao/MyBatis/Maps/User.xml
 - <select id="users.adminlogin">쿼리에 필드추가
 : CAST(AES_DECRYPT(UNHEX(a.mobile), <include refid="sql.digest"></include>) AS char) mobile
 => 본인인증 체크(핸드폰번호) 를 위해서, 핸드폰번호를 복호화하는 필드를 추가
[Model]
(1) Model/Common.cs
 - SmsAuth class에 userno 속성추가
This commit is contained in:
hjcho 2022-04-08 04:51:39 +00:00
parent a81f39ed46
commit acbdbc0c83
10 changed files with 473 additions and 13 deletions

View File

@ -697,6 +697,7 @@
<Content Include="Views\user\pwchange.cshtml" />
<Content Include="Views\user\editinfo.cshtml" />
<Content Include="Views\Shared\Partial\ScdScript2.cshtml" />
<Content Include="Views\Shared\Partial\MobileChk.cshtml" />
</ItemGroup>
<ItemGroup>
<Folder Include="App_Data\" />

View File

@ -29,6 +29,8 @@
<property name="TestCode3" value="Test003" />
</object>
<object id="acommon" parent="baseController" singleton="false" type="NP.Base.Controllers.ACommonController, NP.Base"></object>
<!-- 본인인증-->
<object id="aocommon" parent="baseController" singleton="false" type="NP.Base.Controllers.AOCommonController, NP.Base"></object>
<object id="bobase" parent="baseController" singleton="false" type="NP.BO.Controllers.BOBaseController, NP.BO"></object>

View File

@ -47,7 +47,7 @@
<input type="password" name="user.UserPass" id="pw" placeholder="PASSWORD" class="form-control input-lg">
</div>
<div class="text-center">
<label><input type="checkbox" id="SavedId2" @(string.IsNullOrEmpty(Model.SavedId)?"":"checked") />Remember ID</label>
<label><input type="checkbox" id="SavedId2" @(string.IsNullOrEmpty(Model.SavedId) ? "" : "checked") />Remember ID</label>
<input type="hidden" name="SavedId" id="SavedId" value="@Model.SavedId" />
</div>
<a href="#" id="btn_login" class="btn btn-facebook btn-block m-b-sm">Login to Your Account</a>
@ -98,17 +98,13 @@
}
}
});
$("#btn_login").on("click", function () {
login();
});
$("#user_UserId").focus();
if ($("#user_UserId").val() !== "") {
$("#pw").focus();
}
if ('@(Model.IsLoginFailed?1:0)' == '1') {
if ('@(Model.IsLoginFailed ? 1 : 0)' == '1') {
switch ('@Model.LoginFailedId') {
case "2":
case "3":
@ -158,6 +154,15 @@
$('#xxx').html("인증시간이 초과되어 로그아웃되었습니다.");
$(".xxx").show();
}
@* @custom@ : 본인인증추가 *@
$("#btn_login").on("click", function () {
capp("/aocommon/CheckLoginIp", { loginid: $("#user_UserId").val(), pw: $("#pw").val() }, "checkloginip");
});
$("#user_UserId").on("change", function () {
cnt = 5;
});
});
//function ispop() {
// try {
@ -166,7 +171,52 @@
// catch (e) { }
// return false;
//}
@* @custom@ : 본인인증검증(before 인증문자발송) callback *@
var cnt = 5;
var id = "";
function checkloginip() {
debugger;
if (cnt > 0) {
if (capResult.obj.code == 1) {
/*//20210707 특정ip, 개발서버 제외요청*/
@{
bool isSkip = Helpers.IsSkipIPorHost(NP.Base.ENUM.IpHostSkipGb.SmsAuth, Request.ServerVariables["REMOTE_ADDR"], Request.ServerVariables["HTTP_HOST"]);
}
@if (isSkip)
{
@: login();
}
}
else if (capResult.obj.code == 1000) {
$("#ipaddress").html(capResult.obj.ip);
$("#boxmobile").html(capResult.obj.mobile);
capp("/aocommon/sendlakey", { mobile: capResult.obj.mobile }, "sendkey");
}
else if (capResult.obj.code == -1) {
cnt--;
$('#xxx').html("계정을 확인 후 다시 로그인하세요.");
$(".xxx").show();
}
else if (capResult.obj.code == -2) {
$('#xxx').html("입력하신 계정에 해당하는 휴대폰 정보가 없습니다. 관리자에게 문의하세요.");
$(".xxx").show();
}
else if (capResult.obj.code == -3) {
$('#xxx').html("입력하신 계정이 존재하지 않습니다. 관리자에게 문의하세요.");
$(".xxx").show();
}
else if (capResult.obj.code == -4) {
$('#xxx').html("해당 계정은 잠금상태입니다. 관리자에게 문의하세요.");
$(".xxx").show();
}
else {
$('#xxx').html("계정을 확인 후 다시 로그인하세요.");
$(".xxx").show();
}
} else {
capp("/aocommon/disableaccount", { loginid: $("#user_UserId").val() }, "cbdisableaccount");
}
}
function login() {
if ($.trim($("#user_UserId").val()) === "") {
_focus = $("#user_UserId");
@ -190,7 +240,43 @@
}
}
var _focus;
@* @custom@ : 본인인증처리 callback *@
function cbchkmobile() {
if (capResult.code == 1000) {
$('#xxxlogin').html("인증이 완료되었습니다. 로그인합니다.");
$(".xxxlogin").show();
mobilechkclose();
setTimeout(function () {
$("#fmlogin").submit();
}, 500);
} else if (capResult.code == 1) {
$('#xxx').html("유효시간이 만료되었습니다.\n 창을 닫고 인증을 다시 진행해주세요.");
$(".xxx").show();
hidebox();
} else {
$('#xxx').html("인증이 실패하였습니다.\n 다시 확인해주세요.");
$(".xxx").show();
}
}
function cbdisableaccount() {
if (capResult.code == 1000) {
$('#xxx').html("해당 계정은 잠금상태입니다. 관리자에게 문의하세요.");
$(".xxx").show();
} else {
msgdev();
}
}
function hidebox() {
$('.divIpcheck').slideUp('fast');
}
function showbox() {
$(".divIpcheck").slideDown("fast");
}
</script>
<div class="xxx" style="position: fixed; z-index: 10; background-color: #555; opacity: 0.7; left: 0; top: 0; right: 0; bottom: 0; display: none;"></div>
<div class="xxx" id="msginfo" style="position: fixed; z-index: 11; background-color: #ddd; left: 50%; width: 50%; text-align: center; margin-left: -25%; top: 30%; padding: 30px 30px; border-radius: 5px; font-size: 18px; display: none;">
<span id="xxx">인증시간이 초과되어 로그아웃되었습니다.</span>
@ -198,5 +284,12 @@
<br />
<a class="btn btn-lg btn-info" onclick="$('.xxx').hide(); $(_focus).focus();">확인</a>
</div>
@* @custom@ : 본인인증처리 layer form*@
<div class="xxxlogin" style="position: fixed; z-index: 10; background-color: #555; opacity: 0.7; left: 0; top: 0; right: 0; bottom: 0; display: none;"></div>
<div class="xxxlogin" id="msginfo" style="position: fixed; z-index: 11; background-color: #ddd; left: 50%; width: 50%; text-align: center; margin-left: -25%; top: 30%; padding: 30px 30px; border-radius: 5px; font-size: 18px; display: none;">
<span id="xxxlogin"></span>
</div>
@Html.Partial("./Partial/MobileChk", null, new ViewDataDictionary { { "bindmethod", "cbchkmobile" } })
</body>
</html>

View File

@ -0,0 +1,124 @@
@model NP.Model.VMUser
@{
var _method = ViewData["bindmethod"].ToString();
}
<div id="mobilechk" class="mobilechk" style="position: fixed; z-index: 5; background-color: #282828; opacity: 5; left: 0; top: 0; right: 0; bottom: 0; display: none;">
<div class="mobilechk" style="position: fixed; z-index: 6; background-color: #ddd; left: 50%; width: 50%; text-align: left; margin-left: -25%; top: 30%; padding: 30px 30px; border-radius: 5px; font-size: 18px; display: none;">
<div>
<div>
<div class="">
<h4><i class="fa fa-bars"></i> 본인 인증</h4>
<div class="">
<div id="boxassignnewdata">
<p class="clspNon" style="padding-bottom:10px;">사용자 IP : <span id="ipaddress"></span></p>
<p class="clspNon" style="padding-bottom:10px;">인증요청한 휴대폰번호 : <span id="boxmobile"></span></p>
<p class="clspNon" style="padding-top:10px;">휴대폰으로 전송된 인증번호 6자리를 입력하신 후 확인버튼을 눌러주세요.</p>
<div class="clspInput" style="padding-bottom:10px;">
<input type="text" class="int nocomma" style="width:auto;" maxlength="6" id="mobile_lakey" name="lakey" placeholder="인증번호 입력" />
<input type="button" value="재전송" onclick="resendlakey()" />
</div>
<div class="">
<span id="lblrtime">(남은 시간 0분 00초)</span>
<input type="button" value="시간연장" onclick="pluslearnauthtime()" />
</div>
</div>
<br />
<div class="" style="text-align:center">
<input type="button" value="확인" onclick="chklakey()" class="btn btn-find btn-info btn-sm" />
<input type="button" value="닫기" onclick="mobilechkclose()" class="btn btn-find btn-info btn-sm" />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$("#mobile_lakey").keydown(function (key) {
if (key.keyCode == 13) {
setTimeout(function () {
chklakey();
}, 500);
}
});
});
var _vd_tm1;
var _vd_tmsec;
function mobilechkclose() {
clearInterval(_vd_tm1);
$(".mobilechk").slideUp("fast");
}
var mobile;
function mobilechkview() {
if ($("#boxmobile").html() != "") {
mobile = $("#boxmobile").html();
}
//if ($("#addstringval").val() != "") {
// mobile = $("#addstringval").val();
//}
$("#boxmobile").html(mobile);
$(".mobilechk").slideDown("fast");
clearInterval(_vd_tm1);
_vd_tmsec = 180;
_vd_tm1 = setInterval(sendlatimer, 1000);
}
function resendlakey() {
$("#lblrtime").text("(남은 시간 3분 00초)");
clearInterval(_vd_tm1);
_vd_tmsec = 180;
_vd_tm1 = setInterval(sendlatimer, 1000);
$('#xxx').html("인증번호를 재전송하였습니다.");
$(".xxx").show();
capp("/aocommon/sendlakey", { mobile: mobile }, "sendkey");
}
function sendlatimer() {
_vd_tmsec--;
if (_vd_tmsec < 1) {
clearInterval(_vd_tm1);
$('#xxx').html("유효시간이 만료되었습니다.<br/>창을 닫고 다시 진행해주세요.");
$(".xxx").show();
mobilechkclose();
}
if (_vd_tmsec > 59) {
$("#lblrtime").text("(남은 시간 " + (parseInt(_vd_tmsec / 60)) + "분 " + (_vd_tmsec % 60) + "초)");
} else {
$("#lblrtime").text("(남은 시간 " + (_vd_tmsec) + "초)");
}
}
var authno = 0;
function pluslearnauthtime() {
clearInterval(_vd_tm1);
_vd_tmsec = 180;
_vd_tm1 = setInterval(sendlatimer, 1000);
capp("/aocommon/extendlakey", { authno: authno }, "extlakey");
}
function extlakey() {
$('#xxx').html("인증시간 연장");
$(".xxx").show();
}
function sendkey() {
if (capResult.obj > 0) {
//msg("인증코드 발송");
authno = capResult.obj;
mobilechkview();
} else if (capResult.code == -1) {
$('#xxx').html("휴대폰번호를 번호로 등록한 회원이 두명이상입니다.<br/>운영자에게 문의해주시기 바랍니다.");
$(".xxx").show();
} else if (capResult.code == -2) {
$('#xxx').html("해당 번호로 등록한 회원이 없습니다.");
$(".xxx").show();
}else {
$('#xxx').html("발송실패, 인증코드를 재전송해주세요.");
$(".xxx").show();
}
}
function chklakey() {
if ($("#mobile_lakey").val() == "") {
focus("mobile_lakey");
return;
}
capp("/aocommon/chklakey", { lakey: $("#mobile_lakey").val(), authno: authno }, "@(_method)");
}
</script>

View File

@ -147,6 +147,7 @@
<Compile Include="Controller\ACommonUser.cs" />
<Compile Include="Controller\ACommonCM.cs" />
<Compile Include="Controller\ACommon.cs" />
<Compile Include="Controller\AOCommon.cs" />
<Compile Include="Controller\BaseController.cs" />
<Compile Include="Controller\BasePartialController.cs" />
<Compile Include="Controller\FCommonCC.cs" />

181
Base/Controller/AOCommon.cs Normal file
View File

@ -0,0 +1,181 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using NP.Model;
using System.Collections;
using NP.Base.Auth;
using NP.Base.ENUM;
namespace NP.Base.Controllers
{
/// <summary>
/// @custom@ : 본인인증처리 controller
/// </summary>
public partial class AOCommonController : NP.Base.BaseController
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
}
protected override void OnException(ExceptionContext filterContext)
{
base.OnException(filterContext);
if (Request.IsAjaxRequest())
{
filterContext.Result = new RedirectResult("/Account/NoPermit?_code=" + NP.Base.ENUM.JSONCode.Error + "&_msg=알수없는오류");
}
else
{
filterContext.Result = new RedirectResult("/Account/Error?_code=" + NP.Base.ENUM.JSONCode.Error + "&_msg=알수없는오류");
}
}
/// <summary>
/// @custom@ : 인증문자발송처리
/// </summary>
/// <param name="mobile"></param>
/// <returns></returns>
[HttpPost]
public JsonResult SendLakey(String mobile)
{
long result = 0;
Random r = new Random();
int lakey = r.Next(100000, 999999);
SmsAuth sa = new SmsAuth() { lakey = lakey.ToString(), userno=SUserInfo.UserNo };
String msg = "[영남건설기술교육원]\n\n영남건설기술교육원 인증번호 [" + lakey + "] 입니다.";
Dao.Insert("common.smsauth", sa);
result = sa.authno;
IList<NP.Model.MemoUser> us = new List<NP.Model.MemoUser>();
us.Add(new MemoUser()
{
userno = SUserInfo.UserNo,
mobile = mobile,
title = "인증번호",
mcontents = msg,
smstype = "A",
isok = 1
});
SaveTalk(us, "alarm");
return JsonOK(result);
}
/// <summary>
/// @custom@ : 본인인증처리
/// </summary>
/// <param name="lakey"></param>
/// <param name="authno"></param>
/// <returns></returns>
[HttpPost]
public JsonResult ChkLakey(String lakey, int authno)
{
IList<Data> datas = Dao.Get<Data>("common.sms.chk", new Hashtable() { { "authno", authno } });
if (datas.Count() < 1)
{
return JsonOK(0);
}
else if (datas.Where(w => w.strval.Replace(" ", "").Equals(lakey.Replace(" ", "")) && w.time.AddMinutes(3) < DateTime.Now).Count() > 0)
{
return JsonBack(new JsonRtn() { code = 1 });
}
else if (datas.Where(w => w.strval.Replace(" ", "").Equals(lakey.Replace(" ", "")) && w.time.AddMinutes(3) >= DateTime.Now).Count() > 0)
{
return JsonBack(new JsonRtn() { code = 1000, obj = datas.First() });
}
return JsonOK(0);
}
[HttpPost]
public JsonResult ExtendLakey(int authno)
{
return JsonOK(Dao.Save("common.sms.extend", new Hashtable() { { "authno", authno } }));
}
[HttpPost]
public JsonResult ViewCorrection(int sdno, int lectno)
{
var rtn = new Hashtable() { };
var sd = Dao.Get<LectSD>("lect.lectdiscuss", new Hashtable() { { "lectno", lectno }, { "sdno", sdno } }).FirstOrDefault();
rtn.Add("sd", sd);
return JsonBack(rtn);
}
[HttpPost]
public JsonResult CheckMobile(String mobile)
{
var checkuser = Dao.Get<int>("users.checkuser", new Hashtable() { { "mobile", mobile }, { "userid", null }, { "email", null } }).First();
if (checkuser < 9)
{
return JsonOK(0);
}
return JsonOK(1);
}
/// <summary>
/// 본인인증검증 (before 인증문자발송)
/// </summary>
/// <param name="loginid"></param>
/// <param name="pw"></param>
/// <returns></returns>
[HttpPost]
public JsonResult CheckLoginIp(string loginid, string pw)
{
string ip = GetUserIP();
var p = new Hashtable { { "userid", loginid }, { "password", NP.Base.Lib.KISA_SHA256.SHA256Hash(pw) }};
var ul = Dao.Get<Users>("users.adminlogin", p);
var u= new Users() { };
//해당 아이디인 계정이 없는 경우
if(ul.Count() < 1)
{
return JsonBack(new { code = -3 });
}
else
{
u = ul.FirstOrDefault();
// @custom@ : 로컬&nptech 자동로그인처리
bool isSkip = Helpers.IsSkipIPorHost(IpHostSkipGb.PassWord, ip, Request.ServerVariables["HTTP_HOST"]);
if (isSkip)
{
return JsonBack(new { code = 1 });
}
//비활성화 잠금
if (u.status == 9)
{
return JsonBack(new { code = -4 });
}
//비밀번호 불일치
if (u.userpass != NP.Base.Lib.KISA_SHA256.SHA256Hash(pw))
{
return JsonBack(new { code = -1 });
}
//210707 eduwreq 특정ip인증허용 정책 폐지
//if (ip.StartsWith("10.10.4.") || ip.StartsWith("10.10.13.") || ip == "192.168.103.13" || ip == "192.168.0.87" || ip == "121.140.58.113")
//return JsonBack(new JsonRtn() { code = 1});
//else
//{
//정보에 모바일번호 없는 경우
if (u != null && !string.IsNullOrEmpty(u.mobile))
{
return JsonBack(new { code = 1000, ip = ip, mobile = u.mobile });
}
//기타에러
else
{
return JsonBack(new { code = -2 });
}
}
//}
}
[HttpPost]
public JsonResult DisableAccount(string loginid)
{
return JsonOK(Dao.Save("users.disable", new Hashtable() { { "userid", loginid } }));
}
}
}

View File

@ -131,4 +131,14 @@ namespace NP.Base.ENUM
Voca,
}
/// <summary>
/// Ip 또는 Host 스킵구분
/// </summary>
public enum IpHostSkipGb
{
PassWord, // 패스워드 skip
SmsAuth // SMS인증 skip
}
}

View File

@ -482,4 +482,44 @@ public static class Helpers
return stringBuilder.ToString();
}
/// <summary>
/// 사용자 인증체크시(로그인/본인인증) 특정ip또는host skip할건지 여부
/// <param name="gb">pwd:패스워드skip, smsauth:sms인증skip(</param>
/// <param name="ip"></param>
/// <param name="host"></param>
/// <returns>true:skip처리, false:skip하지않음</returns>
/// </summary>
public static bool IsSkipIPorHost(IpHostSkipGb gb, string ip, string host)
{
string ipAddrs, hosts;
//ipAddrs1 = "127.0.0.1,218.232.111.111,59.150.105.195,59.150.105.198";
//ipAddrs2 = "218.232.111.111,59.150.105.195,59.150.105.198";
//ipAddrs1 = "127.0.0.1,218.232.111.111,59.150.105.195,59.150.105.198";
//hosts = "ynictea.nptc.kr";
ipAddrs = "";
hosts = "";
switch (gb)
{
case IpHostSkipGb.PassWord:
if (ipAddrs.Contains(ip) || hosts.Contains(host))
{
return true;
}
break;
case IpHostSkipGb.SmsAuth:
if (ipAddrs.Contains(ip) || hosts.Contains(host))
{
return true;
}
break;
default:
break;
}
return false;
}
}

View File

@ -43,10 +43,12 @@
</dynamic>
</sql>
<select id="users.adminlogin" parameterClass="hashtable" resultClass="users">
select userno,a.username,a.status,a.userpass, usertype,a.asno, a.logintime
from users a
left outer join assign b on b.asno=a.asno
where a.userid=#userid# and a.usertype &gt; 10 and (a.usertype &lt;&gt; 81 or (a.usertype = 81 and b.status=1))
select userno,a.username,a.status,a.userpass, usertype,a.asno
, CAST(AES_DECRYPT(UNHEX(a.mobile), <include refid="sql.digest"></include>) AS char) mobile
, a.logintime
from users a
left outer join assign b on b.asno=a.asno
where a.userid=#userid# and a.usertype &gt; 10 and (a.usertype &lt;&gt; 81 or (a.usertype = 81 and b.status=1))
</select>
<insert id="users.loginlog" parameterClass="hashtable">
insert into loginlog (logsite,userno, loginstatus, logindata, <include refid="sql.inc"></include>) values (#loginsite#,#userno#, #loginstatus#, #logindata#, <include refid="sql.inv"></include>)

View File

@ -238,6 +238,12 @@ namespace NP.Model
/// 강좌번호
/// </summary>
public Int64 lectno { get; set; }
/// <summary>
/// 요청자
/// </summary>
public Int64 userno { get; set; }
/// <summary>
/// 인증요청시간
/// </summary>