Salesforce限制并发用户的实现
前几天有个需求,对于特定的profile,同一时间只允许一台设备登陆。查了资料,用LoginFlow结合apex实现,Salesforce官网上有个帖子,详细解释了实现方法。(https://developer.salesforce.com/docs/atlas.en-us.securityImplGuide.meta/securityImplGuide/security_login_flow_limit_concurrent_sessions.htm)但代码很古老,完全过时了,不过,取其精华就可以了:
public without sharing class LimitConcurrentSessions { @InvocableMethod public static List<Integer> LimitLoginSession() { Integer no = 0; List<AuthSession> sessions; String userid = UserInfo.getUserId(); sessions = [Select Id, ParentId, SessionType from AuthSession where UsersId=:userid]; for (AuthSession s : sessions) { // Count only parent and non-temp and non-internal sessions if(s.ParentId == null && s.SessionType != 'TempUIFrontdoor' && s.SessionType != 'InternalServiceCall' && s.SessionType != 'TempChatterNetworks') { no++; } } List<Integer> result = new List<Integer>(); result.add(no); return result; } }
然后在Flow中调用,若no >1,则将变量LoginFlow_ForceLogout设成True,强行退出。试了下,运行正常。
本来以为解决了,没想到测试员报告说尽管全退出了,但登陆时仍然提示已登陆了一台设备,强行退出。查了下AuthSession对象,果然有两个Session在那里。不知什么原因退出时session没有清掉,于是干脆写个trigger:
trigger LogoutEventTrigger on LogoutEventStream (after insert) { LogoutEventStream event = Trigger.new[0]; List<Profile> profiled = [SELECT id, Name FROM Profile WHERE Name = :'foo']; if (profiled.size() > 0) { Id profileId = profiled[0].id; Id userId = event.UserId; List<User> loggedOut = [SELECT ProfileId FROM User WHERE id = :userId]; if (loggedOut.size() > 0 && loggedOut[0].ProfileId == profileId) { List<AuthSession> sessions = [SELECT Id, usersid FROM AuthSession WHERE usersid = :userId]; if (sessions.size() > 0) { delete(sessions); } } } }
然后在Setup - Event Manager - Logout Event - Enable Streaming,使trigger生效。这下消停了一会,又有人报错,说有个账户在第一次登陆(由管理员发邮件,点击邮件中的链接登陆)时,提示已登陆一台设备,强行退出。查AuthSession对象,发现每次点链接时都会出现两个session!于是利用AuthSession对象的LoginHistory域,改写代码:
public without sharing class LimitConcurrentSessions { @InvocableMethod public static List<Integer> LimitLoginSession() { Integer no = 0; List<AuthSession> sessions; String userid = UserInfo.getUserId(); List<Datetime> loginTime = new List<DateTime>(); sessions = [Select Id, ParentId, SessionType, LoginHistoryId from AuthSession where UsersId=:userid]; for (AuthSession s : sessions) { // Count only parent and non-temp and non-internal sessions if(s.ParentId == null && s.SessionType != 'TempUIFrontdoor' && s.SessionType != 'InternalServiceCall' && s.SessionType != 'TempChatterNetworks') { List<LoginHistory> history = [SELECT LoginTime FROM LoginHistory WHERE Id = :s.LoginHistoryId]; if (history.size() > 0) { if (!loginTime.contains(history[0].LoginTime)) { loginTime.add(history[0].LoginTime); no++; } } } } List<Integer> result = new List<Integer>(); result.add(no); return result; } }
也就是如果两个session的时间完全一样,就忽略。这以后就正常了,没有人报错。
上面多次用了List保存SOQL的结果,目的是为了避免万一找不到记录,报Exception。
这个功能的实现又一次使我对Salesforce印象不佳。