微服务从零开始之登录与注册一

作者: 老瓦在霸都 | 来源:发表于2016-11-29 00:08 被阅读233次

�概述

任何一个微服务需要基本的安全保证, 也是遵循AAA原则: 鉴证,授权和计帐

  • Authentication 要求是合法用户
  • Authorization 要求有合法权限
  • Accounting 要求有记录可追踪

让我们从需求分析到代码实现,尽量啰嗦地来说说怎么做一个看似简单的登录注册模块, 假设该服务叫做 Checklist 我的清单

需求分析

用例 Use case

除了使用绘图工具和画用例图, 还有有�几种方法通过脚本来�生成用例图

一是使用在线网站 yuml.me

https://yuml.me/608ca377

use case

UML 生成脚本如下

[User]-(Sign In)
[User]-(Sign Out)
[User]-(Sign Up)
[User]-(Forget Password)
[User]-(Change Password)
(Sign In)>(Remember Me)
(Sign Up)>(Send Verification Email)
(Forget Password)>(Send Reset Password Email)
(Change Password)<(Send Reset Password Email)
[Admin]^[User]
[Admin]-(Add User)
[Admin]-(Delete User)
[Admin]-(Lock User)
[Admin]-(Change Password Policy)

�二是使用是通过 plantuml 来生成

http://plantuml.com/ 上下载 plantuml.jar , 然后用如下命令生成用例图

java -jar plantuml.jar usecase.txt

示例UML 生成脚本如下

@startuml

User -> (Sign In)
User --> (Sign Out) 
User --> (Sign Up)
User --> (activate)
User --> (forget/reset password)
:Admin: ---> (lock user)
:Admin: ---> (add user) 
:Admin: ---> (delete user) 

@enduml

三是使用graphviz

先安装graphviz, 再运行如下命令

dot usecase1.gv -Tpng -o usecase1.png

示例UML生成脚本如下

digraph G {
    rankdir=LR;

    subgraph clusterUser {label="User"; labelloc="b"; peripheries=0; user};
    
    user [shapefile="stick.png", peripheries=0];

    signin [label="Sign In", shape=ellipse];

    signout [label="Sign Out", shape=ellipse];

    signup [label="Sign Up", shape=ellipse];

    user->signin [arrowhead=none];

    user->signout [arrowhead=none];

    user->signup [arrowhead=none];
}

用户故事 User Story

User Story 讲究 INVEST 原则

  • "I" ndependent (of all others) 独立的
  • "N" egotiable (not a specific contract for features) 可协商的
  • "V" aluable (or vertical) 有价值的
  • "E" stimable (to a good approximation) 可估量的
  • "S" mall (so as to fit within an iteration) 足够小的
  • "T" estable (in principle, even if there isn't a test for it yet) 可测试的

Sign Up 注册

  1. 作为一个未注册用户, 我想输入我的电子邮件地址和密码,注册到 Checklist
    1.1 我必须输入合法和邮件地址,符合密码策略的密码以及一致的验证码进行注册
    默认的密码策略是最低8个字符, �必须包含大小写字母和至少一个数字

| # | Story | Priority | Estimation | Deadline| Comments |
|---|---|---|---|---|
| 1.1.1 | �生成验证码 |---|---|---|--- |
| 1.1.2 | 显示注册表单|---|---|---|--- |
| 1.1.3 | 邮件地址格式验证|---|---|---|--- |
| 1.1.4 | 比较两次输入的密码是否相同|---|---|---|--- |
| 1.1.5 | 验证密码是否符合密码策略|---|---|---|--- |
| 1.1.6 | 验证输入的验证码|---|---|---|--- |
| 1.1.7 | 检查是否已有相同的邮件地址存在|---|---|---|--- |
| 1.1.8 | 输入验证无误后存入数据库,状态为pending|---|---|---|--- |
| 1.1.9 | 生成此用户的激活链接|---|---|---|--- |
| 1.1.10 | 向注册邮箱发送一封确认邮件|---|---|---|--- |

1.2 我的注册邮箱会收到一封验证邮件, 提示我点击注册连接, �从而激活我的注册帐户

1.3 当我完成激活后会自动跳到 Checklist 的首页, 提示我进行登录

实现

这次我们用Java实现,选择的框架是Spring Boot, 先从最笨最直接的方法入手, 之后再看看相关的框架 Spring Security 和 Apache Shiro 是怎么做的

Model

model

View

字段 �控件
username text
email email
password password
confirmPassword password
rememberMe checkbox
forgetPassword link

创建项目

spring boot starter

或者直接用 Spring Cli 直接生成

spring init --build=maven --java-version=1.8 --dependencies=web --packaging=jar --groupId=com.github.walterfan --artifactId=checklist

在实践中始终牢记 三个基本点

  • 无模型不编程-MDD 模型驱动开发
  • 无测试不开发-TDD 测试驱动开发
  • 无度量不交付-MDD 度量驱动开发

领域模型很简单
Register
User
Role

测试用例也简单

  1. 注册
  2. 激活
  3. 登录

度量就只记录

  1. 注册次数
  2. 激活次数
  3. 性能数据

代码结构

废话不多说,上代码 checklist source codes on github

code structure

数据库我们选用两个

  1. h2 作为测试数据库
               <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
  1. mysql 作为产品数据库
                 <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.41</version>
        </dependency>

表现层

表现层选用 Freemarker 作为后端模板, 前端选用 AngularJS + BootStrap

freemarker 是比较流行的后端页面生成的模板引擎, 这所以不用 JSP 和 JSF, 就是为了不想在后端模板层面引入太多逻辑和不必要的复杂性, freemarker 就只干模板引擎该干的事

在 src/main/resources/templates 做如下模板

  • about.ftl
  • admin.ftl
  • footer.ftl
  • header.ftl
  • index.ftl
  • layout.ftl
  • login.ftl

主要的 Freemarker 模板 layout.ftl 如下

<#macro myLayout>
<!DOCTYPE html>
<html >
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
    <meta name="description" content="Kanban">
    <meta name="author" content="Walter">
    <link rel="icon" href="./images/favicon.ico">

    <title>Check List</title>

    <!-- Bootstrap core CSS -->
    <link href="./css/bootstrap.min.css" rel="stylesheet">

    <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
    <link href="./css/ie10-viewport-bug-workaround.css" rel="stylesheet">

    <!-- Custom styles for this template -->
    <link href="./css/app.css" rel="stylesheet">
    <link href="./css/jumbotron-narrow.css" rel="stylesheet">
    <script src="./js/vendor/jquery-1.11.2.min.js"></script>

    <script src="./js/vendor/angular.js"></script>
    <script src="./js/vendor/angular-sanitize.js"></script>
    <script src="./js/vendor/angular-resource.js"></script>
    <script src="./js/vendor/ui-bootstrap.js"></script>
    <script src="./js/vendor/ui-bootstrap-tpls.js"></script>

    <script src="./js/vendor/ngDialog.min.js"></script>

    <script src="./js/app.js"></script>

</head>

<body>

<div class="container" >
    <#include "header.ftl"/>

    <div class="panel panel-default" >
    <#nested/>
    </div>

    <#include "footer.ftl"/>

</div> <!-- /container -->


<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<script src="./js/vendor/ie10-viewport-bug-workaround.js"></script>
</body>
</html>

</#macro>

首页

home page

源码 index.ftl 如下

<#import "layout.ftl" as layout>
<@layout.myLayout>
<div class="jumbotron">
    <h2>Checklist</h2>
    <p class="lead">
        Checklist for your work and life
    </p>
    <p><a class="btn btn-lg btn-success" href="/checkist/add" role="button">Add a Check list</a></p>
</div>
<script>
    $('li:eq(0)').addClass('active');
</script>
</@layout.myLayout>
login page

登录页面

register page

源码 login.ftl 如下

<#import "layout.ftl" as layout>
<@layout.myLayout>
<!-- refer to http://bootsnipp.com/snippets/featured/login-and-register-tabbed-form -->
<div class="page-header text-center">
    <div class="row nav nav-tabs nav-justified">
        <div class="col-xs-6">
            <a href="#" class="active" id="login-form-link">Login</a>
        </div>
        <div class="col-xs-6">
            <a href="#" id="register-form-link">Register</a>
        </div>
    </div>
</div>

<div class="panel-body" ng-app="myApp" ng-controller="myController">
    <div class="row">
        <div class="col-lg-12">
            <form id="login-form" class="form-horizontal" ng-submit="submitLoginForm()">
                <div class="form-group">
                    <input type="text" name="username" id="username" tabindex="1" class="form-control" placeholder="Username" value=""  ng-model="user.username">
                </div>
                <div class="form-group">
                    <input type="password" name="password" id="password" tabindex="2" class="form-control" placeholder="Password"  ng-model="user.password">
                </div>
                <div class="form-group text-center">
                    <input type="checkbox" tabindex="3" class="" name="remember" id="remember">
                    <label for="remember"> Remember Me</label>
                </div>
                <div class="form-group">
                    <div class="row">
                        <div class="col-sm-6 col-sm-offset-3">
                            <input type="submit" name="login-submit" id="login-submit" tabindex="4" class="form-control btn btn-login" value="Log In">
                        </div>
                    </div>
                </div>
                <div class="form-group">
                    <div class="row">
                        <div class="col-lg-12">
                            <div class="text-center">
                                <a href="http://phpoll.com/recover" tabindex="5" class="forgot-password">Forgot Password?</a>
                            </div>
                        </div>
                    </div>
                </div>
            </form>
            <form id="register-form" class="form-horizontal" style="display: none;" ng-submit="submitRegisterForm()">
                <div class="form-group">
                    <input type="text" name="username" id="username" tabindex="1" class="form-control" placeholder="Username" value=""  ng-model="user.username">
                </div>
                <div class="form-group">
                    <input type="email" name="email" id="email" tabindex="1" class="form-control" placeholder="Email Address" value=""  ng-model="user.email">
                </div>
                <div class="form-group">
                    <input type="password" name="password" id="password" tabindex="2" class="form-control" placeholder="Password"  ng-model="user.password">
                </div>
                <div class="form-group">
                    <input type="password" name="confirm-password" id="confirm-password" tabindex="2" class="form-control" placeholder="Confirm Password"  ng-model="user.passwordConfirmation">
                </div>
                <div class="form-group">
                    <div class="row">
                        <div class="col-sm-6 col-sm-offset-3">
                            <input type="submit" name="register-submit" id="register-submit" tabindex="4" class="form-control btn btn-register" value="Register Now">
                        </div>
                    </div>
                </div>
            </form>
        </div>
    </div>
</div> <!-- panel-body end -->

<script>
    $('li:eq(1)').addClass('active');
</script>
</@layout.myLayout>

注: 我不太擅长前端页面的界面设计, 这里参考了 http://bootsnipp.com/snippets/jvgVX 的示例, 一个很有用的基于 bootstrap 的样式主题设计网站

当用户点击注册页面, 填写所需字段, 并提交表单时, 用 Angular JS 向后台提交, 代码如下

'use strict';

$(function() {

    $('#login-form-link').click(function(e) {
        $("#login-form").delay(100).fadeIn(100);
        $("#register-form").fadeOut(100);
        $('#register-form-link').removeClass('active');
        $(this).addClass('active');
        e.preventDefault();
    });
    $('#register-form-link').click(function(e) {
        $("#register-form").delay(100).fadeIn(100);
        $("#login-form").fadeOut(100);
        $('#login-form-link').removeClass('active');
        $(this).addClass('active');
        e.preventDefault();
    });

});

// Defining angularjs application.
var myApp = angular.module('myApp', []);
// Controller function and passing $http service and $scope var.
myApp.controller('myController', function($scope, $http) {
    // create a blank object to handle form data.
    $scope.user = {};
    // calling our submit function.
    $scope.submitRegisterForm = function() {
        var postData = {
            username:$scope.user.username,
            email: $scope.user.email,
            password: $scope.user.password,
            passwordConfirmation: $scope.user.passwordConfirmation
        };
        $http({
            method  : 'POST',
            url     : '/checklist/api/v1/users/register',
            data    : postData,
            headers : {'Content-Type': 'application/json'}
        })
            .success(function(data) {
                if (data.errors) {
                    // Showing errors.
                    $scope.errors = data.errors;
                } else {
                    $scope.message = data.message;
                }
            });
    };

    $scope.submitLoginrForm = function() {
        var postData = {
            email: $scope.user.email,
            password: $scope.user.password,
        };
        $http({
            method  : 'POST',
            url     : '/checklist/api/v1/users/login',
            data    : postData,
            headers : {'Content-Type': 'application/json'}
        })
            .success(function(data) {
                if (data.errors) {
                    // Showing errors.
                    $scope.errors = data.errors;
                } else {
                    $scope.message = data.message;
                }
            });
    };
});

好了表现层包括前端的代码大致搞定了, 现在开始写后端的 Java web service 代码, 参见 微服务从零开始之登录与注册二

相关文章

  • 微服务从零开始之登录与注册二

    书接上文 微服务从零开始之登录与注册一, 表现层及前端代码搞定, 现在开始来设计和实现后端的 Java Web S...

  • 微服务从零开始之登录与注册一

    �概述 任何一个微服务需要基本的安全保证, 也是遵循AAA原则: 鉴证,授权和计帐 Authentication ...

  • 登录与注册

    注册与登录(一) 注册(前端、服务器) 登录(前端、服务器) 只关注,注册登录的大体逻辑,没有实现邮箱密码校验。博...

  • kiwiDiary-页面功能设计

    功能设计 用户登录注册页面 完成用户登录注册功能,由于微信或者其他第三方登录以及手机号注册登录需要购买相应的服务网...

  • 微信公众号注册流程总结

    注册类型 服务号,主体类型(企业),开通微信认证、开通微信支付 基本资料准备 1 邮箱,用于登录账号(没有与微信绑...

  • 物业管理小程序功能简介

    1、注册与登录 微信分享、群分享、扫码打开小程序 使用微信登录,用户不需要注册(程序自动注册),点开即可使用 绑定...

  • SpringCloud技术指南系列(四)服务注册发现之Consu

    SpringCloud技术指南系列(四)服务注册发现之Consul服务注册 SpringCloud所谓的服务注册与...

  • Session

    在文章Cookie与登录注册中,我们已经实现了简单的注册和登录功能,并且登录后服务器会给客户端发送一个Cookie...

  • iOS电商项目之登录注册

    登录注册分为第三方登录(QQ、微信),立即注册、免密登录、忘记密码,整个模块细节非常多。 登录注册 登录注册 想要...

  • SpringCloud技术指南系列(三)服务注册发现之Eurek

    SpringCloud技术指南系列(三)服务注册发现之Eureka服务调用 SpringCloud所谓的服务注册与...

网友评论

    本文标题:微服务从零开始之登录与注册一

    本文链接:https://www.haomeiwen.com/subject/wbaguttx.html