【发布时间】:2014-07-28 09:41:25
【问题描述】:
我试图通过使其中一种方法需要 HTTP 基本身份验证来确保我的 Web 服务安全。为了做到这一点,我实现了一个名为 LoginInterceptor 的自定义拦截器,它检查请求的 URL,如果它对应于一个名为 open 的方法,它会检查消息头中的用户名和密码。
如果标头中没有认证字段,则响应码设置为HTTP_UNAUTHORIZED,如果用户名或密码不正确,则响应码设置为HTTP_FORBIDDEN。代码如下:
public LoginInterceptor() {
super(Phase.RECEIVE);
addAfter(RequestInterceptor.class.getName()); //another custom interceptor, for some simple redirections.
}
public void handleMessage(Message message) throws Fault {
String requestURI = message.get(Message.REQUEST_URI).toString();
String methodKeyword = requestURI.substring("localhost".length()+1).split("/")[0];
if(methodKeyword.equals("open")) {
AuthorizationPolicy policy = message.get(AuthorizationPolicy.class);
if(policy == null) {
sendErrorResponse(message, HttpURLConnection.HTTP_UNAUTHORIZED);
return;
}
//userPasswords is a hashmap of usernames and passwords.
String realPassword = userPasswords.get(policy.getUserName());
if (realPassword == null || !realPassword.equals(policy.getPassword())) {
sendErrorResponse(message, HttpURLConnection.HTTP_FORBIDDEN);
}
}
}
//This is where the response code is set, and this is where I'd like to write the response message.
private void sendErrorResponse(Message message, int responseCode) {
Message outMessage = getOutMessage(message);
outMessage.put(Message.RESPONSE_CODE, responseCode);
// Set the response headers
Map responseHeaders = (Map) message.get(Message.PROTOCOL_HEADERS);
if (responseHeaders != null) {
responseHeaders.put("Content-Type", Arrays.asList("text/html"));
responseHeaders.put("Content-Length", Arrays.asList(String.valueOf("0")));
}
message.getInterceptorChain().abort();
try {
getConduit(message).prepare(outMessage);
close(outMessage);
} catch (IOException e) {
e.printStackTrace();
}
}
private Message getOutMessage(Message inMessage) {
Exchange exchange = inMessage.getExchange();
Message outMessage = exchange.getOutMessage();
if (outMessage == null) {
Endpoint endpoint = exchange.get(Endpoint.class);
outMessage = endpoint.getBinding().createMessage();
exchange.setOutMessage(outMessage);
}
outMessage.putAll(inMessage);
return outMessage;
}
//Not actually sure what this does. Copied from a tutorial online. Any explanation is welcome
private Conduit getConduit(Message inMessage) throws IOException {
Exchange exchange = inMessage.getExchange();
Conduit conduit = exchange.getDestination().getBackChannel(inMessage);
exchange.setConduit(conduit);
return conduit;
}
private void close(Message outMessage) throws IOException {
OutputStream os = outMessage.getContent(OutputStream.class);
os.flush();
os.close();
}
这很好用,但是,我还想在响应中返回一条消息,例如“用户名或密码不正确”。我已经尝试过,从 sendErrorResponse 方法中,做:
outMessage.setContent(String.class, "incorrect username or password")
我将内容长度设置为"incorrect username or password".length()。这不起作用,我猜是因为 Apache CXF 消息使用InputStreams 和OutputStreams。
所以我尝试了:
OutputStream os = outMessage.getContent(OutputStream.class);
try {
os.write("incorrect username or password".getBytes() );
outMessage.setContent(OutputStream.class, os);
} catch (IOException e) {
e.printStackTrace();
}
这也不起作用。使用调试器单步执行时,我注意到 os 是 null 使用 Postman 进行测试时,我得到:
无法得到任何响应这似乎是连接错误 到
http://localhost:9090/launcher/open。响应状态为 0。 查看 W3C XMLHttpRequest Level 2 规范了解更多关于 当这种情况发生时。
在 Chrome 中按 ctrl+shif+c(打开开发工具),然后检查网络选项卡,我看到了:
"ERR_CONTENT_LENGTH_MISMATCH"
我尝试过使用 XMLStreamWriter,但效果并不好。
问题:
我可以返回正确的响应代码(401 未授权和 403 禁止),但是如何在响应正文中返回消息?
我是否需要专门扩展特定的 OutInterceptor(如 JASRXOutInterceptor)才能修改消息内容?
我之前尝试过使用 JAASInterceptor,但没能成功。有人可以告诉我如何以这种方式实现它,如果这更容易吗?
我也可以像这样抛出错误:
throw new Fault("incorrect username or password", Logger.getGlobal());,但 HTTP 响应代码将是 500。我更愿意返回正确的 401 或 403 响应。
注意:
现在我仍在使用 HTTP 作为传输层。解决此问题后,我将更改为 HTTPS。
【问题讨论】:
标签: java web-services cxf jax-rs