要進行單設備登陸,在其餘地點登陸後,本地的其餘操做會被攔截返回登陸界面。javascript
原理就在於要在登陸時在redis中存儲Session,進行操做時要進行Session的比對。java
具體實現,假設咱們的OAuth 2的登陸調用接口以下:redis
共享Session,User模塊跟OAuth模塊都要設置spring
@Configuration @EnableRedisHttpSession public class SessionConfig { }
Feignapi
@Component @FeignClient("oauth-center") public interface Oauth2Client { /** * 獲取access_token<br> * 這是spring-security-oauth2底層的接口,類TokenEndpoint<br> * * @param parameters * @return * @see org.springframework.security.oauth2.provider.endpoint.TokenEndpoint */ @PostMapping(path = "/api-o/oauth/token") Map<String, Object> postAccessToken(@RequestParam Map<String, String> parameters); /** * 刪除access_token和refresh_token<br> * 認證中心的OAuth2Controller方法removeToken * * @param access_token */ @DeleteMapping(path = "/api-o/remove_token") void removeToken(@RequestParam("access_token") String access_token); }
Controller服務器
/** * Created by Administrator on 2018/10/19. */ @Slf4j @RestController public class UserTokenController { @Autowired private Oauth2Client oauth2Client; @Resource private RedisService redisServiceImpl; /** * 系統登錄<br> * 根據用戶名登陸<br> * 採用oauth2密碼模式獲取access_token和refresh_token * * @param loginParam * @return */ @PostMapping("/users-anon/sys/logins") public Map<String, Object> login(@RequestBody LoginParam loginParam,HttpServletRequest request) { Map<String, String> parameters = new HashMap<>(); parameters.put(OAuth2Utils.GRANT_TYPE, "password"); parameters.put(OAuth2Utils.CLIENT_ID, "system"); // parameters.put(OAuth2Utils.CLIENT_ID, "system"); parameters.put("client_secret", "system"); parameters.put(OAuth2Utils.SCOPE, "app"); // parameters.put("username", username); // 爲了支持多類型登陸,這裏在username後拼裝上登陸類型 parameters.put("username", loginParam.getUsername() + "|" + CredentialType.USERNAME.name()); parameters.put("password", loginParam.getPassword()); parameters.put("status","200"); Map<String, Object> tokenInfo = null; try { tokenInfo = oauth2Client.postAccessToken(parameters); }catch (Exception e){ e.printStackTrace(); return ResponseUtils.getResult(500,"login failed"); } // saveLoginLog(username, "用戶名密碼登錄", BlackIPAccessFilter.getIpAddress(request)); return ResponseUtils.getDataResult(tokenInfo); } }
加入Session的存儲session
/** * Created by Administrator on 2018/10/19. */ @Slf4j @RestController public class UserTokenController { @Autowired private Oauth2Client oauth2Client; @Resource private RedisService redisServiceImpl; /** * 系統登錄<br> * 根據用戶名登陸<br> * 採用oauth2密碼模式獲取access_token和refresh_token * * @param loginParam * @return */
@PostMapping("/users-anon/sys/logins") public Map<String, Object> login(@RequestBody LoginParam loginParam, HttpServletRequest request) { Map<String, String> parameters = new HashMap<>(); parameters.put(OAuth2Utils.GRANT_TYPE, "password"); parameters.put(OAuth2Utils.CLIENT_ID, "system"); // parameters.put(OAuth2Utils.CLIENT_ID, "system"); parameters.put("client_secret", "system"); parameters.put(OAuth2Utils.SCOPE, "app"); // parameters.put("username", username); // 爲了支持多類型登陸,這裏在username後拼裝上登陸類型 parameters.put("username", loginParam.getUsername() + "|" + CredentialType.USERNAME.name()); parameters.put("password", loginParam.getPassword()); parameters.put("status","200"); Map<String, Object> tokenInfo = null; try { tokenInfo = oauth2Client.postAccessToken(parameters); HttpSession session = request.getSession(); String sessionId = UUID.randomUUID().toString(); //此處修改成共享Session session.setAttribute("sessionId", sessionId); session.setAttribute("username",loginParam.getUsername()); String key = loginParam.getUsername() + "-onlyLogin"; redisServiceImpl.set(key,sessionId); redisServiceImpl.expire(key,30 * 60); redisServiceImpl.hset("sessionHash",sessionId,loginParam.getUsername()); }catch (Exception e){ e.printStackTrace(); return ResponseUtils.getResult(500,"login failed"); } // saveLoginLog(username, "用戶名密碼登錄", BlackIPAccessFilter.getIpAddress(request)); return ResponseUtils.getDataResult(tokenInfo); }
}
配置攔截器app
@Slf4j @Component public class RedisInterceptor extends HandlerInterceptorAdapter { @Resource private RedisService redisServiceImpl; @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); //讀取共享Session String requestedSessionId = (String) session.getAttribute("sessionId"); String userName = null; response.setCharacterEncoding("utf-8"); response.setContentType("text/javascript;charset=utf-8"); try { if (!StringUtils.isEmpty(requestedSessionId)) { userName = redisServiceImpl.hget("sessionHash", requestedSessionId); } if (StringUtils.isEmpty(userName)) { response.getWriter().write("{\"message\":\"請先登錄\"}"); return false; } else { String cacheSessionId = null; String sessionKey = userName + "-onlyLogin"; try { cacheSessionId = redisServiceImpl.get(sessionKey); } catch (Exception e) { e.printStackTrace(); } if (StringUtils.isEmpty(cacheSessionId)) { response.getWriter().write("{\"message\":\"請先登錄\"}"); return false; } else { if (!cacheSessionId.equals(requestedSessionId)) { response.getWriter().write("{\"message\":\"您的帳號已在別處登錄,請從新登錄\"}"); return false; } else { redisServiceImpl.expire(sessionKey, 30 * 60); return super.preHandle(request, response, handler); } } } }catch (Exception e) { e.printStackTrace(); } response.getWriter().write("{\"message\":\"服務器忙\"}"); return false; } }
攔截器就是爲了獲取每次的Session,而且跟redis中的session進行比對,若是session不一樣,則進行攔截。dom
@Configuration public class RedisSessionConfig extends WebMvcConfigurerAdapter { @Autowired private RedisInterceptor redisInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(redisInterceptor).excludePathPatterns("/**/users-anon/**").excludePathPatterns("/api-o/oauth/token"); super.addInterceptors(registry); } }
這裏要配置對登陸的url以及feign的url進行放行,則能夠對多地點登陸時,使以前的登陸沒法操做。ide