你好,未来!上个周末过的还行,逛街逛到腿发软,生活还是要有仪式感,一生要待自己待你最亲近的人。周一休息,顺便看了看04版天龙八部,塑造了三位英雄人物,共同点:热血男儿,助人为乐,乔峰大侠气概,段誉风流倜傥,虚竹严于律己,久久思考金庸在写的时候,思想与灵魂是何尝不是伟大,让我也陷入了沉思,久久不能自拔。
前言总会自己乱说一通,然后才能进入主题一些相关而有不相关的东西,都能说一些,可能一个人久了,发现就慢慢的变成了另一个人,好吧,就此打住,还是说说对我们有用的东西吧!
单点登录:顾名思义,就是只能一个人登录
思路:在登录时候传入username和lastLoginDate,生成token时,验证每次登录的时候过滤器判断最后登录时间,如果一直通行,不一致时候,直接返回error
代码实现:
登录
public UserTokenState login(String username, String password, Device device) {
try{
final Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
username,password
)
);
// Inject into security context
SecurityContextHolder.getContext().setAuthentication(authentication);
JwtUser jwtuser = (JwtUser)authentication.getPrincipal();
return generateUserTokenState(jwtuser.getId(),jwtuser.getUsername(),device);
}catch (Exception e){
logger.error("用户登录异常{}",e.getMessage());
return null;
}
}
生成用户token
private UserTokenState generateUserTokenState(Long userId,String username,Device device){
// token creation
final String timestamp = TimeProvider.getTimestampNow().toString();
com.alibaba.fastjson.JSONObject jsonObject = new com.alibaba.fastjson.JSONObject();
jsonObject.put("username",username);
jsonObject.put("lastLoginDate",timestamp);
String jws = jwtUtil.generateToken( jsonObject, device);
int expiresIn = jwtUtil.getExpiredIn(device);
User user = userRepository.findByUsername(username);
user.setLastLoginDate(timestamp);
userRepository.save(user);
//登出其他登录,并记录登录信息
try{
//后期在解释,此处处理单点登录踢出提示(类似,QQ你有新设备登录,您已经处于离线状态)
sendLogOutMessage(userId);
}catch (Exception e){
e.printStackTrace();
}
return new UserTokenState(userId,jws,expiresIn);
}
生成token
/**
* 通过用户名与设备类型生成token
* @param userinfo
* @param device
* @return
*/
public String generateToken(JSONObject userinfo, Device device) {
String audience = generateAudience(device);
return Jwts.builder()
.setIssuer( APP_NAME )
.setSubject(userinfo.toString())
.setAudience(audience)
.setIssuedAt(timeProvider.now())
.setExpiration(generateExpirationDate(device))
.signWith( SIGNATURE_ALGORITHM, SECRET )
.compact();
}
根据设备类型进行token过期设定
/**
* 根据设备类型进行token过期时间的设定(可以自己设定)
* @param device
* @return 转秒
*/
private Date generateExpirationDate(Device device) {
long expiresIn = device.isTablet() || device.isMobile() ? MOBILE_EXPIRES_IN : EXPIRES_IN;
return new Date(timeProvider.now().getTime() + expiresIn * 1000);
}
封装过滤器
@Override
public void doFilterInternal(
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
FilterChain chain
) throws IOException, ServletException {
ServletRequest requestWrapper = null;
HttpServletRequest request= (HttpServletRequest) httpServletRequest;
HttpServletResponse response= (HttpServletResponse) httpServletResponse;
//response此处自己封装自己用的header文件
String username;
logger.info("doFilterInternal={}",request.getHeader("Authorization"));
try {
//获取通用参数
BaseRequestParam baseRequestParam = parseRequestParam(request);
request.setAttribute("baseRequestParam",baseRequestParam);
String authToken = jwtUtil.getToken(request);
if (authToken != null) {
// get username from token
username = jwtUtil.getUsernameFromToken(authToken);
logger.info("login username: "+username);
if (username != null) {
// get user
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(authToken, userDetails)) {
// create authentication
TokenBasedAuthentication authentication = new TokenBasedAuthentication(userDetails);
authentication.setToken(authToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
throw new ClassOnlineException(HttpServletResponse.SC_UNAUTHORIZED,"Unauthorized");
}
}else {
throw new ClassOnlineException(HttpServletResponse.SC_UNAUTHORIZED,"Unauthorized");
}
}
}catch (Exception e) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
return;
}
//此处为了获取一些基础参数,为了日志或者后期实用方便写下的
if(request instanceof HttpServletRequest) { //判断是否是http请求
requestWrapper = new MAPIHttpServletRequestWrapper((HttpServletRequest) httpServletRequest); //再封装request
String json = HttpHelper.getBodyString(requestWrapper);
logger.info("传递参数解析 => {}"+json);
//获取请求参数
Map map = requestWrapper.getParameterMap();
logger.info("过滤器过滤的参数filter => " + httpServletRequest.getRemoteHost() + " " + JSONObject.toJSONString(map));
}
if(requestWrapper == null) {
chain.doFilter(request, response);
} else {
try {
chain.doFilter(requestWrapper, response);
} catch (ServletException e) {
logger.error("doFilterInternal method: filter is exception" + e.getStackTrace().toString());
chain.doFilter(request, response);
} catch (IOException e){
logger.error("doFilterInternal method: filter is exception" + e.getStackTrace().toString());
chain.doFilter(request, response);
}
}
}
上述基本上一个单点流程走完了,但是接下来或许你可以参考
所谓的“被挤下线”功能,即一个账号在A客户端保持登录状态,然后又在B客户端进行登录操作,那么A客户端就会被挤下线
App如何知道该账户已经在其他设备上登陆了呢?有三种实现方式
环信的即时聊天:
Android端:
// 注册连接监听 全局的监听
EMChatManager.getInstance().addConnectionListener(connectionListener);
实现这个链接监听
连接监听,的那个检测到连接断开的时候判断是用户被移除还是连接冲突即账号在其他地方登陆,做出相应的操作。
connectionListener = new EMConnectionListener() {
@Override
public void onDisconnected(int error) {
if (error == EMError.USER_REMOVED) {
onCurrentAccountRemoved();
} else if (error == EMError.CONNECTION_CONFLICT) {
onConnectionConflict();
}
}
@Override
public void onConnected() {
// in case group and contact were already synced, we supposed to
// notify sdk we are ready to receive the events
}
};
我们只关心账号在别处登陆,这个时候,我们一般要跳转到MainActivity,然后强制弹出对话框提示用户重新登陆。
/**
这个地方检测到登陆冲突之后需要回到MainActivity,并为MainActivity携带了一个标识和一个标记位Intent.FLAG_ACTIVITY_NEW_TASK,表示在一个新的task中开启一个Activity,如果包含这个Activity的task已经在运行,那么这个Activity就回到前台显示。然后回调onNewIntent()方法处理这个Intent。
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (intent.getBooleanExtra(Constant.ACCOUNT_CONFLICT, false) && !isConflictDialogShow) {
showConflictDialog();
} else if (intent.getBooleanExtra(Constant.ACCOUNT_REMOVED, false)
&& !isAccountRemovedDialogShow) {
showAccountRemovedDialog();
}
}
首先会判断标识,如果是账户冲突就会弹出对话框提示用户跳转登陆页面重新登陆。另外这个对话框是不能取消也不可关闭的。
这样被挤下线功能就基本实现了。
消息踢出:实现方式,其实单点登录到用户下线都已经成型了,接下来就是优化和完善人为提示,可有可无,看你做到什么程度
思路:用阿里到消息队列发送一条push,app和ios接受到之后,做出相应到提示
实现:logout方式踢出
/**
* 用户被登出时发送消息
*/
private void sendLogOutMessage(Long userId){
//获取当前用户登录的信息
Object loginInfoObj = cacheService.get("LOGIN_INFO" + userId);
if(loginInfoObj != null){
JSONObject userJson = JSONObject.fromObject(loginInfoObj);
String pushType = StringUtils.isBlank(userJson.getString(CommonConsts.PUSH_TYPE)) ? "":userJson.getString(CommonConsts.PUSH_TYPE);
String pushId = StringUtils.isBlank(userJson.getString(CommonConsts.PUSH_ID)) ? "":userJson.getString(CommonConsts.PUSH_ID);
if(!StringUtils.equals("-1",pushType)){
//将消息存储到消息队列中
JSONObject requestBodyJson = new JSONObject();
requestBodyJson.put(CommonConsts.PUSH_MSG_TYPE, CommonConsts.PUSH_MSG_TYPE_LOGOUT);
requestBodyJson.put(CommonConsts.PUSH_TYPE,pushType);
requestBodyJson.put(CommonConsts.PUSH_ID,pushId);
aliMQServiceImpl.sendMessage(CommonConsts.MQPRODUCER_TYPE_PUSH,aliMQServiceImpl.getTopicPush(),
aliMQServiceImpl.getTagPush(),requestBodyJson.toString().getBytes());
}
}
}
用一句话结束今天的记录吧,生命不止,奋斗不息。