【问题标题】:User Authentication with Go and AngularJS使用 Go 和 AngularJS 进行用户身份验证
【发布时间】:2014-09-22 21:27:05
【问题描述】:

我想在我的 Go 应用程序上设置一个带有令牌的身份验证系统,该应用程序使用 AngularJS 前端。

我正在寻找有关创建安全系统的所有步骤的建议。这是我的步骤:

我们会考虑一个用户条目已经写入我们的数据库。

1 - 用户想要登录

用户转到提示他登录的网络应用程序的索引。

通过我的 app.yaml 文件,我为 AngularJS 前端声明了带有 dependenciesHTML 文件:

application: authtest
version: 1
runtime: go
api_version: go1

handlers:
- url: /
  static_files: frontend/index.html
  upload: frontend/(.*\.html)

- url: /js
  static_dir: frontend/js

- url: /.*
  script: _go_app

视觉:

代码:

<form ng-submit="signIn()" ng-controller="signInCtrl">
    <input type="email" ng-model="credentials.email" placeholder="Email" name="email">
    <input type="password" ng-model="credentials.password" placeholder="Password" name="password">
    <input type="submit" value="Sign in">
</form>

用户输入他的凭据,然后点击登录按钮。

2 - 请求服务器

AngularJS 模块代码:

<script>
    angular.module('authtest', ['ngCookies'])
    .controller('signInCtrl', ['$scope', '$http', '$window', '$cookieStore', '$route', function($scope, $http, $window, $cookieStore, $route){
        var credentials = {}
        $scope.signIn = function() {
            $http.post('/signin', credentials)
            .success(function (data, status) {
                if(status == 200) {
                    if(data.Access_token){
                        //set cookies with tokens
                        $cookieStore.put('access_token', data.Access_token);
                        $cookieStore.put('refresh_token', data.Refresh_token);
                        $route.reload();
                    }
                    else {
                        $window.alert('Wrong credentials, try again.');
                    }
                }
            })
            .error(function (data, status) {
                $window.alert('error: ' + data + '(errorStatus: ' + status + ')');
            });
        };
    }]);
</script>

Angular 模块将表单发布到 /signin 路径,因此 Go 应用程序需要处理它:

package main
import (
    "net/http"
    "encoding/json"
    "fmt"
)
type SignInCredentials struct {
    Email string
    Password string
}
type Token struct {
    Access_token string
    Refresh_token string
    Id_token string
}
func init() {        //I use init() instead of main() because I'm using App Engine.
    http.HandleFunc("/signin", SignInHandler)
}
func SignInHandler (w http.ResponseWriter, r *http.Request) {
    decoder := json.NewDecoder(r.Body)
    var c SignInCredentials
    err := decoder.Decode(&c)
    if err != nil {
        panic()
    }
    var hashPassword = hash(c.Password) //Is it at a good level to hash it ?
    if isInDatabase(c.Email, hashPassword) == false {
        fmt.Fprintf(w, "") // Really useful ?
        return
    }

    var tokens Token
    /**
    * The user seems to be well stored in the database, but how
    * to manage my tokens ?
    */
    response, _ := json.Marshal(tokens)
    fmt.Fprintf(w, string(response))
}

显然,hashPassword(p string) 对用户密码进行哈希处理,isInDatabase(e string, p string) 是一个布尔值,用于检查提供的凭据是否存储在我们的数据库中。

  • 正如我在代码中所说,我想知道现在是散列用户密码的 好时机,还是在 客户端 中散列它?
  • 如果用户不在数据库中,打印一个零车字符串真的很有用,或者我可以返回函数吗?
  • 这里的主要问题是如何管理我的令牌?我经常看到token主要由access_tokenrefresh_tokenid_token组成。我也知道access_token 的使用时间有限(例如当前会话,3600 秒),我们使用refresh_token 来获得一个全新的access_token。但是如何生成和存储呢?

3 - 使用 AngularJS 处理响应

正如已经用 AngularJS 模块代码编写的那样,当用户通过良好的身份验证时,我们使用提供的 tokens 设置 cookie。这是一个好方法吗?我也想知道如何在我的系统中防止XSRF (CSRF) 攻击?

感谢您未来的回答和建议。

【问题讨论】:

    标签: angularjs authentication go token


    【解决方案1】:

    好的,我一直在使用这个 approach(作者 Frederik Nakstad),目前看来还可以。

    身份验证非常简单。用户通过如下形式通过加密连接提交凭据:

    <form name="loginform" class="uk-form" ng-submit="login()">
            <fieldset data-uk-margin>
                <legend><h2>Login</h2></legend>
                <div class="uk-form-row">
                    <input class="uk-form-large" type="text" ng-model="cred.user" placeholder="Username" required>
                </div>
                <div class="uk-form-row">
                    <input class="uk-form-large" type="password" ng-model="cred.password" placeholder="Password" required>
                </div>
                <div class="uk-form-row">
                    <button ng-disabled="loginform.$invalid" class="uk-button uk-button-large" type="submit">Login</button>
                </div>
            </fieldset>
        </form>
    

    然后控制器按如下方式处理响应:

    App.controller('LoginCtrl', function ($rootScope, $scope, $location, $window, authService) {
        $scope.cred = {}
        $scope.login = function () {
            if ($scope.loginform.$valid) {
                authService.auth(this.cred, function (stat) {
                    if (stat === 200) {
                        $rootScope.loginStatus = authService.isLoggedIn();
                        $location.path('/test');
                    } else {
                       ....
                       $location.path('/login');
                    }
                });
                $scope.cred = {}
                $scope.loginform.$setPristine();
            }
        };
        $window.document.title = 'Admin Login';
    });
    

    控制器需要一个 200 的 http 状态码(除非验证成功,否则后端不应响应),如果服务器响应的不是 200,则路由再次更改为登录页面。

    棘手的部分是维护对身份验证状态的检查。

    我实现身份验证系统的方式是通过以下方式向我的路由对象添加自定义属性(如指南但没有角色系统):

    App.js

    ...
    
    $routeProvider.when('/login', {
                templateUrl: '/content/...',
                controller: 'Ctrl1',
                requireLogin: false
            }).when('/logout', {
                resolve: {
                    logout: function ($location, $rootScope, authService) {
                        authService.logout(function (s) {
                            if (s === 200) {
                                $rootScope.loginStatus = authService.isLoggedIn();
                                $location.path('/test');
                            } else {
                                ....
                            }
    
                        });
                    }
                },
                requireLogin: true
            }).when('/metaconfig', {
                templateUrl: '/content/...',
                controller: 'Ctrl2',
                requireLogin: true
    
    ...
    

    然后,每当请求路由时,我都有此功能来评估路由是否需要特定权限/身份验证才能在该路由中(ofc 这并不意味着您的路由是安全的,它只是意味着您的平均乔不会除非动态修改 js 文件,否则能够查看路由)

    App.js

    angular.module('App', ['ngCookies', 'ngRoute'])
    .config(function{...}).run(function ($rootScope, ..., $location, authService) {
    
    
        $rootScope.loginStatus = false;
        authService.authCheck();
    
        $rootScope.$on('$routeChangeStart', function (event, next, current) {
             if (next.requireLogin) {
                 if (!authService.isLoggedIn()) {
                    $location.path('/login');
                    event.preventDefault();
                  }
               }
           });
    

    $routeChangeStart 传递的函数调用了我称为 authService 的服务中的另一个函数,它看起来像这样

    authService.js

    'use strict';
    
    
    App.service('authService', function ($rootScope, $log, $http, $q) {
    
        var userIsAuthenticated = false;
    
        this.auth = function (up, cb) {
            $http({method: 'POST', url: '/api/login', data: up}).
                success(function (data, status, headers, config) {
                    if (status === 200) {
                        userIsAuthenticated = true;
                    }
                    cb(status)
                }).
                error(function (data, status, headers, config) {
                    userIsAuthenticated = false;
                    cb(status)
                });
        };
        this.authCheck = function () {
            $http({method: 'PUT', url: '/api/login' }).
                success(function (data, status, headers, config) {
                    if (status === 200) {
                        userIsAuthenticated = true;
                    } else {
                        userIsAuthenticated = false;
                    }
                    $rootScope.loginStatus = userIsAuthenticated;
                }).
                error(function (data, status, headers, config) {
                    userIsAuthenticated = false;
                    $rootScope.loginStatus = userIsAuthenticated;
                });
        };
        this.isLoggedIn = function () {
            return userIsAuthenticated;
        };
        this.logout = function (cb) {
            $http({ method: 'DELETE', url: '/api/logout' }).
                success(function (data, status, headers, config) {
                    userIsAuthenticated = false;
                    cb(status)
                }).
                error(function (data, status, headers, config) {
                    userIsAuthenticated = false;
                    cb(status)
                });
        };
    });
    

    现在,每次需要特定角色或身份验证的路由都需要评估并从服务器获取 200 状态代码,否则身份验证状态将设置为 false,直到用户再次进行身份验证。最后,即使 1% 的身份验证过程依赖您的前端也会使整个身份验证系统变得多余。


    编辑

    CSRF 风险和会话管理

    在 CSRF 的情况下,我使用了一个名为 Beego 的框架,该框架提供了开箱即用的 CSRF 避免机制,您只需要指定出国日期和令牌的加密。

    当涉及到登录会话时,服务器应该管理在客户端存储为 cookie 的加密会话,当然我不建议您实施这个,因为它非常危险并且可能以错误的方式实施。同样,Beego 提供了一个不错的功能,允许您以自己的方式管理和加密会话

    我希望这有助于回答你的问题,祝你好运。

    【讨论】:

      猜你喜欢
      • 2016-04-13
      • 2017-10-24
      • 2014-01-14
      • 1970-01-01
      • 2017-08-14
      • 1970-01-01
      • 2017-05-09
      • 1970-01-01
      • 2013-04-20
      相关资源
      最近更新 更多