微服務(wù)從零開始之登錄與注冊(cè)一

?概述

任何一個(gè)微服務(wù)需要基本的安全保證, 也是遵循AAA原則: 鑒證照皆,授權(quán)和計(jì)帳

  • Authentication 要求是合法用戶
  • Authorization 要求有合法權(quán)限
  • Accounting 要求有記錄可追蹤

讓我們從需求分析到代碼實(shí)現(xiàn)急鳄,盡量啰嗦地來說說怎么做一個(gè)看似簡(jiǎn)單的登錄注冊(cè)模塊, 假設(shè)該服務(wù)叫做 Checklist 我的清單

需求分析

用例 Use case

除了使用繪圖工具和畫用例圖堰酿, 還有有?幾種方法通過腳本來?生成用例圖

一是使用在線網(wǎng)站 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, 再運(yùn)行如下命令

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) 獨(dú)立的
  • "N" egotiable (not a specific contract for features) 可協(xié)商的
  • "V" aluable (or vertical) 有價(jià)值的
  • "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) 可測(cè)試的

Sign Up 注冊(cè)

  1. 作為一個(gè)未注冊(cè)用戶, 我想輸入我的電子郵件地址和密碼触创,注冊(cè)到 Checklist
    1.1 我必須輸入合法和郵件地址坎藐,符合密碼策略的密碼以及一致的驗(yàn)證碼進(jìn)行注冊(cè)
    默認(rèn)的密碼策略是最低8個(gè)字符, ?必須包含大小寫字母和至少一個(gè)數(shù)字

| # | Story | Priority | Estimation | Deadline| Comments |
|---|---|---|---|---|
| 1.1.1 | ?生成驗(yàn)證碼 |---|---|---|--- |
| 1.1.2 | 顯示注冊(cè)表單|---|---|---|--- |
| 1.1.3 | 郵件地址格式驗(yàn)證|---|---|---|--- |
| 1.1.4 | 比較兩次輸入的密碼是否相同|---|---|---|--- |
| 1.1.5 | 驗(yàn)證密碼是否符合密碼策略|---|---|---|--- |
| 1.1.6 | 驗(yàn)證輸入的驗(yàn)證碼|---|---|---|--- |
| 1.1.7 | 檢查是否已有相同的郵件地址存在|---|---|---|--- |
| 1.1.8 | 輸入驗(yàn)證無誤后存入數(shù)據(jù)庫(kù),狀態(tài)為pending|---|---|---|--- |
| 1.1.9 | 生成此用戶的激活鏈接|---|---|---|--- |
| 1.1.10 | 向注冊(cè)郵箱發(fā)送一封確認(rèn)郵件|---|---|---|--- |

1.2 我的注冊(cè)郵箱會(huì)收到一封驗(yàn)證郵件岩馍, 提示我點(diǎn)擊注冊(cè)連接碉咆, ?從而激活我的注冊(cè)帳戶

1.3 當(dāng)我完成激活后會(huì)自動(dòng)跳到 Checklist 的首頁, 提示我進(jìn)行登錄

實(shí)現(xiàn)

這次我們用Java實(shí)現(xiàn)蛀恩,選擇的框架是Spring Boot, 先從最笨最直接的方法入手疫铜, 之后再看看相關(guān)的框架 Spring Security 和 Apache Shiro 是怎么做的

Model

model

View

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

創(chuàng)建項(xiàng)目

spring boot starter

或者直接用 Spring Cli 直接生成

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

在實(shí)踐中始終牢記 三個(gè)基本點(diǎn)

  • 無模型不編程-MDD 模型驅(qū)動(dòng)開發(fā)
  • 無測(cè)試不開發(fā)-TDD 測(cè)試驅(qū)動(dòng)開發(fā)
  • 無度量不交付-MDD 度量驅(qū)動(dòng)開發(fā)

領(lǐng)域模型很簡(jiǎn)單
Register
User
Role

測(cè)試用例也簡(jiǎn)單

  1. 注冊(cè)
  2. 激活
  3. 登錄

度量就只記錄

  1. 注冊(cè)次數(shù)
  2. 激活次數(shù)
  3. 性能數(shù)據(jù)

代碼結(jié)構(gòu)

廢話不多說双谆,上代碼 checklist source codes on github

code structure

數(shù)據(jù)庫(kù)我們選用兩個(gè)

  1. h2 作為測(cè)試數(shù)據(jù)庫(kù)
               <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
  1. mysql 作為產(chǎn)品數(shù)據(jù)庫(kù)
                 <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.41</version>
        </dependency>

表現(xiàn)層

表現(xiàn)層選用 Freemarker 作為后端模板壳咕, 前端選用 AngularJS + BootStrap

freemarker 是比較流行的后端頁面生成的模板引擎, 這所以不用 JSP 和 JSF, 就是為了不想在后端模板層面引入太多邏輯和不必要的復(fù)雜性, 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  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>

注: 我不太擅長(zhǎng)前端頁面的界面設(shè)計(jì), 這里參考了 http://bootsnipp.com/snippets/jvgVX 的示例, 一個(gè)很有用的基于 bootstrap 的樣式主題設(shè)計(jì)網(wǎng)站

當(dāng)用戶點(diǎn)擊注冊(cè)頁面, 填寫所需字段, 并提交表單時(shí), 用 Angular JS 向后臺(tái)提交, 代碼如下

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

好了表現(xiàn)層包括前端的代碼大致搞定了, 現(xiàn)在開始寫后端的 Java web service 代碼, 參見 微服務(wù)從零開始之登錄與注冊(cè)二

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市顽馋,隨后出現(xiàn)的幾起案子谓厘,更是在濱河造成了極大的恐慌,老刑警劉巖寸谜,帶你破解...
    沈念sama閱讀 216,919評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件竟稳,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡熊痴,警方通過查閱死者的電腦和手機(jī)他爸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來愁拭,“玉大人讲逛,你說我怎么就攤上這事×氩海” “怎么了盏混?”我有些...
    開封第一講書人閱讀 163,316評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)惜论。 經(jīng)常有香客問我许赃,道長(zhǎng),這世上最難降的妖魔是什么馆类? 我笑而不...
    開封第一講書人閱讀 58,294評(píng)論 1 292
  • 正文 為了忘掉前任混聊,我火速辦了婚禮,結(jié)果婚禮上乾巧,老公的妹妹穿的比我還像新娘句喜。我一直安慰自己,他們只是感情好沟于,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,318評(píng)論 6 390
  • 文/花漫 我一把揭開白布咳胃。 她就那樣靜靜地躺著,像睡著了一般旷太。 火紅的嫁衣襯著肌膚如雪展懈。 梳的紋絲不亂的頭發(fā)上销睁,一...
    開封第一講書人閱讀 51,245評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音存崖,去河邊找鬼冻记。 笑死,一個(gè)胖子當(dāng)著我的面吹牛来惧,可吹牛的內(nèi)容都是我干的冗栗。 我是一名探鬼主播,決...
    沈念sama閱讀 40,120評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼违寞,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼贞瞒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起趁曼,我...
    開封第一講書人閱讀 38,964評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤军浆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后挡闰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體乒融,經(jīng)...
    沈念sama閱讀 45,376評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,592評(píng)論 2 333
  • 正文 我和宋清朗相戀三年摄悯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了赞季。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,764評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡奢驯,死狀恐怖申钩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情瘪阁,我是刑警寧澤撒遣,帶...
    沈念sama閱讀 35,460評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站管跺,受9級(jí)特大地震影響义黎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜豁跑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,070評(píng)論 3 327
  • 文/蒙蒙 一廉涕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧艇拍,春花似錦狐蜕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至娇哆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背碍讨。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工治力, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人勃黍。 一個(gè)月前我還...
    沈念sama閱讀 47,819評(píng)論 2 370
  • 正文 我出身青樓宵统,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親覆获。 傳聞我的和親對(duì)象是個(gè)殘疾皇子马澈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,665評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)弄息,斷路器痊班,智...
    卡卡羅2017閱讀 134,654評(píng)論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,806評(píng)論 6 342
  • 書接上文 微服務(wù)從零開始之登錄與注冊(cè)一, 表現(xiàn)層及前端代碼搞定, 現(xiàn)在開始來設(shè)計(jì)和實(shí)現(xiàn)后端的 Java Web S...
    老瓦在霸都閱讀 3,359評(píng)論 1 2
  • 這些屬性是否生效取決于對(duì)應(yīng)的組件是否聲明為 Spring 應(yīng)用程序上下文里的 Bean(基本是自動(dòng)配置的),為一個(gè)...
    發(fā)光的魚閱讀 1,424評(píng)論 0 14
  • 甲骨文里摹量,不/丕同源涤伐,是植物的根部: 后來分化成兩字,不表否定缨称,丕則保留原意凝果,有“大”義,因?yàn)楦?..
    ChanKun閱讀 363評(píng)論 0 0