�概述
任何一个微服务需要基本的安全保证, 也是遵循AAA原则: 鉴证,授权和计帐
- Authentication 要求是合法用户
- Authorization 要求有合法权限
- Accounting 要求有记录可追踪
让我们从需求分析到代码实现,尽量啰嗦地来说说怎么做一个看似简单的登录注册模块, 假设该服务叫做 Checklist 我的清单
需求分析
用例 Use case
除了使用绘图工具和画用例图, 还有有�几种方法通过脚本来�生成用例图
一是使用在线网站 yuml.me
https://yuml.me/608ca377
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 注册
- 作为一个未注册用户, 我想输入我的电子邮件地址和密码,注册到 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
View
字段 | �控件 |
---|---|
username | text |
password | password |
confirmPassword | password |
rememberMe | checkbox |
forgetPassword | link |
创建项目
- 在 http://start.spring.io 上选择所需模块, 创建 Checklist 项目并打包下载
或者直接用 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
测试用例也简单
- 注册
- 激活
- 登录
度量就只记录
- 注册次数
- 激活次数
- 性能数据
代码结构
废话不多说,上代码 checklist source codes on github
数据库我们选用两个
- h2 作为测试数据库
com.h2database
h2
runtime
- mysql 作为产品数据库
mysql
mysql-connector-java
5.1.41
表现层
表现层选用 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>
Check List
<#include "header.ftl"/>
<#nested/>
<#include "footer.ftl"/>
#macro>
首页
源码 index.ftl 如下
<#import "layout.ftl" as layout>
<@layout.myLayout>
@layout.myLayout>
登录页面
源码 login.ftl 如下
<#import "layout.ftl" as layout>
<@layout.myLayout>
@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 代码, 参见 微服务从零开始之登录与注册二