背景描述
做app后端服務(wù)的coder都知道,很多服務(wù)都是無狀態(tài)的,所謂的無狀態(tài),在這里我們可以簡(jiǎn)單的理解為(與傳統(tǒng)web不同點(diǎn))沒有session。
那么這個(gè)時(shí)候怎么保證我們的請(qǐng)求安全性呢(這里我們所提到的安全性即用戶請(qǐng)求來源和判斷的安全性圆丹,不涉及驗(yàn)簽只估,加密等數(shù)據(jù)來源之類的安全)。
需求描述
我們要實(shí)現(xiàn)一個(gè)基于token令牌的請(qǐng)求攔截系統(tǒng)捏鱼,針對(duì)我們的每一次的請(qǐng)求進(jìn)行攔截蕾额。以此來判斷用戶數(shù)據(jù)的完整性崭庸,安全性梳玫。
那么我們的攔截系統(tǒng)是什么樣子的呢谍珊?
首先我們按照auth2.0的協(xié)議進(jìn)行一個(gè)偽auth2.0設(shè)計(jì)一個(gè)權(quán)限分發(fā)驗(yàn)證的模式(因?yàn)槲艺J(rèn)為規(guī)則是人制訂的治宣,沒必要所有的事都按照規(guī)則進(jìn)行,方便即可)
那么我們的token鑒權(quán)是什么樣的過程呢砌滞?
- 客戶端登錄->server
- 登錄成功后頒發(fā)token令牌給客戶端
- 客戶端每次請(qǐng)求時(shí)候攜帶token
- server端針對(duì)需要鑒權(quán)的請(qǐng)求進(jìn)行攔截
- server端對(duì)token進(jìn)行鑒權(quán)
- 鑒權(quán)成功的話進(jìn)行請(qǐng)求分發(fā)(既侮邀,繼續(xù)請(qǐng)求),鑒權(quán)失敗的話把請(qǐng)求攔截下來返回相應(yīng)的失敗狀態(tài)碼
關(guān)鍵點(diǎn)分析
整個(gè)過程中最重要的是3點(diǎn):
- token的生成算法
- token的驗(yàn)證方式
- 針對(duì)需要鑒權(quán)的請(qǐng)求進(jìn)行攔截
今天我們要做的就是第三點(diǎn)贝润,如何實(shí)現(xiàn)針對(duì)需要鑒權(quán)的請(qǐng)求進(jìn)行攔截
針對(duì)需要鑒權(quán)的請(qǐng)求進(jìn)行攔截的實(shí)現(xiàn)過程
整個(gè)攔截的過程中主要有這么兩個(gè)點(diǎn):
- Server filters(服務(wù)器的過濾器)
- Dynamic binding(動(dòng)態(tài)的過濾器攔截器綁定)
Server filters簡(jiǎn)述
Server filters主要是一個(gè)什么作用呢绊茧,這我們這里,他主要是負(fù)責(zé)兩個(gè)地方:
- 攔截請(qǐng)求
- 業(yè)務(wù)內(nèi)對(duì)token鑒權(quán)
整個(gè)這一塊的代碼如下
public class AuthorizationFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
//獲取客戶端Header中提交的token
String token = requestContext.getHeaderString("Authorization");
if (StringUtil.isEmpty(token)) {
// TODO:攔截響應(yīng)
}
//判斷該用戶是否已登陸
User user = TokenUtils.sign(token);
if (user == null) {
// TODO:攔截響應(yīng)
}
}
}
那么大家可能注意到我加了todo的地方打掘,因?yàn)檫@些都是需要把請(qǐng)求攔截下來的地方按傅,那么我們?cè)趺磳?shí)現(xiàn)吧這個(gè)請(qǐng)求攔截下來呢
我們采用了拋異常的方式,具體做法如下:
首先我們先聲明一個(gè)auth驗(yàn)證失敗的異常
public class AuthorizationException extends RuntimeException {
private String response = ErrorCode.NOT_AUTHED.getMsg();
public String getResponse(){
return this.response;
}
}
然后在全局捕獲這個(gè)異常并輸出提示信息
@Provider
public class AuthExceptionMapper implements ExceptionMapper<AuthorizationException> {
@Override
public Response toResponse(AuthorizationException exception) {
return Response.ok(exception.getResponse()).build();
}
}
Dynamic binding
在上面我們看到了如何對(duì)一個(gè)請(qǐng)求進(jìn)行攔截胧卤,并進(jìn)行鑒權(quán)驗(yàn)證唯绍,包括異常處理。
那么有個(gè)關(guān)鍵性的問題來了枝誊,我們?cè)撊绾螖r截需要鑒權(quán)的請(qǐng)求况芒?
例如我登錄的請(qǐng)求是不需要攔截的,獲取個(gè)人信息的請(qǐng)求是需要攔截的
我怎么能做到上面的效果(答:Dynamic binding)
那么我們看一下什么是Dynamic binding叶撒?
讓我們來看一下官方的解釋
動(dòng)態(tài)綁定就是一個(gè)以動(dòng)態(tài)的方式分配資源方法的篩選器和攔截器绝骚。
那么我們?cè)趺蠢?strong>Dynamic binding來實(shí)現(xiàn)我們的區(qū)分?jǐn)r截的功能呢
具體的需求描述
我想實(shí)現(xiàn)這么樣的一個(gè)功能,通過一個(gè)注解祠够,我只需要把這個(gè)注解標(biāo)注在類或者方法上就能攔截這個(gè)對(duì)應(yīng)的類或者方法
那么我怎么實(shí)現(xiàn)這個(gè)動(dòng)態(tài)的過濾綁定功能呢压汪?
首先,我們聲明一個(gè)自定義的注解古瓤,命名為AuthAnnotation
然后我們亮出我們的實(shí)現(xiàn)辦法
@Provider
public class AuthorizationFilterFeature implements DynamicFeature {
@Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
List<Annotation> authzSpecs = new ArrayList<>();
Annotation classAuthzSpec =
resourceInfo.getResourceClass().getAnnotation(AuthAnnotation.class);
Annotation methodAuthzSpec =
resourceInfo.getResourceMethod().getAnnotation(AuthAnnotation.class);
if (classAuthzSpec != null)
authzSpecs.add(classAuthzSpec);
if (methodAuthzSpec != null)
authzSpecs.add(methodAuthzSpec);
if (!authzSpecs.isEmpty()) {
// 需要攔截的api
context.register(AuthorizationFilter.class);
}
}
}