使用Go和AngularJS进行用户身份验证

时间:2014-09-22 21:27:06

标签: angularjs authentication go token

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

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

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

1 - 用户想要登录

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

使用 app.yaml 文件,我为AngularJS前端声明 HTML 文件依赖关系

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

视觉:

Alt text

代码:

<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)是一个布尔值,用于检查提供的凭据是否存储在我们的数据库中。

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

3 - 使用AngularJS处理响应

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

感谢您将来的答案和建议。

1 个答案:

答案 0 :(得分:2)

好的,我一直在使用这个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

...

然后,无论何时请求路由,我都有此功能来评估路由是否需要特定的权限/身份验证才能在该路由中(这并不意味着您的路由是安全的,它只是意味着您的平均乔不会能够查看路由除非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%的auth进程将使整个auth系统变得冗余。


修改

CSRF风险和会话管理

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

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

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