/src/main/java/com/linbox/im/server/connector/tcp/handler/AuthHandler.java
Java | 264 lines | 203 code | 56 blank | 5 comment | 21 complexity | b0a1feb74021439305cc871e7ac30868 MD5 | raw file
1package com.linbox.im.server.connector.tcp.handler;
2
3import com.alibaba.fastjson.JSON;
4import com.linbox.im.message.*;
5import com.linbox.im.server.connector.tcp.constant.HandlerName;
6import com.linbox.im.server.service.IOutboxService;
7import com.linbox.im.server.storage.dao.IServerDAO;
8import com.linbox.im.server.storage.dao.IUserDAO;
9import io.netty.channel.Channel;
10import io.netty.channel.ChannelFuture;
11import io.netty.channel.ChannelHandlerContext;
12import io.netty.channel.ChannelInboundHandlerAdapter;
13import io.netty.util.concurrent.Future;
14import io.netty.util.concurrent.GenericFutureListener;
15import org.apache.commons.lang.StringUtils;
16import org.slf4j.Logger;
17import org.slf4j.LoggerFactory;
18import org.springframework.context.support.ClassPathXmlApplicationContext;
19
20import java.net.InetSocketAddress;
21import java.util.Base64;
22import java.util.concurrent.ScheduledExecutorService;
23import java.util.concurrent.ScheduledFuture;
24import java.util.concurrent.TimeUnit;
25
26/**
27 * Created by lrsec on 6/28/15.
28 */
29public class AuthHandler extends ChannelInboundHandlerAdapter {
30
31 private static Logger logger = LoggerFactory.getLogger(AuthHandler.class);
32
33 private ScheduledExecutorService executor;
34 private int loopRatio = 500;
35 private int maxHandleTimeInMills = 5 * 1000;
36 private ScheduledFuture task;
37
38 private long rId = -1;
39 private String userId = null;
40 private AES aes = null;
41 private Base64.Encoder base64Encoder = null;
42
43 private IOutboxService outboxService;
44 private IUserDAO userDAO;
45 private IServerDAO serverDAO;
46
47 public AuthHandler(ClassPathXmlApplicationContext applicationContext, ScheduledExecutorService executor, int loopRatio, int maxHandleTimeInMills, AES aes) {
48 this.executor = executor;
49 this.loopRatio = loopRatio;
50 this.maxHandleTimeInMills = maxHandleTimeInMills;
51 this.aes = aes;
52 this.outboxService = (IOutboxService)applicationContext.getBean("outboxService");
53 this.userDAO = (IUserDAO) applicationContext.getBean("userDAO");
54 this.serverDAO = (IServerDAO) applicationContext.getBean("serverDAO");
55 this.base64Encoder = Base64.getEncoder();
56 }
57
58 @Override
59 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
60 logger.error("Exception in AuthHandler. Close the connection for user: " + StringUtils.trimToEmpty(userId), cause);
61 if (rId >= 0) {
62 sendFailResponse(ctx, 500, cause.getMessage());
63 }
64
65 ctx.close();
66 }
67
68 @Override
69 public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
70 logger.debug("Received AuthRequest. Message: {}", JSON.toJSONString(msg));
71
72 MessageWrapper wrapper = (MessageWrapper) msg;
73
74 if (wrapper == null) {
75 logger.error("Get an null message wrapper in message handler");
76 ctx.close();
77 return;
78 }
79
80 RequestResponseType type = wrapper.type;
81
82 if (type == null || type != RequestResponseType.AuthRequestMsg) {
83 logger.error("The first message from client: {} is not AuthRequest. Close the connection. Message type is {}", ctx.channel().remoteAddress(), msg.getClass().getName());
84 ctx.close();
85 return;
86 }
87
88 AuthRequest authRequest = (AuthRequest) wrapper.content;
89 rId = authRequest.rId;
90 boolean authenticated = isCertified(authRequest);
91
92 if (authenticated) {
93 logger.debug("User {} authenticated.", userId);
94
95 InetSocketAddress address = (InetSocketAddress)ctx.channel().localAddress();
96 String addressRecord = address.toString() + Long.toString(System.currentTimeMillis());
97 serverDAO.registerConnection(userId, addressRecord);
98 logger.debug("Create connection for user: {}. Remote address: {}.", userId, ctx.channel().remoteAddress());
99
100 //TODO 动态密码的生成策略,在测试时关闭
101// resetPassword(ctx);
102
103 task = executor.scheduleAtFixedRate(new SendMessageChecker(ctx.channel(), addressRecord), 0, loopRatio, TimeUnit.MILLISECONDS);
104
105 IMMessageHandler imMsgHandler = (IMMessageHandler) ctx.pipeline().get(HandlerName.HANDLER_MSG);
106 imMsgHandler.setUserId(userId);
107 IMIdleStateHandler imIdleStateHandler = (IMIdleStateHandler) ctx.pipeline().get(HandlerName.HANDLER_IDLE);
108 imIdleStateHandler.setUserId(userId);
109
110 ctx.pipeline().remove(this);
111
112 sendSuccessResponse(ctx);
113
114 return;
115 } else {
116 logger.error("User {} is not certified. Close the connection.", userId);
117 sendFailResponse(ctx, 401, "User is not certified");
118
119 ctx.close();
120 return;
121 }
122 }
123
124 private boolean isCertified(AuthRequest request) {
125 userId = request.userId;
126 return userDAO.isUserValid(request.userId, request.token);
127 }
128
129 private void resetPassword(final ChannelHandlerContext ctx) {
130 String password = serverDAO.getPassword(Long.parseLong(userId));
131
132 if (StringUtils.isBlank(password)) {
133 logger.error("Can not get im password for user: {}", userId);
134 sendFailResponse(ctx, 400, "Can not find user im password");
135 return;
136 } else {
137 aes.resetPassword(password);
138 }
139 }
140
141 private class SendMessageChecker implements Runnable {
142 private Logger logger = LoggerFactory.getLogger(SendMessageChecker.class);
143
144 private volatile boolean isConnectionClosed = false;
145 private Channel ch;
146 private String linkRecord;
147
148 public SendMessageChecker(Channel ch, String linkRecord) {
149 this.ch = ch;
150 this.linkRecord = linkRecord;
151 }
152
153 public void run() {
154 long start = System.currentTimeMillis();
155 while ((System.currentTimeMillis() - start <= maxHandleTimeInMills)) {
156 try {
157 if(shouldClose()) {
158 terminate();
159 return;
160 }
161
162 final String msg = outboxService.get(userId);
163
164 if (StringUtils.isBlank(msg)) {
165 return;
166 }
167
168 final MessageWrapper wrapper = JSON.parseObject(msg, MessageWrapper.class);
169
170 ChannelFuture future = ch.writeAndFlush(wrapper);
171
172 logger.debug("Tcp sender send message for {}. Message type: {}. Message body: {}", userId, wrapper.type, msg);
173
174 future.addListener(new GenericFutureListener() {
175 public void operationComplete(Future future) throws Exception {
176 if (!future.isSuccess()) {
177 logger.info("Network I/O write fail. Should close connection for user {}.", userId);
178 isConnectionClosed = true;
179
180 Throwable t = future.cause();
181 if (t != null) {
182 logger.info("Send message fail", t);
183 }
184 } else {
185 logger.debug("Tcp sender send message success. user: {}. Message type: {}. Message body: {}", userId, wrapper.type, msg);
186 }
187 }
188 });
189
190 } catch (Exception e) {
191 logger.error("Exception in sending task for user: " + userId + ". But the sending loop will continue to work", e);
192 return;
193 }
194 }
195 }
196
197 private boolean shouldClose() {
198 if (!ch.isActive()) {
199 logger.warn("Connection for user {} is inactive, should terminate the sending task.", userId);
200 return true;
201 }
202
203 if(isConnectionClosed) {
204 logger.warn("Connection for user {} is closed, should terminate the sending task.", userId);
205 return true;
206 }
207
208 String record = serverDAO.getConnection(userId);
209
210 if(!StringUtils.equals(record, linkRecord)) {
211 logger.warn("Connection is updated, should terminate the sending task for user {}. Local address {}. New connection address: {}", userId, StringUtils.trimToEmpty(linkRecord), StringUtils.trimToEmpty(record));
212
213 sendOfflineInfo();
214 return true;
215 }
216
217 return false;
218 }
219
220 private void terminate() {
221 logger.info("Terminate sending task for user {}", userId);
222
223 task.cancel(false);
224
225 try {
226 ch.close();
227 } catch (Exception e) {
228 logger.error("Exception when terminate sending task", e);
229 }
230 }
231
232 private void sendOfflineInfo() {
233 OfflineInfo offlineInfo = new OfflineInfo();
234 offlineInfo.userId = userId;
235
236 MessageWrapper wrapper = offlineInfo.toWrapper();
237 ch.writeAndFlush(wrapper);
238 }
239 }
240
241 private void sendSuccessResponse(ChannelHandlerContext ctx) {
242 AuthResponse response = new AuthResponse();
243 response.rId = rId;
244 response.userId = userId;
245 response.status = 200;
246 response.sendTime = System.currentTimeMillis();
247
248 MessageWrapper responseWrapper = response.toWrapper();
249 ctx.channel().writeAndFlush(responseWrapper);
250 }
251
252 private void sendFailResponse(ChannelHandlerContext ctx, int status, String errCode) {
253
254 AuthResponse response = new AuthResponse();
255 response.rId = rId;
256 response.userId = userId;
257 response.status = status;
258 response.errMsg = errCode;
259 response.sendTime = System.currentTimeMillis();
260
261 MessageWrapper responseWrapper = response.toWrapper();
262 ctx.channel().writeAndFlush(responseWrapper);
263 }
264}