본문 바로가기
프로그래밍

ASP.NET C# Web API Identity Oauth 인증 샘플 코드

by 도장깨기 2021. 3. 19.
728x90
반응형

안녕하세요. 

오늘 소개해드릴 내용은 ASP.NET에서 Web API Idnetity 를 구성하는 샘플코드를 작성해 볼건데요.

코드는 C#으로 작성해보겠습니다.

 

 

 

Ouath 형식으로 구성하였는데요.

Ouath란
웹, 앱 서비스에서 제한적으로 권한을 요청해 사용 할 수 있는 키를 발급해주는 것

간단히 API 접근을 위해 보안적인 측면으로 인증 방법을 설정한다고 생각하시면됩니다.

 

이제 구성을 시작해볼까요?

 

먼저, 

저는 Visual Studio 2017을 이용해서 작성하였는데요.

 

프로젝트 한개 생성 해주겠습니다.

 

웹 - ASP.NET 웹 응용 프로그램 (.NET Framework) - 확인

 

 

 

Web API 선택 - 인증변경 선택

 

 

개별 사용자 계정 선택 후 프로젝트 생성

 

먼저, 시작에 앞서 

 

큰 프로세스로는 사용자등록 - 해당 사용자 토큰발급 - 발급받은 토큰으로 인증받아 API호출 

의 과정이 됩니다.

 

먼저 web.config 에서 ConnectionStrings의 DefaultConnection을 설정해주시면되는데

API에서 사용할 DB를 설정해주시면됩니다.

 

Azure AppService를 사용하시는 경우에는 Azure SqlDatabase 주소 넣으시면되겠네요!

저 같은 경우 테스트이니 그냥 로컬 주소로 사용하겠습니다.

 

 

인증을 위해 발급할 토큰의 유효기간을 설정하시고 싶다면

 

APP_Start 폴더의 Startup Auth.cs에서

 

AccessTokenExpireTimeSpan 설정으로 발급할 토큰의 유효기간을 설정할 수 있습니다.

 

AccountController에서 RoutePrefix를 확인합니다. 

필요에 따라 변경하시면되고

 

 

Api 경로도 확인합니다. 

상황에 맞게 변경하셔도 됩니다.

일반적으로 api주소/컨트롤러이름/메소드이름 형태가 됩니다.

ex) api/Account/Register 

 

 

토큰 발급을 위해 사용자 등록을 할때 필요한 모델은

AccountBindingModels.cs 에서 확인할 수 있습니다.

저희는 사용자를 등록할것이기 때문에 RegisterBindingModel를 확인하시면되는데요.

메일 주소, 암호, 암호재확인 3개로 등록이 가능하네요~

 

 

먼저, TokenModel 발급을 위한 TokenModel을 생성해줍니다. 

 

using System;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace WebApplication2.Models
{
	public class TokenModel
	{
		[JsonProperty("access_token")]
		public string AccessToken { get; set; }

		[JsonProperty("token_type")]
		public string TokenType { get; set; }

		[JsonProperty("expires_in")]
		public int ExpiresIn { get; set; }

		[JsonProperty("userName")]
		public string Username { get; set; }

		[JsonProperty(".issued")]
		public DateTime Issued { get; set; }

		[JsonProperty(".expires")]
		public DateTime Expires { get; set; }
	}
}

Models 폴더에 TokenModel.cs 생성하고

이후에 토큰발급받을때 해당 모델에 맞게 토큰을 발급받아 볼게요.

 

 

이제 전체적인 부분을 컨트롤하기 위한 BaseController를 하나 생성해줄건데요.

 

저 같은 경우는 Common 폴더를 생성 후에 

 

using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;

namespace WebApplication2.Common
{
	public class BaseController : Controller
	{
		/// <summary>
		/// Call Api Get
		/// </summary>
		/// <param name="apiEndPoint"></param>
		/// <param name="model"></param>
		/// <returns></returns>
		public static async Task<HttpResponseMessage> CallApiTask(string apiEndPoint, Dictionary<string, string> model = null)
		{
			try
			{
				using (var client = new HttpClient())
				{
					client.BaseAddress = new Uri("http://localhost:61445");     // config 에 Uri를 관리해야 함.
					client.DefaultRequestHeaders.Accept.Clear();
					client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
					return await client.PostAsync(apiEndPoint, model != null ? new FormUrlEncodedContent(model) : null);
				}
			}
			catch (Exception)
			{
				throw new ArgumentException("RegisterException");
			}
		}
		/// <summary>
		/// Get ResponseErrors Error Message
		/// </summary>
		public class ResponseErrors
		{
			public string Message { get; set; }
			public Dictionary<string, string[]> ModelState { get; set; }
		}
		/// <summary>
		/// Return View JSON String Object Datas
		/// </summary>
		/// <param name="data"></param>
		/// <returns></returns>
		public ContentResult JsonNet(object data)
		{
			var jsonSerializerSettings = new JsonSerializerSettings
			{
				ContractResolver = new CamelCasePropertyNamesContractResolver(),
				NullValueHandling = NullValueHandling.Ignore
			};
			var json = JsonConvert.SerializeObject(data, Formatting.Indented, jsonSerializerSettings);
			return Content(json, "application/json");
		}
	}

}

위와 같이 생성해 주었는데요.

여기서 설정해줘야할 부분이 client.BaseAddress 부분인데요.

 

자신이 사용할 api 주소를 설정해 주셔야해요.

저 같은 경우 로컬에서 테스트 중이기 때문에 로컬 주소를 넣었어요.

 

BaseController를 생성하였으면 Api 호출 결과 값을 저희가 설정한 code나 Message로 받기위해 

ReturnValue를 설정해 줄거에요.

 

동일하게 Common 폴더에

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace WebApplication2.Common
{
	public class ReturnValue : BaseController
	{
		/// <summary>
		/// Code, Message 값을 리턴 값으로 전달
		/// </summary>
		/// <param name="code"></param>
		/// <param name="message"></param>
		/// <returns></returns>
		public ContentResult RetVal(int code, string message)
		{
			return JsonNet(new { Code = code, Message = message });
		}

		/// <summary>
		/// Exception 발생 시 최종 클라이언트에게 리턴하는 Exception 메시지
		/// </summary>
		/// <param name="ex"></param>
		/// <returns></returns>
		public ContentResult RetValByException(Exception ex)
		{
			return JsonNet(new
			{
				Code = -9999,
				Message = ex.InnerException != null ? string.Format("{0} {1} {2}", ex.Message, ex.InnerException.Message, ex.InnerException.InnerException.Message) : string.Format("{0}", ex.Message)
			});
		}

		/// <summary>
		/// 에러 메시지가 ModelState에 존재하는 경우 배열에 존재하는 에러메시지를 합친다.
		/// </summary>
		/// <param name="errors"></param>
		/// <returns></returns>
		public string GetErrorMessage(ResponseErrors errors)
		{
			try
			{
				if (errors == null || errors.Message == null)
					return "No Error Message";

				string errorMessage = errors.Message;
				foreach (var d in errors.ModelState)
				{
					foreach (var value in d.Value)
					{
						errorMessage = errorMessage + " " + value;
					}
				}

				return errorMessage;
			}
			catch (Exception ex)
			{
				throw ex;
			}
		}
	}
}

ReturnValue.cs를 생성해줍니다.

 

저 같은 경우 테스트로 진행했기 때문에 따로 메세지나 코드를 저장할 모델을 두지않았는데요.

다른 API를 추가하게 되면 그만큼 Return해줄 api호출 결과가 많으니 

하드코딩 하지마시고 

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WebApplication2.Common
{
	public class ResultCode
	{
		public const int SUCCESS = 0;
		public const int FAIL = -9999;
		public const int VAILDATION = -1;
	}

	public class ResultMessage
	{
		public const string SUCCESS = "SUCCESS";
		public const string FAIL = "FAIL";
		public const string SUCCESS_ACCESS_TOKEN = "정상적으로 AccessToken 발급이 완료 되었습니다.";
	}
}

 

이런식으로 따로 ResultCode와 ResultMessage를 저장해서 사용하시면 편리합니다.

 

마지막으로 토큰발급을 위한 

AccessController를 생성해볼건데요.

 

Controllers 폴더에서 추가 - 컨트롤러를 선택해 줍니다.

 

MVC5 컨트롤러 - 비어있음 선택

 

 

AccessController 입력 후 추가

 

using WebApplication2.Common;
using WebApplication2.Models;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;

namespace WebApplication2.Controllers
{
	public class AccessController : BaseController
	{
		/// <summary>
		/// 리턴 값
		/// </summary>
		ReturnValue _rv;
		public AccessController()
		{
			_rv = new ReturnValue();
		}
		/// <summary>
		/// Token Api ID Register
		/// </summary>
		/// <param name="email"></param>
		/// <param name="password"></param>
		/// <param name="passwordConfirm"></param>
		/// <returns></returns>
		[HttpPost]
		public async Task<ActionResult> Register(string email, string password, string confirmPassword)
		{
			try
			{
				ContentResult result = new ContentResult();
				var registerModel = new Dictionary<string, string>
						   {
								  {"Email", email},
								  {"Password", password},
								  {"ConfirmPassword", confirmPassword}
						   };
				var response = await CallApiTask("api/Account/Register", registerModel);
				if (response.IsSuccessStatusCode)
				{
					result = _rv.RetVal(0, "SUCESS");
				}
				else
				{
					var errors = await response.Content.ReadAsAsync<ResponseErrors>();
					string errorMessage = _rv.GetErrorMessage(errors);
					result = _rv.RetVal(-9999, errorMessage);
				}
				return result;
			}
			catch (Exception ex)
			{
				return _rv.RetValByException(ex);
			}
		}
		/// <summary>
		/// Get AccessToken
		/// </summary>
		/// <param name="email"></param>
		/// <param name="password"></param>
		/// <returns></returns>
		[HttpPost]
		public async Task<ActionResult> GetAccessToken(string email, string password)
		{
			try
			{
				ContentResult result = new ContentResult();
				var tokenModel = new Dictionary<string, string>
						   {
								  {"grant_type", "password"},
								  {"username", email},
								  {"password", password},
						   };
				var response = await CallApiTask("/Token", tokenModel);
				if (response.IsSuccessStatusCode)
				{
					var tokenData = response.Content.ReadAsAsync<TokenModel>(new[] { new JsonMediaTypeFormatter() }).Result;
					return JsonNet(new
					{
						Code = 0,
						Message ="토큰이 발급되었습니다",
						TokenData = tokenData
					});
				}
				else
				{
					var errors = await response.Content.ReadAsStringAsync();
					Errors errorMessage = JsonConvert.DeserializeObject<Errors>(errors);
					result = _rv.RetVal(-9999, string.Format("{0} {1}", errorMessage.error, errorMessage.error_description));
				}
				return result;
			}
			catch (Exception ex)
			{
				return _rv.RetValByException(ex);
			}
		}
		/// <summary>
		/// Access Token 요청 후 실패할 경우 리턴되는 에러 메시지
		/// </summary>
		private class Errors
		{
			public string error { get; set; }
			public string error_description { get; set; }
		}
	}
}

위와같이 추가해줍니다.

 

자 이제 사용자등록과 토큰발급을 위한 소스코드 구성은 완료가 됐으니

Postman을 통해 API를 호출하여 토큰을 발급받아 볼텐데요.

 

https://www.postman.com/downloads/

 

Download Postman | Try Postman for Free

Try Postman for free! Join 13 million developers who rely on Postman, the collaboration platform for API development. Create better APIs—faster.

www.postman.com

 

없으신분들은 위에서 다운로드 받으시면되요.

 

사용자 등록 API 주소(ex. api주소/Access/Register)를 입력하고 Body에 아까전에 Register 할때 필요했던 이메일, 패스워드, 패스워드 확인을 입력해주고 Send

 

 

사용자 등록 완료

 

이번엔 토큰을 발급 받아볼텐데요

 

 

토큰발급을 위해 생성했던  Access/GetAccessToken 경로

위에서 등록한 사용자의 email과 Password를 통해 호출 합니다.

 

 

토큰 생성 완료

 

 

이제 해당 토큰 인증을 통해 

생성한 API 접근하는 방법을 간략하게 알려드릴텐데요.

 

 

먼저 생성할 API를 Controller 폴더에 생성한 한다음 

(저 같은경우 회원등록api 작성을 위해 UserController라는 컨트롤러를 생성해주었습니다.) 

 

클래스 위에 [CustomAuthorize]를 선언한후에 

 

제가 호출할 Api인 RegisterUserInfo 매소드를 작성 후 에 [HttpPost]를 

선언해 줍니다.

 

그 후에 해당 api 호출할때 Header에 Authorization을 

발급받은 토큰에 Bearer를 앞에 붙여서 설정한 후에 호출

 

이런식으로 인증받아 보안설정이 가능합니다.

 

 

저도 아직 서툰 개발자이기때문에 잘못된 부분이나 설명이 부족한 부분이 있을 수 있습니다.

질문이 있으신분이나 잘못된부분 지적해주시면 더 공부해서 꼭 답변 드리도록 하겠습니다.

감사합니다.

728x90
반응형

댓글