آموزش استفاده از Guardian در الکسیر

با درود خدمت شما عزیزان چندی پیش تصمیم گرفتم از کتابخانه Guardian برای ساخت توکن و همینطور ورود به سایت در الکسیر استفاده کنم به همین ترتیب پستی رو در انجمن زدم که با کمک دوستان عزیزی مثل @toomaj , @lxsameer و مخصوصا دوست محترم @samdvr به سرانجام رسید .

لینک پرسش و پاسخ های مربوط به این کتابخانه

کار Guardian چیست :

یک برنامه نویس الکسیر کاری به نام ueberauth (ممکن اسم شرکت یا گروهشون باشه ) پلاگین های زیادی را برای زبان برنامه نویسی الکسیر و همینطور فریم ورک phoenix تا به حال زده اند که یکی از این دسته پلاگین ها Guardian می باشد که وضیفه ساخت و مدیریت توکن و کدگذاری آن را برعهده می گیرد. که برای اطلاعات بیشتر می توانید به لینک های زیر مراجعه کنید


https://hexdocs.pm/guardian

اگر می خواهید ببنید توکن جی سان چیست از لینک زیر استفاده کنید :

نکته : این آموزش در حال به روز رسانی می باشد و بعد از تکمیل سورس نمونه حتما این پست دوباره بازنگری می گردد

مراحل تنظیم Guardian در elixir

مرحله اول :

قبل از هرچیزی یک پروژه فونیکس بسازید برای آموزش ساخت پروژه جدید phoenix کلیک کنید . بعد از ساخت می توانید به فایل mix.exs مراجعه کنید . و دو کتابخانه زیر را به آن اضافه نمایید .

 defp deps do
    [
  ....
      {:guardian, "~> 1.0"},
      {:jose, "~> 1.8"},
  ....
    ]
  end

بعد از اضافه کردن در مسیر جاری پروژه لطفا قرار بگیرید و دستور mix deps.get‍‍ را اجرا کنید تا از منبع هر پروژه نسخه مورد نظر دریافت شود . لازم به ذکر هست در زمان ساخت پروژه ما از نسخه های بالا استفاده کردیم شما می توانید با مراجعه به سایت hex.pm آخرین نسخه آن را دریافت کنید .

شاید برای شما سوال شده که پلاگین jose برای چه کاری اضافه شده است . لازم به ذکر هست این کتابخانه برای ساخت و ایجاد کلید خصوصی می باشد که می توانید برای دیدن داکیومنت ساخت کلید خصوصی و همینطور نمونه کلید های خصوصی ساخته شده کلیک کنید

مرحله دوم :

در مرحله دوم ما به سمت این خواهیم رفت که کلید خصوصی ساخته شده را در فایل config.exs قرار بدهیم پس لازمه کار ما قبل از هرچیزی این است که بیاییم کلید مذکور را بسازیم

برای این کار شما باید وارد ترمینال خود شده (در مسیر جاری پروژه phoenix ) و بعد بیایید کامند زیر را بزنید

iex -S mix phx.server

و بعد از آن لطفا بیایید و کامند زیر را برای ساخت کلید خصوصی ایجاد کنید :

JOSE.JWS.generate_key(%{"alg" => "ES512"}) |> JOSE.JWK.to_map |> elem(1)

لازم به ذکر هست شما می توانید ES512 به رمز های دیگری نیز تغییر دهید مثلا بنده از نسخه ۲۵۶ استفاده کردم .

خوب حالا شما باید بیایید این کلید خصوصی را در فایل کانفیگ به صورت زیر قرار بدهید

config :api_trangell, ApiTrangell.Guardian,
  issuer: "api_trangell",
  allowed_algos: ["ES512"],
  secret_key: %{
    "alg" => "ES512",
    "crv" => "P-521",
    "d" => "5lzq86dlBivEqOmJj_6x_xW5lxY5bCAV1cxJ1Lt8fsshKAkHOz3j0iOF_ruG2PYXF69kDnY3HvSiY2tbog2UjBc",
    "kty" => "EC",
    "use" => "sig",
    "x" => "AIYZCBlSZ4jGvRHJnhWU_s85Uqu6Fl8F7TMMD1WjcibHIGIHfPjEwyfIdmAjvgMwKalqjhKIgqQEejvaPtxHwLMB",
    "y" => "AOogDP-U1x4VcgL0xUr7TEXc9FmQv3wvJ_goDW6ZJ_1PBebpOVltZP-3ydG1nAr-ddqoq9AFUQP9UhY3wQhvKFD_"
  }
  • توجه فرمایید این یک نمونه از رمز می باشد شما برای پروژه خودتان باید از رمزی که در بالا ایجاد کردید استفاده کنید . هرچه کلید کوچک تر باشه موارد موجود در این رمز نیز کوچک تر است .
  • شما باید بجای api_trangell اسم پروژه خود را قرار بدهید و همینطور بجای ApiTrangell

# مرحله سوم

حال زمان این هست که یک فایل با نام guardian.ex به پروژه خود اضافه کنید .

defmodule ApiTrangell.Guardian do
  use Guardian, otp_app: :api_trangell

  def subject_for_token(resource, _claims) do
    # You can use any value for the subject of your token but
    # it should be useful in retrieving the resource later, see
    # how it being used on `resource_from_claims/1` function.
    # A unique `id` is a good subject, a non-unique email address
    # is a poor subject.
    sub = to_string(resource.id)
    {:ok, sub}
  end
  def subject_for_token(_, _) do
    {:error, :reason_for_error}
  end

  def resource_from_claims(claims) do
    # Here we'll look up our resource from the claims, the subject can be
    # found in the `"sub"` key. In `above subject_for_token/2` we returned
    # the resource id so here we'll rely on that to look it up.
    id = claims["sub"]
    resource = ApiTrangell.get_resource_by_id(id)
    {:ok,  resource}
  end
  def resource_from_claims(_claims) do
    {:error, :reason_for_error}
  end
end

این فایل شما را با پلاگ گاردین اتصال می دهد و هم چنین در نظر داشته باشید که شما باید اسم پروژه خودتان را جایگزین اسم پروژه نمونه کنید ( ApiTrangell را تغییر دهید )

مرحله چهارم

در این مرحله شما نیاز دارید به فایل api_trangell.ex بروید و یک فانکشن به آن اضافه کنید که به شرح زیر می باشد و همینطور توجه کنید شما باید به اسم فایل خودتان رفته و اسم بالا یک نمونه می باشد

def get_resource_by_id(id) do
    %{id: id, user: "shahryar"}
  end

مرحله پنجم

حال زمان این هست که یک فایلی را بسازید تا ارور های جاری را فراخوانی کند که من اسم این فایل را auth_error_handler.ex قرار دادم

کد های مربوط به این فایل به شرح زیر می باشد :

defmodule ApiTrangell.AuthErrorHandler do
  import Plug.Conn

  def auth_error(conn, {type, _reason}, _opts) do
    body = Poison.encode!(%{message: to_string(type)})
    send_resp(conn, 401, body)
  end
end

مرحله ششم

در این مرحله که دوباره به آن سر خواهیم زد در حقیقت ما می آییم به روتر این امکان را می دهید قبل از ارسال به فانکشن و فایل مورد نظر یک بررسی روی برخی از موارد امنیتی داشته باشد از جمله هدر درخواست و جلسه کاربری درست شده و …

پس یک فایل با اسم auth_pipeline.ex می سازیم و بعد از آن به سمت این خواهیم رفت تا کد های زیر را در آن قرار بدهیم

defmodule ApiTrangell.AuthPipeline do
	@claims %{typ: "access"}

  use Guardian.Plug.Pipeline, otp_app: :api_trangell,
                              module: ApiTrangell.Guardian,
                              error_handler: ApiTrangell.AuthErrorHandler

  plug Guardian.Plug.VerifySession, claims: @claims
  plug Guardian.Plug.VerifyHeader, claims: @claims, realm: "Bearer"
  plug Guardian.Plug.EnsureAuthenticated
  plug Guardian.Plug.LoadResource, ensure: true


  # json VerifyHeader
  # plug Guardian.Plug.VerifyHeader, realm: "Bearer"
  # plug Guardian.Plug.LoadResource, ensure: true, allow_blank: true
end

اگر به کد های بالا توجه کرده باشید من اومدم یک سری از کد هارو که در حقیقت فراخوانی پلاگ هست را کامنت کردم .تا متوجه بشوید که ما نیاز به همه آن ها در هر جایی نداریم به صورت مثال سشن یا همان جلسه کاربری در اپلیکیشن موبایل به کار نمی رود بستگی به هدف شما دارد /

  • در بالای سایت ما یک متغییر گلوبال درست کردیم که در آن یک سری اطلاعات هست که در جلوتر آموزش به آن خواهیم رسید فقط برای شفاف سازی باید بگویم این بخش می تواند یک نشانه گر بر اساس نوع توکن باشد که به وسیله شما تولید می شود در حقیقت یک فیلتری بر ارسال درخواست و همینطور تفکیک آن می باشد .

مرحله هفتم

حال نوبت این هست که روتر و مسیر دهی را آماده کنیم تا به اصل موضوع یعنی کد و فانکشن های مربوطه برسیم پس به فایل روتر رفته و مورد زیر را اضافه کنید

defmodule ApiTrangellWeb.Router do
  use ApiTrangellWeb, :router

  pipeline :api do
    plug :accepts, ["json"]
  end

  pipeline :unauthorized do
    plug :fetch_session
  end

  pipeline :authorized do
    plug :fetch_session
    plug ApiTrangell.AuthPipeline
  end

  scope "/api/users", ApiTrangellWeb do
    pipe_through :api
    pipe_through :unauthorized
    post "/sign-in", PageController, :sign_in
    post "/verify-token", PageController, :verify_token
    post "/refresh-token", PageController, :refresh_token
  end

  scope "/api/users", ApiTrangellWeb do
    pipe_through :api
    pipe_through :authorized
    post "/sign-out", PageController, :sign_out
    post "/me", PageController, :show
  end
end

اگر توجه کرده باشید فایلی که قبلا ساختیم یعنی ApiTrangell.AuthPipeline را در اینجا اضافه کردیم . و دو نوع pipeline ایجاد شد که به شرح بالاست یکی با اسم unauthorized که نیازی نمی بیند تا هدر و … چک شود و دیگیری که فایل مذکر در آن اضافه شده است authorized می باشد .

مرحله هشتم

حالا نوبت این هست که به کنترلر مورد نظر خودتان در phoenix رفته و فانکشن های زیر را اضافه کنید لازم به ذکر هست این فانکشن ها به صورت خام درج شدند و شما می توانید به بانک اطلاعاتی خودتان وصل شوید و چک کنید و موارد دیگیری که در پهنای این آموزش نمی گنجد

defmodule Person do
	defstruct token: ""
end

defmodule ApiTrangellWeb.PageController do
	use ApiTrangellWeb, :controller

	def sign_in(conn, %{"password" => password,}) do
		user = %{id: "1", user: "shahryar"}

		case password do
			"2" ->
				{:ok, token, _claims} = ApiTrangell.Guardian.encode_and_sign(user, %{some: "claim", userid: 2, admin: 2}, token_type: "access",ttl: {99, :weeks})
				json conn, %Person{token: token}
			_ -> 
				conn |> send_resp(204, "")
				json conn, %{error: "you have an error"} 

		end 
	end

	def sign_in(conn, _params) do
		send_resp(conn, 401, Poison.encode!(%{error: "Incorrect password"}))
	end

	def sign_out(conn, _params) do
		conn
		|> ApiTrangell.Guardian.Plug.sign_out()
		|> send_resp(204, "")
	end

	def verify_token(conn, %{"token" => token}) do
		case  ApiTrangell.Guardian.decode_and_verify(token) do
			{:ok, claims} ->
				json conn, %Person{token: Map.get(claims, "exp")}
			{:error, _} ->
				conn
				|> put_status(403)
				|> json(%{ error: "khata"})
		end
	end

	def refresh_token(conn, %{"token" => token}) do
		case ApiTrangell.Guardian.refresh(token) do
			{:ok, {old_token, old_claims}, {new_token, new_claims}} ->
			  json conn,  %{
					old_token: old_token, 
					old_claims: old_claims,
					new_token: new_token, 
					new_claims: new_claims
				}

			{:error, %CaseClauseError{term: {:error, {:badmatch, false}}}} ->	
				conn		  
				|> put_status(404)
				|> json(%{ error: "errorrrrrr"})
		end
	end

	def current_user(conn) do
    	Guardian.Plug.current_resource(conn)
  	end
end

سعی کردم اسم هر فانکشن را به طوری نام گذاری کنم تا شما به سادگی از آن به درک مناسبی برسید و نیاز به توضیح هر فانکشن به صورت جدا نباشد

امید وارم این آموزش مفید واقع شود

باز هم از دوستانی که در اول پست هست تشکر می کنم بدون وجود این دوستان این قسمت خیلی زمان بر می شد و شاید هم به خوبی درست نمی گردید.

سورس کامل این پروژه :

ارسال درخواست :

برای دریافت توکن باید به لینک زیر درخواست POST ارسال کنید . نیاز به ارسال یک پرامتر دارید : password با ارزش استرینگ “2”

http://localhost:4000/api/users/sign-in

برای از بین بردن در سشن می تونید به لینک زیر درخواست بزنید . لازم به ذکر هست شما باید در هدر درخواست POST خودتان بیایید Authorization با ارزش Bearer TOKEN ارسال کنید . TOKEN رو باید با توکنی که در لینک بالا دریافت کردید تغییر بدهید

http://localhost:4000/api/users/sign-out

شما می توانید در لینک زیر توکن خودتان را بررسی کنید ببنید ورفای هست یا خیر ؟ برای این کار نیاز دارید شما یک درخواست POST بفرستید که پرامتر token داشته باشه و ارزشش نیز همان توکنی که دریافت کردید باشد

http://localhost:4000/api/users/refresh-token

پیوست

نحوه کار کرد jwt

سطوح دسترسی

گاردین سطوح دسترسی هم در امکانات خودش قرار داده به صورت مثال شما می آیید در پروژه خودتان یک فایل می سازید به شرح زیر

defmodule ApiTrangell.Auth.Token do

  use Guardian, otp_app: :api_trangell,
                         permissions: %{
                         default: [:public_profile, :user_about_me],
                         user_actions: %{
                           books: 0b1,
                           fitness: 0b100,
                           music: 0b1000,
                         }
                       }

  use Guardian.Permissions.Bitwise

  # snip

  def build_claims(claims, _resource, opts) do
    claims =
      claims
      |> encode_permissions_into_claims!(Keyword.get(opts, :permissions))
    {:ok, claims}
  end
end

خوب شما در این بخش اومدید سطح دسترسی به خصوصی معرفی کردید حالا نوبت این هست که بیایید در توکن دسترسی خودتان را قرار دهید

{:ok, token, claims} = ApiTrangell.Guardian.encode_and_sign(user, %{some: "claim", userid: 2, admin: 2, pem: %{default: [:public_profile], user_actions: [:books]}}, token_type: "access",ttl: {99, :weeks})

اگر توجه کرده باشید من گروه کاربری رو در public_profile و همینطور گفتم یوزر اکشن اون books باشه حالا می خواهیم این مورد رو ولید کنیم :

claims |> ApiTrangell.Auth.Token.decode_permissions_from_claims |> ApiTrangell.Auth.Token.all_permissions?(%{default: [:public_profile], user_actions: [:books]})

اگر در توکن هم books باشه به شما جواب true بر می گردوند و اگر نباشه false

به روز رسانی

نکته: در صورتی که در حال نصب این پلاگین روی پروژه الکسیر (غیر فونیکس) می باشید لطفا Plug را در فایل mix خود قرار دهید و اگر باز هم گاردین را شنایی نکرد یک بار deps را پاک کرده و بیلد را حذف نمایید و دوباره نصب نمایید.

6 Likes
1 Like

یاشا جوان یاشا
دمت گرم تشکر

1 Like

آموزش به روز رسانی شد با کمک دوستان محترم و همیشه همراه

2 Likes

پست اول به روز رسانی شد

2 Likes