在学习了极客时间的设计模式之美后,发现了自己由于长时间写curd,导致思维都变得面向过程了。
记录一下整体设计的思路。
要求
关于鉴权系统要求如下:
调用方进行接口请求的时候,将 URL、AppID、密码、时间戳拼接在一起,通过加密算
法生成 token,并且将 token、AppID、时间戳拼接在 URL 中,一并发送到微服务端。微服务端在接收到调用方的接口请求之后,从请求中拆解出 token、AppID、时间戳。
微服务端首先检查传递过来的时间戳跟当前时间,是否在 token 失效时间窗口内。如果
已经超过失效时间,那就算接口调用鉴权失败,拒绝接口调用请求。如果 token 验证没有过期失效,微服务端再从自己的存储中,取出 AppID 对应的密
码,通过同样的 token 生成算法,生成另外一个 token,与调用方传递过来的 token 进
行匹配。如果一致,则鉴权成功,允许接口调用;否则就拒绝接口调用。
分析和设计
首先,我们需要分析涉及到的功能:
- 将 URL、AppID、密码、时间戳拼接在一起,通过加密算法生成 token
- 将 token、AppID、时间戳拼接在 URL,形成新的URL
- 从请求中拆解出 token、AppID、时间戳
- 检查token是否失效
- 从存储中取出 AppID 对应的密码
- 进行token的验证比较
那么接下来分析涉及到的操作对象主要有三个:
-
token
1 4 6
-
url
2 3
-
storage(存储)
5
那么接下来可以进行相关功能的设计了。
在设计期间,或者直接写代码期间,我的思维是:搭建框架的过程中思考实现
Token设计
token类,可以这样:
public class AuthToken {
public static String generateToken(String url, String appId,
String password, Long timestamp);
public boolean isExpire();
public boolean match(String token);
}
好的,确实,我们涵盖了所有的功能,但是怎么判断是否过期呢?
显然我们需要一个timestamp
变量来记录当前token
的时间戳。并且提供一个默认多长时间过期,来判断是否过期了。
public class AuthToken {
private Long timestamp;
// 默认过期时间,一分钟
private static final long DEFAULT_EXPIRED_TIMESTAMP = TimeUnit.MINUTES.toSeconds(1);
public static String generateToken(String url, String appId,
String password, Long timestamp);
public boolean isExpire();
public boolean match(String token);
}
此时如果直接generateToken
返回String
好像就不太行了,因为我们的token
现在包含了除了主体string
之外的东西。
改。
public class AuthToken {
private Long timestamp;
private String token;
public static AuthToken generateToken(String url, String appId,
String password, Long timestamp);
public boolean isExpire();
public boolean match(AuthToken token);
public String getToken();
}
具体实现应该就比较简单了,
Url设计
public class URL {
private String baseUrl;
private String token;
private String appId;
private Long timestamp;
/**
* 根据参数拼接为url
*/
public static String generateUrl(String baseUrl, String token,
String appId, Long timestamp);
/**
* 根据url构建URL对象,相当于进行URL的解析
* @param url url
* @return 构建后的对象
*/
public static URL createFromUrl(String url);
public String getBaseUrl() {
return baseUrl;
}
public String getToken() {
return token;
}
public String getAppId() {
return appId;
}
public Long getTimestamp() {
return timestamp;
}
}
Stroage设计
public interface Storage {
/**
* 通过appId获得对应的密码
* @param appId appId
* @return 获取到的密码,如果找不到返回null
*/
public String getPasswordByAppId(String appId);
}
最终提供给其他人调用的第三方调用入口是这样的:
public interface Auth {
/**
* 根据url进行权限验证
* @param url
* @return
*/
public void auth(String url);
/**
* 根据apiRequest进行权限验证
* @param url
* @return
*/
public void auth(ApiRequest url);
}
可以提供默认的实现:
public class DefaultAuth implements Auth{
private Storage storage;
public DefaultAuth() {
storage = new MySqlStorage();
}
public DefaultAuth(Storage storage) {
this.storage = storage;
}
@Override
public void auth(String url) {
// 从url构建对应的request对象
URL url= URL.createFromUrl(url);
auth(url);
}
@Override
public void auth(URL url) {
// 构建token对象
AuthToken authToken = new AuthToken(url.getToken(), url.getTimestamp());
// 检查是否过期
if (authToken.isExpired()) {
throw new TokenExpiredRuntimeException();
}
// 根据appid去数据库拿到对应的密码
String password = storage.getPasswordByAppId(url.getToken());
// 重新构建服务器端的token
AuthToken token = AuthToken.create(url.getBaseUrl(), url.getAppId(),
password, url.getTimestamp());
// 比较两个token是否相同
if (authToken.match(token)) {
throw new TokenNotMatchRuntimeException();
}
}
}