要进行单设备登录,在其他地点登录后,本地的其他操作会被拦截返回登录界面。
原理就在于要在登录时在redis中存储Session,进行操作时要进行Session的比对。
具体实现,假设我们的OAuth 2的登录调用接口如下:
共享Session,User模块跟OAuth模块都要设置
@Configuration
@EnableRedisHttpSession
public class SessionConfig {
}
Feign
@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的存储
/**
* 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);
}
}
配置拦截器
@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不同,则进行拦截。
@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进行放行,则可以对多地点登录时,使之前的登录无法操作。