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 数学建模数学建模需要
  • ¥15 关于#lua#的问题,请各位专家解答!
  • ¥15 什么设备可以研究OFDM的60GHz毫米波信道模型
  • ¥15 不知道是该怎么引用多个函数片段
  • ¥30 关于用python写支付宝扫码付异步通知收不到的问题
  • ¥50 vue组件中无法正确接收并处理axios请求
  • ¥15 隐藏系统界面pdf的打印、下载按钮
  • ¥15 基于pso参数优化的LightGBM分类模型
  • ¥15 安装Paddleocr时报错无法解决
  • ¥15 python中transformers可以正常下载,但是没有办法使用pipeline