/*
* <p>
* 版权: ©2011
* </p>
*/
package org.young.isocket.filter;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.glassfish.grizzly.Connection;
import org.glassfish.grizzly.filterchain.BaseFilter;
import org.glassfish.grizzly.filterchain.FilterChainContext;
import org.glassfish.grizzly.filterchain.NextAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.young.isocket.service.ServiceRequest;
import org.young.isocket.service.ServiceResponse;
import org.young.isocket.util.SocketKeys;
/**
* <p>
*
* </p>
*
* @see
* @author yangjun2
* @email yangjun1120@gmail.com
*
*/
public class ServerAuthFilter extends BaseFilter {
private static final Logger logger = LoggerFactory.getLogger(ServerAuthFilter.class);
// Authenticated clients connection map
private Map<Connection, String> authenticatedConnections = new ConcurrentHashMap<Connection, String>();
// Random, to generate client ids.
// private final Random random = new Random();
/**
* The method is called once we have received {@link MultiLinePacket} from
* a client.
* Filter check if incoming message is the client authentication request.
* If yes - we generate new client id and send it back in the
* authentication response. If the message is not authentication request -
* we check message authentication header to correspond to a connection id
* in the authenticated clients map. If it's ok - the filter removes
* authentication header from the message and pass the message to a next
* filter in a filter chain, otherwise, if authentication failed - the filter
* throws an Exception
*
* @param ctx Request processing context
*
* @return {@link NextAction}
* @throws IOException
*/
@Override
public NextAction handleRead(FilterChainContext ctx) throws IOException {
// Get the connection
final Connection connection = ctx.getConnection();
// Get the incoming packet
final ServiceRequest sourceRequest = (ServiceRequest) ctx.getMessage();
// get the command string
// check if it's authentication request from a client
if (sourceRequest.getServiceId().equals(SocketKeys.SERVICE_ID_AUTH)) {
//check Service Id
// if (!sourceRequest.getServiceId().equals(SocketKeys.SERVICE_ID_AUTH)) {
// return handleAuthFail(ctx, sourceRequest, SocketKeys.MESSAGE_SERVICE_ERROR,
// sourceRequest.getServiceId());
// }
return ctx.getInvokeAction();
} else {
// if it's some custom message
// Get id line
final String sessionId = sourceRequest.getSessionId();
// Check the client id
if (checkAuth(connection, sessionId)) {
// if id corresponds to what server has -
// Remove authentication header
sourceRequest.setSessionId(null);
// Pass to a next filter
return ctx.getInvokeAction();
} else {
return handleAuthFail(ctx, sourceRequest, SocketKeys.MESSAGE_AUTH_ERROR);
}
}
}
private NextAction handleAuthFail(FilterChainContext ctx, ServiceRequest sourceRequest, String errMsg,
Object... objects) throws IOException {
ServiceResponse serviceResponse = new ServiceResponse();
serviceResponse.setResponseCode(SocketKeys.RESPONSE_CODE_AUTHENTICATIONERROR);
serviceResponse.setResponseMessage(String.format(errMsg, objects));
serviceResponse.setTransformType(sourceRequest.getTransformType());
//serviceResponse.setAuth(sourceRequest.isAuth());
serviceResponse.setId(sourceRequest.getId());
serviceResponse.setSessionId(sourceRequest.getSessionId());
serviceResponse.setServiceId(sourceRequest.getServiceId());
ctx.write(serviceResponse);
// stop the packet processing
return ctx.getStopAction();
}
/**
* The method is called each time, when server sends a message to a client.
* First of all filter check if this packet is not authentication-response.
* If yes - filter just passes control to a next filter in a chain, if not -
* filter gets the client id from its local authenticated clients map and
* adds "auth-id: <connection-id>" header to the outgoing message and
* finally passes control to a next filter in a chain.
*
* @param ctx Response processing context
*
* @return {@link NextAction}
* @throws IOException
*/
@Override
public NextAction handleWrite(FilterChainContext ctx) throws IOException {
// Get the connection
final Connection connection = ctx.getConnection();
// Get the sending packet
final ServiceResponse sourceResponse = (ServiceResponse) ctx.getMessage();
// if it's authentication-response
if (sourceResponse.getServiceId().equals(SocketKeys.SERVICE_ID_AUTH)) {
// just pass control to a next filter in a chain
if (sourceResponse.getResponseCode() == SocketKeys.RESPONSE_CODE_SUCCESS) {
String sessionId = sourceResponse.getResponseObject();
authenticatedConnections.put(connection, sessionId);
sourceResponse.setSessionId(sessionId);
} else {
//modify error code
sourceResponse.setResponseCode(SocketKeys.RESPONSE_CODE_AUTHENTICATIONERROR);
}
return ctx.getInvokeAction();
} else {
// if not - get connection id from authenticated connections map
final String sessionId = authenticatedConnections.get(connection);
if (sessionId != null) {
// if id exists - add "auth-id" header to a packet
sourceResponse.setSessionId(sessionId);
// pass control to a next filter in a chain
// return ctx.getInvokeAction();
} else {
sourceResponse.setResponseCode(SocketKeys.RESPONSE_CODE_AUTHENTICATIONERROR);
sourceResponse.setResponseMessage(String.format(SocketKeys.MESSAGE_AUTH_ERROR, new Object[] {
sourceResponse.getId(), sourceResponse.getServiceId(), sourceResponse.getResponseMessage() }));
}
return ctx.getInvokeAction();
}
}
/**
* Method checks, whether authentication header, sent in the message corresponds
* to a value, stored in the server authentication map.
*
* @param connection {@link Connection}
* @param idLine authentication header string.
*
* @return <tt>true</tt>, if authentication passed, or <tt>false</tt> otherwise.
*/
private boolean checkAuth(Connection connection, String sessionId) {
if (sessionId == null)
return false;
// Get the connection id, from the server map
final String registeredId = authenticatedConnections.get(connection);
if (registeredId == null)
return false;
return sessionId.equals(registeredId);
}
/**
* The method is called, when a connection gets closed.
* We remove connection entry in authenticated connections map.
*
* @param ctx Request processing context
*
* @return {@link NextAction}
* @throws IOException
*/
@Override
public NextAction handleClose(FilterChainContext ctx) throws IOException {
authenticatedConnections.remove(ctx.getConnection());
return ctx.getInvokeAction();
}
}