ASP.NET MVC를 이용한 위챗 공식 계정 자동 로그인 및 원본 페이지 리디렉션

ASP.NET MVC 프레임워크를 사용하여 위챗 공식 계정(Official Account)에서 자동 로그인 후 원래 접속하려던 페이지로 되돌아가는 방법을 구현합니다. 이 방식은 사용자가 로그인하지 않은 상태에서 특정 페이지에 접근하려 할 때, 로그인 과정을 거쳐 원래 요청했던 페이지로 리디렉션하는 흐름을 따릅니다.

1. 전역 필터 설정

애플리케이션 시작 시점에 전역 필터를 등록하여 모든 요청을 가로챕니다. 사용자가 로그인되어 있지 않은 경우, 로그인 페이지로 리디렉션하며 이때 원래 요청했던 URL을 쿼리 스트링으로 함께 전달합니다.


protected void Application_Start()
{
    // Global.asax의 Application_Start 메서드에서 필터를 등록합니다.
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
}

public static class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new ExceptionFilter()); // 예외 처리 필터
        filters.Add(new AuthenticationFilterAttribute()); // 인증 필터
    }
}
    

인증 필터 구현 (AuthenticationFilterAttribute)

이 필터는 컨트롤러 액션 실행 전에 호출됩니다. 현재 요청이 로그인 관련 컨트롤러가 아니고, 세션에 사용자 정보가 없으면 사용자를 로그인 페이지로 리디렉션합니다. 이때 현재 URL을 oldurl 파라미터로 전달합니다.


using System.Web;
using System.Web.Mvc;
using System.Linq; // Array.IndexOf 사용을 위해 추가

public class AuthenticationFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var controllerType = filterContext.Controller.GetType();
        var controllerName = controllerType.Name.Replace("Controller", "").ToLower();

        // 로그인 컨트롤러는 검증에서 제외합니다.
        if (controllerName != "login")
        {
            // 세션에 사용자 정보가 없는 경우
            if (HttpContext.Current.Session["Account"] == null)
            {
                // 현재 요청 URL을 가져옵니다.
                string currentUrl = HttpContext.Current.Request.Url.ToString();

                // 로그인 페이지로 리디렉션하며, 원래 URL을 쿼리 스트링으로 전달합니다.
                filterContext.HttpContext.Response.Redirect($"/Login/Index?oldurl={currentUrl}");
            }
        }
    }
}
    

2. 로그인 페이지 (LoginController)

로그인 페이지에서는 다음과 같은 JavaScript 로직을 수행합니다.

  • URL에서 oldurl 파라미터를 추출합니다.
  • 현재 브라우저가 위챗 내부 브라우저인지 판별합니다.
  • 위챗 브라우저인 경우, WxWebLogin 액션으로 리디렉션하며 oldurl을 함께 전달합니다.

$(function () {
    // 이전 페이지 URL 가져오기
    var originalUrl = getQueryParameter("oldurl");

    // 자동 로그인 기능이 활성화된 경우 (예: _auto 변수로 제어)
    if (typeof _auto === 'undefined' || _auto !== 'false') {
        if (isWeixinBrowser() === 1) {
            // 레이어 UI를 사용하여 로딩 메시지 표시 (선택 사항)
            layui.use(['layer'], function () {
                var $ = layui.$;
                layer.msg('로그인 중...', {
                    icon: 16,
                    shade: 0.01
                });
            });
            // 위챗 브라우저에서 자동 로그인 처리를 위해 WxWebLogin 액션으로 이동
            window.location.href = "/Login/WxWebLogin?oldurl=" + originalUrl;
        }
    }
});

// URL 쿼리 스트링 파라미터 값을 가져오는 함수
function getQueryParameter(paramName) {
    const urlParams = new URLSearchParams(window.location.search);
    return urlParams.get(paramName);
}

// 브라우저 종류 판별 함수 (1: 위챗, 2: 기타)
function isWeixinBrowser() {
    const ua = navigator.userAgent.toLowerCase();
    if (ua.indexOf('micromessenger') !== -1) {
        return 1; // 위챗 브라우저
    } else {
        return 2; // 기타 브라우저
    }
}
    

3. 위챗 공식 계정 연동 로그인

LoginControllerWxWebLoginWxWebLoginHandle 액션은 위챗 공식 계정 인증 과정을 처리합니다.

WxWebLogin 액션

이 메서드는 사용자를 위챗 OAuth 2.0 인증 URL로 리디렉션시킵니다. 이때, 사용자 정보 획득 후 돌아올 콜백 URL(WxWebLoginHandle)과 원래 요청했던 페이지 URL(oldurl)을 포함시킵니다.


using System.Configuration; // ConfigurationManager 사용을 위해 추가
using System.Web;
using System.Web.Mvc;

public class LoginController : Controller
{
    // ... (이전 코드)

    public ActionResult WxWebLogin()
    {
        // 설정 파일에서 도메인 이름 가져오기
        string domainName = ConfigurationManager.AppSettings["DomainName"] ?? Request.Url.Host; // 기본값으로 현재 호스트 사용
        string returnUrl = Request.QueryString["oldurl"];

        // 위챗 API 호출을 위한 리디렉션 URI 생성
        string redirectUri = $"http://{domainName}/Login/WxWebLoginHandle?oldurl={HttpUtility.UrlEncode(returnUrl)}";
        string wechatAuthUrl = $"https://open.weixin.qq.com/connect/oauth2/authorize?appid={YourWechatAppId}&redirect_uri={HttpUtility.UrlEncode(redirectUri)}&response_type=code&scope=snsapi_userinfo&state=snsapi_userinfo_ok#wechat_redirect";

        // 위챗 인증 URL로 리디렉션
        return Redirect(wechatAuthUrl);
    }

    // ... (WxWebLoginHandle 메서드)
}
    

WxWebLoginHandle 액션

위챗에서 인증 코드를 받아 사용자 정보를 처리하고, 최종적으로 원래 페이지로 리디렉션하거나 내부 페이지로 이동시키는 역할을 합니다.


using System;
using System.IO;
using System.Net;
using System.Text;
using System.Web;
using System.Web.Mvc;
using Newtonsoft.Json; // JSON 처리를 위한 라이브러리 (예: Newtonsoft.Json)

// DTO (Data Transfer Object) 예시
public class WxUserInfo
{
    public string openid { get; set; }
    public string nickname { get; set; }
    public int sex { get; set; }
    public string language { get; set; }
    public string city { get; set; }
    public string province { get; set; }
    public string country { get; set; }
    public string headimgurl { get; set; }
    public string unionid { get; set; }
    public int errcode { get; set; } // 에러 코드
    public string errmsg { get; set; } // 에러 메시지
}

public class AccountModel
{
    public Guid Id { get; set; }
    public string Photo { get; set; }
    public string UserName { get; set; }
    public string LoginSource { get; set; }
    public string OpenId { get; set; }
    public int UserType { get; set; }
}

public class LoginController : Controller
{
    // ... (이전 코드)

    public ActionResult WxWebLoginHandle()
    {
        try
        {
            // state와 code 파라미터 확인
            if (Request.QueryString["state"] != null && Request.QueryString["code"] != null)
            {
                string code = Request.QueryString["code"];
                string originalUrl = Request.QueryString["oldurl"];

                // 1. Access Token 및 OpenID 얻기
                string tokenUrl = $"https://api.weixin.qq.com/sns/oauth2/access_token?appid={YourWechatAppId}&secret={YourWechatAppSecret}&code={code}&grant_type=authorization_code";
                string tokenResponse = MakeHttpRequest(tokenUrl);
                var tokenData = JsonConvert.DeserializeObject<dynamic>(tokenResponse);

                if (tokenData.access_token != null)
                {
                    string accessToken = tokenData.access_token;
                    string openId = tokenData.openid;

                    // 2. 사용자 정보 얻기
                    string userInfoUrl = $"https://api.weixin.qq.com/sns/userinfo?access_token={accessToken}&openid={openId}";
                    string userInfoResponse = MakeHttpRequest(userInfoUrl);
                    WxUserInfo wxUser = JsonConvert.DeserializeObject<WxUserInfo>(userInfoResponse);

                    if (wxUser != null && string.IsNullOrEmpty(wxUser.errcode.ToString())) // 에러 코드 없을 때
                    {
                        // 3. 사용자 정보 처리 (DB 저장 또는 업데이트)
                        // 예시: PhoneLoginBLL 클래스 사용
                        var loginBll = new YourApp.BLL.PhoneLoginBLL(); // 실제 BLL 클래스로 변경
                        int userId = loginBll.ProcessWxUser(wxUser); // 사용자 생성/업데이트 및 ID 반환

                        if (userId > 0)
                        {
                            // 4. 자동 로그인 처리
                            var user = loginBll.GetUserById(userId); // 사용자 정보 조회
                            var account = new AccountModel
                            {
                                Id = user.Guid, // 가정
                                Photo = wxUser.headimgurl,
                                UserName = wxUser.nickname, // 또는 DB의 userName
                                LoginSource = "gzh-auto",
                                OpenId = wxUser.openid,
                                UserType = user.UserType // 가정
                            };
                            Session["Account"] = account;
                            // Session ID 기반 로그인 관리 (필요 시)
                            LoginUserManage.Add(Session.SessionID, account.Id);

                            // 5. 원래 페이지로 리디렉션
                            if (!string.IsNullOrEmpty(originalUrl))
                            {
                                return Redirect(originalUrl);
                            }
                            else
                            {
                                // 로그인 후 기본 리디렉션 경로 설정
                                if (account.UserType == 1)
                                {
                                    return Redirect("/home/index"); // 일반 사용자 홈
                                }
                                else
                                {
                                    return Redirect("/some/other/default/page"); // 다른 유형 사용자 홈
                                }
                            }
                        }
                        else
                        {
                            // 사용자 처리 실패 시 수동 로그인 유도
                            return Content("<script>alert('사용자 정보 처리 실패. 수동 로그인으로 전환합니다.'); setTimeout(function(){window.location.href='/Login/index?auto=false';}, 300);</script>");
                        }
                    }
                    else
                    {
                        // 위챗 API 오류 처리
                        return Content($"<script>alert('위챗 API 오류: {wxUser.errmsg}'); setTimeout(function(){window.location.href='/Login/index?auto=false';}, 300);</script>");
                    }
                }
                else
                {
                    // Access Token 획득 실패
                    return Content($"<script>alert('인증 토큰 획득 실패. 에러: {tokenData.errmsg}'); setTimeout(function(){window.location.href='/Login/index?auto=false';}, 300);</script>");
                }
            }
            else
            {
                 // state 또는 code 누락
                 return Content("<script>alert('인증 과정에 오류가 발생했습니다. 다시 시도해주세요.'); window.location.href='/Login/index';</script>");
            }
        }
        catch (Exception ex)
        {
            // 전역 예외 처리 또는 로깅
            // Logger.LogError(ex); // 실제 로깅 구현
            return Content($"<script>alert('처리 중 오류가 발생했습니다: {ex.Message}'); window.location.href='/Login/index';</script>");
        }
        return Content(""); // 기본 반환값
    }

    // HTTP 요청을 보내고 응답을 문자열로 반환하는 헬퍼 메서드
    private string MakeHttpRequest(string url)
    {
        try
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = "GET";
            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            using (Stream stream = response.GetResponseStream())
            using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
            {
                return reader.ReadToEnd();
            }
        }
        catch (Exception ex)
        {
            // 예외 로깅
            throw new Exception($"HTTP 요청 실패: {ex.Message}", ex);
        }
    }

    // 임시 클래스: 실제 애플리케이션의 사용자 관리 로직에 맞게 구현해야 합니다.
    public static class LoginUserManage
    {
        public static void Add(string sessionId, Guid userId) { /* 구현 */ }
    }

    // 실제 애플리케이션에 맞게 YourApp.BLL.PhoneLoginBLL 클래스를 구현해야 합니다.
    // 이 예제에서는 WxUserInfo 객체를 받아 처리하고 사용자 ID를 반환한다고 가정합니다.
    namespace YourApp.BLL {
        public class PhoneLoginBLL {
            public int ProcessWxUser(WxUserInfo wxUser) { return 1; /* 실제 로직 */ }
            public dynamic GetUserById(int userId) { return new { Guid = Guid.NewGuid(), UserType = 1 }; /* 실제 로직 */ }
        }
    }

    // 위챗 App ID 및 App Secret을 설정하세요.
    public string YourWechatAppId => "YOUR_WECHAT_APPID";
    public string YourWechatAppSecret => "YOUR_WECHAT_APPSECRET";
}
    

태그: ASP.NET MVC WeChat Official Account OAuth 2.0 Automatic Login Redirect

9월 5일 09:02에 게시됨