dongyou9373 2014-09-22 21:27
浏览 40
已采纳

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

I want to set up an authentication system with tokens on my Go application, which use an AngularJS front-end.

What I'm looking for is advices about all steps to create a safe system. Here's my steps :

We will considerate a user entry is already written in our database.

1 - The user wants to sign in

The user goes to the index of the web app which prompts him to sign in.

With my app.yaml file, I declare my HTML file with dependencies for the AngularJS front-end:

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

Visual :

Alt text

Code:

<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>

The user enters his credentials, and click on Sign in button.

2 - Request the server

The AngularJS module code:

<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>

The Angular module posts the form to /signin path, so the Go application needs to handle it:

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))
}

Obviously, hashPassword(p string) hashes the user password and isInDatabase(e string, p string) is a boolean which checks if the provided credentials are stored in our database.

  • As I said inside the code, I'm wondering if it's the good moment to hash the password of the user, or maybe hash it in client-side ?
  • Print a zero-car string if the user is not in database is really useful or could I just return the function ?
  • The main question here is how to manage my tokens ? I often see tokens are mainly composed of access_token, refresh_token and id_token. I also know access_token is limited in time to use (the current session, ot 3600seconds for example) and we use refresh_token to have a fresh new access_token. But how to generate it and store it ?

3 - Handle response with AngularJS

As already written in AngularJS module code, when the user is well authenticated, we set cookies with provided tokens. Is it a good way ? I'm also wondering how to prevent XSRF (CSRF) attacks in my system ?

Thank you for your future answers and advices.

  • 写回答

1条回答 默认 最新

  • doushan5222 2014-09-22 21:59
    关注

    Ok I've been using this approach (by Frederik Nakstad) and it seems to be ok so far.

    The authentication is pretty straight forward. The user submits credentials over encrypted connection through a form like this:

    <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>
    

    Then the controller handles the response in the following way:

    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';
    });
    

    The controller expects a 200 http status code (which the back-end should not respond with unless the authentication is successful), if the server responds with anything other than 200, the route is changed to login page again.

    The tricky part is maintaining checks of the authentication status.

    The way I implemented my authentication system is by adding custom attributes (like the guide but without a role system) to my route object in the following way:

    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
    
    ...
    

    Then Whenever a route gets requested I have this function that evaluates whether the route requires specific privileges/authentication to be in that route or not (ofc this does not mean that your route is secured, it simply means that your average joe wont be able to view routes unless js files get modified on the fly)

    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();
                  }
               }
           });
    

    The function passed with $routeChangeStart calls another function inside a service I have called authService and it looks like this

    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)
                });
        };
    });
    

    Now every time a route that requires specific role or authentication will need to evaluate and get a 200 status code from the server otherwise the authentication status is set to false until the user authenticates again. Finally, relying on your front-end on even 1% of the auth process would make the whole auth system redundant.


    Edit

    CSRF risk and session management

    In the case of CSRF, I am using a framework called Beego, this framework offers CSRF avoidance mechanism out of the box where you just need to specify expatriation date and encryption of the token.

    When it comes to the login sessions, the server should manage encrypted sessions that are stored as cookies on the client side, of course I do not recommend you implement this as it is very risky and can be implemented in a wrong way. Again, Beego offers a nice feature that allows you to manage and encrypt sessions in your own way

    I hope this helps answer your question, good luck.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥20 给自己本科IT专业毕业的妹m找个实习工作
  • ¥15 用友U8:向一个无法连接的网络尝试了一个套接字操作,如何解决?
  • ¥30 我的代码按理说完成了模型的搭建、训练、验证测试等工作(标签-网络|关键词-变化检测)
  • ¥50 mac mini外接显示器 画质字体模糊
  • ¥15 TLS1.2协议通信解密
  • ¥40 图书信息管理系统程序编写
  • ¥20 Qcustomplot缩小曲线形状问题
  • ¥15 企业资源规划ERP沙盘模拟
  • ¥15 树莓派控制机械臂传输命令报错,显示摄像头不存在
  • ¥15 前端echarts坐标轴问题