<추가사항>

2021.11.29 박상완 (swpark@nptc.kr) 

1. 로그인 정책
 1) 로그인정책 
 2) 로그인 세션 
 3) 중복로그인 차단

2. 메인-90일 비밀번호
 1) 비밀번호 변경 90일 지났을 때 팝업 및 로직
 (운영 배포시, /user/pwchange 권한 필요)

# 커밋 파일
Base\Controller\ACommonUser.cs
Base\Controller\BaseController.cs
BO\Controllers\AccountController.cs
BO\Controllers\cmController.cs
BO\Controllers\userController.cs
BO\js\site.js
BO\Views\Account\Index.cshtml
BO\Views\cm\terms.cshtml
BO\Views\croom\grades.cshtml
BO\Views\Home\Index.cshtml
BO\Views\user\us.cshtml
Dao\MyBatis\Maps\Common.xml
Dao\MyBatis\Maps\User.xml
Model\VMUser.cs
C:\project\YNICTE\BO\Spring\Controllers.xml
C:\project\YNICTE\BO\Spring\ControllersStaging.xml

# 테스트 결과 
- 이상없음

# 특이사항 및 이슈사항
- 이상없음
This commit is contained in:
swpark 2021-11-29 06:11:55 +00:00
parent 25b0c71f14
commit de982a73c6
16 changed files with 162 additions and 15 deletions

View File

@ -30,6 +30,9 @@ namespace NP.BO.Controllers
{
vm.SavedId = CookieGet("SavedId", "");
}
// 로그인 실패 카운트 초기화
vm.logincnt = 0;
return View(vm);
}
public JsonResult PassGet(String pw)
@ -39,6 +42,7 @@ namespace NP.BO.Controllers
[HttpPost]
public ActionResult Index(VMUser vm, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (!string.IsNullOrEmpty(vm.SavedId))
{
@ -63,11 +67,37 @@ namespace NP.BO.Controllers
{
LoginStatus = 1;
ht["userno"] = u.userno;
// 최근로그인시점 + 90일 일 경우, 비활성처리
if (DateTime.Now >= u.logintime.AddMonths(3))
{
Random r = new Random();
var loginkey = r.Next(10000000, 99999999);
Dao.Save("users.loginkey", new Hashtable() { { "userno", u.userno }, { "loginkey", loginkey } });
Dao.Save("users.disable", new Hashtable() { { "userno", u.userno } });
u.status = 9;
}
//vm.IntranetIPs = Dao.Get<IntranetIP>("users.intranetip", new Hashtable() { {"IsActive", 1 } });
//0: 정상(로그인성공), 1: 정상(외부아이피), 2: 비밀번호오류, 3: 아이디오류, 4: 외부아이피차단, 5: 퇴사자, 6: 사용안함, 7: 크래킹공격
if (u.status != 1) { LoginStatus = 6; }
//0: 정상(로그인성공), 1: 정상(외부아이피), 2: 비밀번호오류, 3: 아이디오류, 4: 외부아이피차단, 5: 퇴사자, 6: 사용안함, 7: 크래킹공격, 8:비활성상태
if (u.status != 1) {
if (u.status == 9) {
LoginStatus = 8;
} else
{
LoginStatus = 6;
}
}
//else if (u.RetireDate != null) { LoginStatus = 5; }
else if (!"192.168.0.87,192.168.0.56,127.0.0.1,59.150.105.198".Contains(SUserInfo.LoginIP) && !u.userpass.Equals(NP.Base.Lib.KISA_SHA256.SHA256Hash(vm.User.userpass.Trim()))) { LoginStatus = 2; }
else if (!"192.168.1.3,127.0.0.1,192.168.0.87,192.168.0.56,59.150.105.198".Contains(SUserInfo.LoginIP) && !u.userpass.Equals(NP.Base.Lib.KISA_SHA256.SHA256Hash(vm.User.userpass.Trim()))) {
LoginStatus = 2;
// 로그인 실패 카운트 증가
vm.logincnt++;
// 로그인 비밀번호 5번 실패시, 비활성화 처리
if (vm.logincnt > 4) {
Dao.Save("users.disable", new Hashtable() { { "userno", u.userno } });
}
}
else
{
//var isIntranet = false;
@ -87,8 +117,10 @@ namespace NP.BO.Controllers
//else if (u.Security == 1 && !"127.0.0.1,59.150.105.198".Contains(SUserInfo.LoginIP)) { LoginStatus = 4; }
LoginStatus = 0;
}
ht["loginstatus"] = LoginStatus;
Dao.Insert("users.loginlog", ht);
if(LoginStatus < 2)
{
Random r = new Random();

View File

@ -536,5 +536,6 @@ namespace NP.BO.Controllers
}
return View(vm);
}
}
}

View File

@ -295,5 +295,12 @@ namespace NP.BO.Controllers
}
return View(vm);
}
// 비밀번호 90일 경과된 사용자 정보 조회
public ActionResult pwchange(VMUser vm)
{
vm.User = Dao.Get<Users>("users.users", new System.Collections.Hashtable() { { "userno", SUserInfo.UserNo }, { "includesysadmin", 1 } }).First();
return View(vm);
}
}
}

View File

@ -17,7 +17,7 @@
<property name="OnCode1" value="On001" />
<property name="OnCode2" value="On002" />
<property name="OnCode3" value="On003" />
<property name="IsDupCheck" value="0" />
<property name="IsDupCheck" value="1" />
<!--자격검정-->
<property name="TestCode" value="Test" />

View File

@ -17,7 +17,7 @@
<property name="OnCode1" value="On001" />
<property name="OnCode2" value="On002" />
<property name="OnCode3" value="On003" />
<property name="IsDupCheck" value="0" />
<property name="IsDupCheck" value="1" />
<!--자격검정-->
<property name="TestCode" value="Test" />

View File

@ -55,6 +55,9 @@
- 본 시스템은 <span style="color: red;">구글 크롬</span></b>에 최적화되어있습니다.<br />
- 타 브라우저로 접근 시 일부 기능이 제한될 수 있습니다.(<a style="text-decoration: underline;" href="https://www.google.com/intl/ko_ALL/chrome/" target="_blank" title="크롬다운로드">크롬 다운로드</a>)
</div>*@
<input type="hidden" id="logincnt" name="logincnt" value="0"/>
</form>
</section>
</div>
@ -69,7 +72,7 @@
<script src="/js/app.js"></script>
<script src="/js/app.plugin.js"></script>
<script src="/js/slimscroll/jquery.slimscroll.min.js"></script>*@
<script>
<script>
window.onunload=function(){
window.location.replace(self.location);
}
@ -80,6 +83,10 @@
}
}
});
// 로그인 실패 카운트
var login_cnt = @Model.logincnt;
$(document).ready(function () {
$("input").on("keydown", function (e) {
if (e.keyCode == 13) {
@ -98,10 +105,20 @@
if ($("#user_UserId").val() !== "") {
$("#pw").focus();
}
if ('@(Model.IsLoginFailed?1:0)' == '1') {
switch ('@Model.LoginFailedId') {
case "2":
case "3":
// 로그인 비밀번호 5번 실패시
if (@Model.logincnt > 4) {
$('#xxx').html("비밀 번호가 5회 틀렸습니다. 관리자에게 문의해주세요.");
$(".xxx").show();
break;
}
$('#xxx').html("계정을 확인 후 다시 로그인하세요.");
$(".xxx").show();
break;
@ -109,6 +126,10 @@
$('#xxx').html("외부접근이 차단된 상태입니다.");
$(".xxx").show();
break;
case "8":
$('#xxx').html("해당 계정은 잠금상태입니다. 관리자에게 문의해주세요");
$(".xxx").show();
break;
case "5":
case "6":
case "7":
@ -145,6 +166,7 @@
// catch (e) { }
// return false;
//}
function login() {
if ($.trim($("#user_UserId").val()) === "") {
_focus = $("#user_UserId");
@ -161,11 +183,14 @@
if ($("#SavedId2").prop("checked")) {
$("#SavedId").val($.trim($("#user_UserId").val()));
}
setv("logincnt", login_cnt)
$("#fmlogin").submit();
}
}
var _focus;
</script>
</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>

View File

@ -33,7 +33,7 @@
<table class="table table-striped b-t b-light">
<thead>
<tr>
<th>구분</th>
<th>구분 '@(ViewBag.SSPWC)'</th>
<th>상위분류</th>
<th>유형</th>
<th>강좌명(반)</th>
@ -74,6 +74,13 @@
}
@section scripts{
<script>
$(document).ready(function () {
if ('@(ViewBag.SSPWC)' == 'True') {
showramemainlayer('/user/pwchange');
$("#mainlayerframe").css("width", "520px");
$("#mainlayerframe").css("height", "520px");
}
});
$("#stringval").on('keydown', function(e) {
if (e.keyCode == 13) {
capp("/acommon/cmget", {ismaster:0, cname: val("stringval"), iscurrent:1,orderby : "a.setime desc, t.tyear desc,t.tseq desc,a.cname,a.classno"}, "cbget");

View File

@ -61,6 +61,12 @@
@section scripts{
<script>
$(document).ready(function () {
if ('@(ViewBag.SSPWC)' == 'True') {
showramemainlayer('/user/pwchange');
$("#mainlayerframe").css("width", "520px");
$("#mainlayerframe").css("height", "520px");
}
$("#stringval").on("change", function () {
submit();
});

View File

@ -96,6 +96,13 @@
<script>
var terms = '@(string.Join(";", Model.Terms.Select(s=>string.Format("{0}:{1}:{2}", s.tmno, s.tyear, s.tseq))))';
$(document).ready(function () {
alert('2');
if ('@(Model.IsProf)' == 'True' && '@(ViewBag.SSPWC)' == 'True') {
showramemainlayer('/user/pwchange');
$("#mainlayerframe").css("width", "520px");
$("#mainlayerframe").css("height", "520px");
}
$("#stringval").on("change", function () {
$("#stringval2 option:gt(0)").remove();
var y = $(this).val();

View File

@ -159,7 +159,11 @@ else if (Model.viewname == "admin")
@section scripts{
<script>
$(document).ready(function () {
if ('@(ViewBag.issiteadmin)' == 'True' && '@(Model.viewname)' == 'user' && '@(ViewBag.SSPWC)' == 'True') {
showramemainlayer('/user/pwchange');
$("#mainlayerframe").css("width", "520px");
$("#mainlayerframe").css("height", "520px");
}
});
function reg(no) {
if (no < 0) {

View File

@ -1170,6 +1170,12 @@ function ispassword(v) {
//return passwordRules.test(v);
return $.trim(v).length > 5;
}
function ispass(v) {
var r = /^(?=.*[a-zA-Z])(?=.*[!@#$%^&~*+=-])(?=.*[0-9]).{8,20}$/;
return r.test(v);
}
function isemail(v) {
//이메일 검사
var regExp = /^[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/i;

View File

@ -242,6 +242,31 @@ namespace NP.Base.Controllers
{
return JsonOK(Dao.Save("councel.del",new Hashtable(){ {"bno",bno },{"uno",SUserInfo.UserNo },{"uip",GetUserIP() } }));
}
/// <summary>
/// pwchange partial 팝업 내 비밀번로 변경
/// </summary>
/// <param name="vm"></param>
/// <returns></returns>
[HttpPost]
public JsonResult PwChange(VMUser vm)
{
if (string.IsNullOrEmpty(vm.User.userpass) || vm.User.userpass.Trim() == "")
{
if (Dao.Save("users.resetuserpass", new Hashtable() { { "userno", vm.User.userno } }) > 0)
{
return JsonOK(1);
}
}
if (vm.User.userno > 0 && !string.IsNullOrEmpty(vm.User.userpass) && vm.User.userpass.Trim() != "")
{
if (Dao.Save("users.resetuserpass", new Hashtable() { { "userpass", NP.Base.Lib.KISA_SHA256.SHA256Hash(vm.User.userpass) }, { "userno", vm.User.userno } }) > 0)
{
return JsonOK(1);
}
}
return JsonOK(0);
}
}
}

View File

@ -76,6 +76,7 @@ namespace NP.Base
ViewBag.SSUserInfo = SUserInfo.UserInfo = sui[5];//관리자가 사용자backdoor진입시 "usertype.userno" 값으로 사용자 세션 제거 안하도록 사용하는 구분자 필드
ViewBag.SSLoginKey = SUserInfo.LoginKey = Convert.ToInt32(sui[6]); //로그인키
ViewBag.SSLoginTime = sui[7];
ViewBag.SSPWC = false;
//filterContext.Result = new RedirectResult("/Account/Error?_code=9991");
//최근서버접속시간이 30분 초과되었고 현재시간이 18시 이후라면 자동로그아웃 처리
//또는 최근서버접속시간이 8시간(480분) 초과되었다면 자동로그아웃 처리
@ -89,6 +90,7 @@ namespace NP.Base
{
tc = filterContext.HttpContext.Request.Cookies[SUIFTCROOM];
}
// 쿠키 시간 설정
if (tc == null || string.IsNullOrEmpty(tc.Value) || Convert.ToDateTime(DecString(tc.Value)).AddHours(8) < DateTime.Now)
{
CookieClear(null, true);
@ -137,9 +139,18 @@ namespace NP.Base
CookieClear(null, true);
filterContext.Result = new RedirectResult("/Account/Index?istimeout=true");
}
else if (data.FirstOrDefault().dtype == 0) // 중복 로그인 차단
{
CookieClear(null, true);
filterContext.Result = new RedirectResult("/Account/Index?istimeout=true");
}
else
{
ViewBag.MainMemoNotCount = data.First().intval;
if (data.FirstOrDefault().time != null && data.FirstOrDefault().time.AddMonths(3) < DateTime.Now)
{
ViewBag.SSPWC = true;
}
}
}

View File

@ -30,7 +30,7 @@
where a.userno=#userno# and (a.loginkey=#loginkey# or #IsDupCheck#=0)
</select>
<select id="common.check.admin" parameterClass="hashtable" resultClass="data">
select a.userno dtype, count(b.mno) intval
select a.userno dtype, a.udt time, count(b.mno) intval
from users a
left outer join memouser b on b.userno=a.userno and b.isread=0 and b.isdel=0
where a.userno=#userno# and (a.loginkey=#loginkey# or #IsDupCheck#=0)

View File

@ -43,10 +43,10 @@
</dynamic>
</sql>
<select id="users.adminlogin" parameterClass="hashtable" resultClass="users">
select userno,a.username,a.status,a.userpass, usertype,a.asno
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, 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>)
@ -760,6 +760,19 @@
where a.isdel=0
<isNotNull property="userno" prepend="and">b.userno=#userno#</isNotNull>
</select>
<update id="users.disable" parameterClass="hashtable">
update users set status = 9
<dynamic prepend="where">
<isNotNull property="userid" prepend="and">userid =#userid#</isNotNull>
<isNotNull property="userno" prepend="and">userno =#userno#</isNotNull>
</dynamic>
</update>
<update id="users.resetuserpass" parameterClass="hashtable">
update users set <isNotEmpty property="userpass">userpass=#userpass#,</isNotEmpty>udt=<include refid="sql.now"></include> where userno=#userno#
</update>
</statements>
</sqlMap>

View File

@ -25,6 +25,9 @@ namespace NP.Model
public Councel Councel{get; set;}
public IList<Councel> Councels{get; set;}
/// <summary>
/// 로그인 실패 카운트
/// </summary>
public int logincnt { get; set; }
}
}