我按照本教程使用Java EE设置websocket端点:
出于显而易见的原因,还有一些关于安全性的工作要做(例如,没有SSL和访问限制/身份验证)。
所以我的目标是通过
改进websocket安全性我的问题:如何验证我在ServerEndpoint的LoginBean中创建的令牌?
奖金问题:我是否遗漏了在Java EE中保护websockets的一些重要部分?
这是我到目前为止所做的:
ServerEndpoint
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint("/user/endpoint/{token}")
public class ThisIsTheSecuredEndpoint {
@OnOpen
public void onOpen(@PathParam("token") String incomingToken,
Session session) throws IOException {
//How can i check if the token is valid?
}
}
LoginBean
@ManagedBean
@SessionScoped
public class LoginBean {
public String login() {
FacesContext facesContext = FacesContext.getCurrentInstance();
HttpServletRequest request = (HttpServletRequest)facesContext.getExternalContext().getRequest();
try {
request.login("userID", "password");
HttpSession session = request.getSession();
// here we put the token in the session
session.setAttribute("token", "someVeeeeryLongRandomValue123hfgrtwpqllkiw");
} catch (ServletException e) {
facesContext.addMessage(null, new FacesMessage("Login failed."));
return "error";
}
return "home";
}
}
javascipt的
这是我想用来连接websocket的代码:
// use SSL
// retrive the token from session via EL-expression #{session.getAttribute("token")}
var wsUri = "wss://someHost.com/user/endpoint/#{session.getAttribute("token")}";
var websocket = new WebSocket(wsUri);
websocket.onerror = function(evt) { onError(evt) };
function onError(evt) {
writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);
}
// For testing purposes
var output = document.getElementById("output");
websocket.onopen = function(evt) { onOpen(evt) };
function writeToScreen(message) {
output.innerHTML += message + "<br>";
}
function onOpen() {
writeToScreen("Connected to " + wsUri);
}
网络的XML:
保护&#34; / user / *&#34;登录目录并强制执行SSL通信
<security-constraint>
...
<web-resource-name>Secured Area</web-resource-name>
<url-pattern>pathToSecuredDicrtoy</url-pattern>
...
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
...
</security-constraint>
<login-config>
<auth-method>FORM</auth-method> ...
</login-config>
注意:我正在使用JSF
任何反馈意见都将受到高度赞赏。
答案 0 :(得分:4)
您可以使用Servlet过滤器进行身份验证。
这是我之前创建的用于保护聊天端点的过滤器的示例。它从名为access-token
的查询参数中提取访问令牌,并将令牌验证委托给名为Authenticator
的bean。
您可以轻松地根据自己的需求进行调整:
/**
* Access token filter for the chat websocket. Requests without a valid access token
* are refused with a <code>403</code>.
*
* @author cassiomolin
*/
@WebFilter("/chat/*")
public class AccessTokenFilter implements Filter {
@Inject
private Authenticator authenticator;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
// Extract access token from the request
String token = request.getParameter("access-token");
if (token == null || token.trim().isEmpty()) {
returnForbiddenError(response, "An access token is required to connect");
return;
}
// Validate the token and get the user who the token has been issued for
Optional<String> optionalUsername = authenticator.getUsernameFromToken(token);
if (optionalUsername.isPresent()) {
filterChain.doFilter(
new AuthenticatedRequest(
request, optionalUsername.get()), servletResponse);
} else {
returnForbiddenError(response, "Invalid access token");
}
}
private void returnForbiddenError(HttpServletResponse response, String message)
throws IOException {
response.sendError(HttpServletResponse.SC_FORBIDDEN, message);
}
@Override
public void destroy() {
}
/**
* Wrapper for a {@link HttpServletRequest} which decorates a
* {@link HttpServletRequest} by adding a {@link Principal} to it.
*
* @author cassiomolin
*/
private static class AuthenticatedRequest extends HttpServletRequestWrapper {
private String username;
public AuthenticatedRequest(HttpServletRequest request, String username) {
super(request);
this.username = username;
}
@Override
public Principal getUserPrincipal() {
return () -> username;
}
}
}
聊天端点类似于:
@ServerEndpoint("/chat")
public class ChatEndpoint {
private static final Set<Session> sessions =
Collections.synchronizedSet(new HashSet<>());
@OnOpen
public void onOpen(Session session) {
sessions.add(session);
String username = session.getUserPrincipal().getName();
welcomeUser(session, username);
}
...
}
该应用程序可用here。