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

huangapple go评论95阅读模式
英文:

User Authentication with Go and AngularJS

问题

我想在我的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

可视化:

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

代码:

<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模块代码中已经写过的,当用户成功验证时,我们使用提供的令牌设置cookies。这是一个好方法吗?我还想知道如何防止我的系统受到XSRF(CSRF)攻击?

谢谢您未来的答复和建议。

英文:

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 :

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

Code:

&lt;form ng-submit=&quot;signIn()&quot; ng-controller=&quot;signInCtrl&quot;&gt;
	&lt;input type=&quot;email&quot; ng-model=&quot;credentials.email&quot; placeholder=&quot;Email&quot; name=&quot;email&quot;&gt;
	&lt;input type=&quot;password&quot; ng-model=&quot;credentials.password&quot; placeholder=&quot;Password&quot; name=&quot;password&quot;&gt;
	&lt;input type=&quot;submit&quot; value=&quot;Sign in&quot;&gt;
&lt;/form&gt;

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

2 - Request the server

The AngularJS module code:

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

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

package main
import (
	&quot;net/http&quot;
	&quot;encoding/json&quot;
	&quot;fmt&quot;
)
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&#39;m using App Engine.
	http.HandleFunc(&quot;/signin&quot;, SignInHandler)
}
func SignInHandler (w http.ResponseWriter, r *http.Request) {
	decoder := json.NewDecoder(r.Body)
	var c SignInCredentials
	err := decoder.Decode(&amp;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, &quot;&quot;) // 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

得分: 2

好的,我将为你翻译以下内容:

好的,我一直在使用这种方法(由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%的内容,整个身份验证系统就会变得多余。


编辑

CSRF风险和会话管理

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

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

希望这能帮助回答你的问题,祝你好运。

英文:

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:

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

Then the controller handles the response in the following way:

App.controller(&#39;LoginCtrl&#39;, 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(&#39;/test&#39;);
} else {
....
$location.path(&#39;/login&#39;);
}
});
$scope.cred = {}
$scope.loginform.$setPristine();
}
};
$window.document.title = &#39;Admin Login&#39;;
});

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(&#39;/login&#39;, {
templateUrl: &#39;/content/...&#39;,
controller: &#39;Ctrl1&#39;,
requireLogin: false
}).when(&#39;/logout&#39;, {
resolve: {
logout: function ($location, $rootScope, authService) {
authService.logout(function (s) {
if (s === 200) {
$rootScope.loginStatus = authService.isLoggedIn();
$location.path(&#39;/test&#39;);
} else {
....
}
});
}
},
requireLogin: true
}).when(&#39;/metaconfig&#39;, {
templateUrl: &#39;/content/...&#39;,
controller: &#39;Ctrl2&#39;,
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(&#39;App&#39;, [&#39;ngCookies&#39;, &#39;ngRoute&#39;])
.config(function{...}).run(function ($rootScope, ..., $location, authService) {
$rootScope.loginStatus = false;
authService.authCheck();
$rootScope.$on(&#39;$routeChangeStart&#39;, function (event, next, current) {
if (next.requireLogin) {
if (!authService.isLoggedIn()) {
$location.path(&#39;/login&#39;);
event.preventDefault();
}
}
});

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

authService.js

&#39;use strict&#39;;
App.service(&#39;authService&#39;, function ($rootScope, $log, $http, $q) {
var userIsAuthenticated = false;
this.auth = function (up, cb) {
$http({method: &#39;POST&#39;, url: &#39;/api/login&#39;, 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: &#39;PUT&#39;, url: &#39;/api/login&#39; }).
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: &#39;DELETE&#39;, url: &#39;/api/logout&#39; }).
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.

huangapple
  • 本文由 发表于 2014年9月23日 05:27:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/25983367.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定