OAuth2.0认证授权

认证授权过程

  • 服务提供方(provider),用户使用服务提供方来存储受保护的资源,如照片,视频,联系人列表
  • 用户,存放在服务提供方的受保护的资源的拥有者
  • 客户端,要访问服务提供方资源的第三方应用,通常是网站,如提供照片打印服务的网站。在认证过程之前,客户端要向服务提供者申请客户端标识使用OAuth进行认证和授权的过程如下所示:

用户想操作存放在服务提供方的资源。用户登录客户端向服务提供方请求一个临时令牌。服务提供方验证客户端的身份后,授予一个临时令牌。客户端获得临时令牌后,将用户引导至服务提供方的授权页面请求用户授权。在这个过程中将临时令牌和客户端的回调连接发送给服务提供方。用户在服务提供方的网页上输入用户名和密码,然后授权该客户端访问所请求的资源。授权成功后,服务提供方引导用户返回客户端的网页。客户端根据临时令牌从服务提供方那里获取访问令牌。服务提供方根据临时令牌和用户的授权情况授予客户端访问令牌。客户端使用获取的访问令牌访问存放在服务提供方上的受保护的资源。

目录结构

主目录basicauth

  1. —— main.go
  2. —— templates
  3. —— index.html
  4. —— user.html

代码示例

main.go

  1. package main
  2. // 任何OAuth2(甚至是纯golang/x/net/oauth2)包
  3. // 可以与iris一起使用,但在这个例子中我们将看到markbates' goth:
  4. // 获取包 go get github.com/markbates/goth/...
  5. // 这个OAuth2示例适用于会话,因此我们需要
  6. // 附加会话管理器
  7. // 可选:为了更安全的会话值,
  8. // 开发人员可以使用任何第三方包添加自定义cookie编码器/解码器。
  9. // 在这个例子中,我们将使用gorilla的securecookie:
  10. // 获取包 go get github.com/gorilla/securecookie
  11. // securecookie的示例可以在“sessions / securecookie”示例文件夹中找到。
  12. // 注意:
  13. // 整个示例由markbates/goth/example/main.go转换。
  14. // 它使用我自己的TWITTER应用程序进行测试,即使对于localhost也可以使用它。
  15. // 我猜其他一切都按预期工作,goth库社区报告了所有错误
  16. // 在我写这个例子的时候修好了,玩得开心!
  17. import (
  18. "errors"
  19. "os"
  20. "sort"
  21. "github.com/kataras/iris"
  22. "github.com/kataras/iris/sessions"
  23. "github.com/gorilla/securecookie" //可选,用于seesion的编码器/解码器
  24. "github.com/markbates/goth"
  25. "github.com/markbates/goth/providers/amazon"
  26. "github.com/markbates/goth/providers/auth0"
  27. "github.com/markbates/goth/providers/bitbucket"
  28. "github.com/markbates/goth/providers/box"
  29. "github.com/markbates/goth/providers/dailymotion"
  30. "github.com/markbates/goth/providers/deezer"
  31. "github.com/markbates/goth/providers/digitalocean"
  32. "github.com/markbates/goth/providers/discord"
  33. "github.com/markbates/goth/providers/dropbox"
  34. "github.com/markbates/goth/providers/facebook"
  35. "github.com/markbates/goth/providers/fitbit"
  36. "github.com/markbates/goth/providers/github"
  37. "github.com/markbates/goth/providers/gitlab"
  38. "github.com/markbates/goth/providers/gplus"
  39. "github.com/markbates/goth/providers/heroku"
  40. "github.com/markbates/goth/providers/instagram"
  41. "github.com/markbates/goth/providers/intercom"
  42. "github.com/markbates/goth/providers/lastfm"
  43. "github.com/markbates/goth/providers/linkedin"
  44. "github.com/markbates/goth/providers/meetup"
  45. "github.com/markbates/goth/providers/onedrive"
  46. "github.com/markbates/goth/providers/openidConnect"
  47. "github.com/markbates/goth/providers/paypal"
  48. "github.com/markbates/goth/providers/salesforce"
  49. "github.com/markbates/goth/providers/slack"
  50. "github.com/markbates/goth/providers/soundcloud"
  51. "github.com/markbates/goth/providers/spotify"
  52. "github.com/markbates/goth/providers/steam"
  53. "github.com/markbates/goth/providers/stripe"
  54. "github.com/markbates/goth/providers/twitch"
  55. "github.com/markbates/goth/providers/twitter"
  56. "github.com/markbates/goth/providers/uber"
  57. "github.com/markbates/goth/providers/wepay"
  58. "github.com/markbates/goth/providers/xero"
  59. "github.com/markbates/goth/providers/yahoo"
  60. "github.com/markbates/goth/providers/yammer"
  61. )
  62. var sessionsManager *sessions.Sessions
  63. func init() {
  64. //附加session管理器
  65. cookieName := "mycustomsessionid"
  66. // AES only supports key sizes of 16, 24 or 32 bytes.
  67. // You either need to provide exactly that amount or you derive the key from what you type in.
  68. // AES仅支持16,24或32字节的密钥大小。
  69. // 您需要准确提供该字节数,或者从您键入的内容中获取密钥。
  70. hashKey := []byte("the-big-and-secret-fash-key-here")
  71. blockKey := []byte("lot-secret-of-characters-big-too")
  72. secureCookie := securecookie.New(hashKey, blockKey)
  73. sessionsManager = sessions.New(sessions.Config{
  74. Cookie: cookieName,
  75. Encode: secureCookie.Encode,
  76. Decode: secureCookie.Decode,
  77. })
  78. }
  79. //下面是一些辅助函数
  80. // GetProviderName函数是用于获取提供者名称(授权应用名称)通过请求。
  81. // 默认情况下,将从URL查询字符串中提取此授权应用名称。
  82. // 如果您以不同的方式提供, 将自己的函数分配给返回提供者的变量用您的请求的名称。
  83. var GetProviderName = func(ctx iris.Context) (string, error) {
  84. //尝试从的url参数中获取provider
  85. if p := ctx.URLParam("provider"); p != "" {
  86. return p, nil
  87. }
  88. //尝试从url PATH参数“{provider}或:provider或{provider:string}或{provider:alphabetical}”获取它
  89. if p := ctx.Params().Get("provider"); p != "" {
  90. return p, nil
  91. }
  92. //尝试从上下文的每个请求存储中获取它
  93. if p := ctx.Values().GetString("provider"); p != "" {
  94. return p, nil
  95. }
  96. //如果没有找到,则返回一个带有相应错误的空字符串
  97. return "", errors.New("you must select a provider")
  98. }
  99. /*
  100. BeginAuthHandler是用于启动身份验证过程的便捷处理程序。
  101. 它希望能够从查询参数中获取提供程序的名称授权应用名称)
  102. 作为“provider”或“:provider”。
  103. BeginAuthHandler会将用户重定向到相应的身份验证端点对于请求的provider。
  104. 请参阅https://github.com/markbates/goth/examples/main.go以查看此操作。
  105. */
  106. func BeginAuthHandler(ctx iris.Context) {
  107. url, err := GetAuthURL(ctx)
  108. if err != nil {
  109. ctx.StatusCode(iris.StatusBadRequest)
  110. ctx.Writef("%v", err)
  111. return
  112. }
  113. ctx.Redirect(url, iris.StatusTemporaryRedirect)
  114. }
  115. /*
  116. GetAuthURL使用provided的请求启动身份验证过程。
  117. 它将返回一个应该用于向用户发送的URL。
  118. 它希望能够从查询参数中获取provider的名称
  119. 作为“provider”或“:provider”或来自“provider”键的上下文值。
  120. 我建议使用BeginAuthHandler而不是执行所有这些步骤,但那完全取决于你。
  121. */
  122. func GetAuthURL(ctx iris.Context) (string, error) {
  123. providerName, err := GetProviderName(ctx)
  124. if err != nil {
  125. return "", err
  126. }
  127. provider, err := goth.GetProvider(providerName)
  128. if err != nil {
  129. return "", err
  130. }
  131. sess, err := provider.BeginAuth(SetState(ctx))
  132. if err != nil {
  133. return "", err
  134. }
  135. url, err := sess.GetAuthURL()
  136. if err != nil {
  137. return "", err
  138. }
  139. session := sessionsManager.Start(ctx)
  140. session.Set(providerName, sess.Marshal())
  141. return url, nil
  142. }
  143. // SetState设置与给定请求关联的状态字符串。
  144. // 如果没有状态字符串与请求相关联,则会生成一个。
  145. // 此状态发送给provider,可以在提取期间检索回调
  146. var SetState = func(ctx iris.Context) string {
  147. state := ctx.URLParam("state")
  148. if len(state) > 0 {
  149. return state
  150. }
  151. return "state"
  152. }
  153. // GetState获取回调期间provider的返回的状态。
  154. // 这用于防止CSRF攻击,请参阅 http://tools.ietf.org/html/rfc6749#section-10.12
  155. var GetState = func(ctx iris.Context) string {
  156. return ctx.URLParam("state")
  157. }
  158. /*
  159. CompleteUserAuth在锡上做了它所说的。 它完成了身份验证处理并从provider处获取有关用户的所有基本信息。
  160. 它希望能够从查询参数中获取provider的名称作为“provider”或“:provider”。
  161. 请参阅https://github.com/markbates/goth/examples/main.go以查看此操作。
  162. */
  163. var CompleteUserAuth = func(ctx iris.Context) (goth.User, error) {
  164. providerName, err := GetProviderName(ctx)
  165. if err != nil {
  166. return goth.User{}, err
  167. }
  168. provider, err := goth.GetProvider(providerName)
  169. if err != nil {
  170. return goth.User{}, err
  171. }
  172. session := sessionsManager.Start(ctx)
  173. value := session.GetString(providerName)
  174. if value == "" {
  175. return goth.User{}, errors.New("session value for " + providerName + " not found")
  176. }
  177. sess, err := provider.UnmarshalSession(value)
  178. if err != nil {
  179. return goth.User{}, err
  180. }
  181. user, err := provider.FetchUser(sess)
  182. if err == nil {
  183. //可以找到现有session数据的用户
  184. return user, err
  185. }
  186. //获取新令牌并重试获取
  187. _, err = sess.Authorize(provider, ctx.Request().URL.Query())
  188. if err != nil {
  189. return goth.User{}, err
  190. }
  191. session.Set(providerName, sess.Marshal())
  192. return provider.FetchUser(sess)
  193. }
  194. // 注销使用户session
  195. func Logout(ctx iris.Context) error {
  196. providerName, err := GetProviderName(ctx)
  197. if err != nil {
  198. return err
  199. }
  200. session := sessionsManager.Start(ctx)
  201. session.Delete(providerName)
  202. return nil
  203. }
  204. //一些函数助手的结尾 设置key secret 与回调方法
  205. func main() {
  206. goth.UseProviders(
  207. twitter.New(os.Getenv("TWITTER_KEY"), os.Getenv("TWITTER_SECRET"), "http://localhost:3000/auth/twitter/callback"),
  208. //如果您想在Twitter提供程序中使用authenticate而不是authorize,请改用它。
  209. // twitter.NewAuthenticate(os.Getenv("TWITTER_KEY"), os.Getenv("TWITTER_SECRET"), "http://localhost:3000/auth/twitter/callback"),
  210. facebook.New(os.Getenv("FACEBOOK_KEY"), os.Getenv("FACEBOOK_SECRET"), "http://localhost:3000/auth/facebook/callback"),
  211. fitbit.New(os.Getenv("FITBIT_KEY"), os.Getenv("FITBIT_SECRET"), "http://localhost:3000/auth/fitbit/callback"),
  212. gplus.New(os.Getenv("GPLUS_KEY"), os.Getenv("GPLUS_SECRET"), "http://localhost:3000/auth/gplus/callback"),
  213. github.New(os.Getenv("GITHUB_KEY"), os.Getenv("GITHUB_SECRET"), "http://localhost:3000/auth/github/callback"),
  214. spotify.New(os.Getenv("SPOTIFY_KEY"), os.Getenv("SPOTIFY_SECRET"), "http://localhost:3000/auth/spotify/callback"),
  215. linkedin.New(os.Getenv("LINKEDIN_KEY"), os.Getenv("LINKEDIN_SECRET"), "http://localhost:3000/auth/linkedin/callback"),
  216. lastfm.New(os.Getenv("LASTFM_KEY"), os.Getenv("LASTFM_SECRET"), "http://localhost:3000/auth/lastfm/callback"),
  217. twitch.New(os.Getenv("TWITCH_KEY"), os.Getenv("TWITCH_SECRET"), "http://localhost:3000/auth/twitch/callback"),
  218. dropbox.New(os.Getenv("DROPBOX_KEY"), os.Getenv("DROPBOX_SECRET"), "http://localhost:3000/auth/dropbox/callback"),
  219. digitalocean.New(os.Getenv("DIGITALOCEAN_KEY"), os.Getenv("DIGITALOCEAN_SECRET"), "http://localhost:3000/auth/digitalocean/callback", "read"),
  220. bitbucket.New(os.Getenv("BITBUCKET_KEY"), os.Getenv("BITBUCKET_SECRET"), "http://localhost:3000/auth/bitbucket/callback"),
  221. instagram.New(os.Getenv("INSTAGRAM_KEY"), os.Getenv("INSTAGRAM_SECRET"), "http://localhost:3000/auth/instagram/callback"),
  222. intercom.New(os.Getenv("INTERCOM_KEY"), os.Getenv("INTERCOM_SECRET"), "http://localhost:3000/auth/intercom/callback"),
  223. box.New(os.Getenv("BOX_KEY"), os.Getenv("BOX_SECRET"), "http://localhost:3000/auth/box/callback"),
  224. salesforce.New(os.Getenv("SALESFORCE_KEY"), os.Getenv("SALESFORCE_SECRET"), "http://localhost:3000/auth/salesforce/callback"),
  225. amazon.New(os.Getenv("AMAZON_KEY"), os.Getenv("AMAZON_SECRET"), "http://localhost:3000/auth/amazon/callback"),
  226. yammer.New(os.Getenv("YAMMER_KEY"), os.Getenv("YAMMER_SECRET"), "http://localhost:3000/auth/yammer/callback"),
  227. onedrive.New(os.Getenv("ONEDRIVE_KEY"), os.Getenv("ONEDRIVE_SECRET"), "http://localhost:3000/auth/onedrive/callback"),
  228. //将localhost.com指向http://localhost:3000/auth/yahoo/通过代理回调作为雅虎
  229. //不允许在重定向uri中放置自定义端口
  230. yahoo.New(os.Getenv("YAHOO_KEY"), os.Getenv("YAHOO_SECRET"), "http://localhost.com"),
  231. slack.New(os.Getenv("SLACK_KEY"), os.Getenv("SLACK_SECRET"), "http://localhost:3000/auth/slack/callback"),
  232. stripe.New(os.Getenv("STRIPE_KEY"), os.Getenv("STRIPE_SECRET"), "http://localhost:3000/auth/stripe/callback"),
  233. wepay.New(os.Getenv("WEPAY_KEY"), os.Getenv("WEPAY_SECRET"), "http://localhost:3000/auth/wepay/callback", "view_user"),
  234. //默认使用paypal production auth urls,请将PAYPAL_ENV = sandbox设置为环境变量进行测试
  235. //在沙盒环境中
  236. paypal.New(os.Getenv("PAYPAL_KEY"), os.Getenv("PAYPAL_SECRET"), "http://localhost:3000/auth/paypal/callback"),
  237. steam.New(os.Getenv("STEAM_KEY"), "http://localhost:3000/auth/steam/callback"),
  238. heroku.New(os.Getenv("HEROKU_KEY"), os.Getenv("HEROKU_SECRET"), "http://localhost:3000/auth/heroku/callback"),
  239. uber.New(os.Getenv("UBER_KEY"), os.Getenv("UBER_SECRET"), "http://localhost:3000/auth/uber/callback"),
  240. soundcloud.New(os.Getenv("SOUNDCLOUD_KEY"), os.Getenv("SOUNDCLOUD_SECRET"), "http://localhost:3000/auth/soundcloud/callback"),
  241. gitlab.New(os.Getenv("GITLAB_KEY"), os.Getenv("GITLAB_SECRET"), "http://localhost:3000/auth/gitlab/callback"),
  242. dailymotion.New(os.Getenv("DAILYMOTION_KEY"), os.Getenv("DAILYMOTION_SECRET"), "http://localhost:3000/auth/dailymotion/callback", "email"),
  243. deezer.New(os.Getenv("DEEZER_KEY"), os.Getenv("DEEZER_SECRET"), "http://localhost:3000/auth/deezer/callback", "email"),
  244. discord.New(os.Getenv("DISCORD_KEY"), os.Getenv("DISCORD_SECRET"), "http://localhost:3000/auth/discord/callback", discord.ScopeIdentify, discord.ScopeEmail),
  245. meetup.New(os.Getenv("MEETUP_KEY"), os.Getenv("MEETUP_SECRET"), "http://localhost:3000/auth/meetup/callback"),
  246. // Auth0为每个客户分配域,必须提供域以使auth0正常工作
  247. auth0.New(os.Getenv("AUTH0_KEY"), os.Getenv("AUTH0_SECRET"), "http://localhost:3000/auth/auth0/callback", os.Getenv("AUTH0_DOMAIN")),
  248. xero.New(os.Getenv("XERO_KEY"), os.Getenv("XERO_SECRET"), "http://localhost:3000/auth/xero/callback"),
  249. )
  250. // OpenID Connect基于OpenID Connect自动发现URL(https://openid.net/specs/openid-connect-discovery-1_0-17.html)
  251. //因为OpenID Connect提供程序在New()中初始化它,它可以返回应该处理或忽略的错误
  252. //暂时忽略错误
  253. openidConnect, _ := openidConnect.New(os.Getenv("OPENID_CONNECT_KEY"), os.Getenv("OPENID_CONNECT_SECRET"), "http://localhost:3000/auth/openid-connect/callback", os.Getenv("OPENID_CONNECT_DISCOVERY_URL"))
  254. if openidConnect != nil {
  255. goth.UseProviders(openidConnect)
  256. }
  257. m := make(map[string]string)
  258. m["amazon"] = "Amazon"
  259. m["bitbucket"] = "Bitbucket"
  260. m["box"] = "Box"
  261. m["dailymotion"] = "Dailymotion"
  262. m["deezer"] = "Deezer"
  263. m["digitalocean"] = "Digital Ocean"
  264. m["discord"] = "Discord"
  265. m["dropbox"] = "Dropbox"
  266. m["facebook"] = "Facebook"
  267. m["fitbit"] = "Fitbit"
  268. m["github"] = "Github"
  269. m["gitlab"] = "Gitlab"
  270. m["soundcloud"] = "SoundCloud"
  271. m["spotify"] = "Spotify"
  272. m["steam"] = "Steam"
  273. m["stripe"] = "Stripe"
  274. m["twitch"] = "Twitch"
  275. m["uber"] = "Uber"
  276. m["wepay"] = "Wepay"
  277. m["yahoo"] = "Yahoo"
  278. m["yammer"] = "Yammer"
  279. m["gplus"] = "Google Plus"
  280. m["heroku"] = "Heroku"
  281. m["instagram"] = "Instagram"
  282. m["intercom"] = "Intercom"
  283. m["lastfm"] = "Last FM"
  284. m["linkedin"] = "Linkedin"
  285. m["onedrive"] = "Onedrive"
  286. m["paypal"] = "Paypal"
  287. m["twitter"] = "Twitter"
  288. m["salesforce"] = "Salesforce"
  289. m["slack"] = "Slack"
  290. m["meetup"] = "Meetup.com"
  291. m["auth0"] = "Auth0"
  292. m["openid-connect"] = "OpenID Connect"
  293. m["xero"] = "Xero"
  294. var keys []string
  295. for k := range m {
  296. keys = append(keys, k)
  297. }
  298. sort.Strings(keys)
  299. providerIndex := &ProviderIndex{Providers: keys, ProvidersMap: m}
  300. //创建我们的应用,
  301. //设置一个视图
  302. //设置sessions
  303. //并为展示设置路由器
  304. app := iris.New()
  305. //附加并构建我们的模板
  306. app.RegisterView(iris.HTML("./templates", ".html"))
  307. //启动路由器
  308. app.Get("/auth/{provider}/callback", func(ctx iris.Context) {
  309. user, err := CompleteUserAuth(ctx)
  310. if err != nil {
  311. ctx.StatusCode(iris.StatusInternalServerError)
  312. ctx.Writef("%v", err)
  313. return
  314. }
  315. ctx.ViewData("", user)
  316. if err := ctx.View("user.html"); err != nil {
  317. ctx.Writef("%v", err)
  318. }
  319. })
  320. app.Get("/logout/{provider}", func(ctx iris.Context) {
  321. Logout(ctx)
  322. ctx.Redirect("/", iris.StatusTemporaryRedirect)
  323. })
  324. app.Get("/auth/{provider}", func(ctx iris.Context) {
  325. //尝试让用户无需重新进行身份验证
  326. if gothUser, err := CompleteUserAuth(ctx); err == nil {
  327. ctx.ViewData("", gothUser)
  328. if err := ctx.View("user.html"); err != nil {
  329. ctx.Writef("%v", err)
  330. }
  331. } else {
  332. BeginAuthHandler(ctx)
  333. }
  334. })
  335. app.Get("/", func(ctx iris.Context) {
  336. ctx.ViewData("", providerIndex)
  337. if err := ctx.View("index.html"); err != nil {
  338. ctx.Writef("%v", err)
  339. }
  340. })
  341. // http://localhost:3000
  342. app.Run(iris.Addr("localhost:3000"))
  343. }
  344. type ProviderIndex struct {
  345. Providers []string
  346. ProvidersMap map[string]string
  347. }

index.html

  1. {{range $key, $value:=.Providers}}
  2. <p><a href="/auth/{{ $value }}">Log in with {{index $.ProvidersMap $value}}</a></p>
  3. {{end}}

user.html

  1. <p><a href="/logout/{{ .Provider }}">logout</a></p>
  2. <p>Name: {{.Name}} [{{.LastName}}, {{.FirstName}}]</p>
  3. <p>Email: {{.Email}}</p>
  4. <p>NickName: {{.NickName}}</p>
  5. <p>Location: {{.Location}}</p>
  6. <p>AvatarURL: {{.AvatarURL}} <img src="{{ .AvatarURL }}"></p>
  7. <p>Description: {{.Description}}</p>
  8. <p>UserID: {{.UserID}}</p>
  9. <p>AccessToken: {{.AccessToken}}</p>
  10. <p>ExpiresAt: {{.ExpiresAt}}</p>
  11. <p>RefreshToken: {{.RefreshToken}}</p>

提示

  • 去Github设置第三方授权登录,设置回调路径,把key,secret填入上面代码指定位置
  • 执行程序选择github 即可看到想要的效果