Spring Boot 实现用户登陆并限制同一用户只在一个地方登陆

开始写文章,主要为了记录自己开发过程中遇到的问题,是已经解决了的问题,分享出来交流学习。
如若个人理解有误,文章中有不正确的地方,希望大家批评指正,我会继续学习,及时改正。

本文主旨实现用户登陆并且限制同一个用户只在一个地方是登陆状态,并没有用Zuul实现,是自己的一个实现方法。
项目架构:Spring Boot + Spring Cloud + Angularjs(前端),前端会控制未登录时只能访问到登陆页面。

涉及到一个小坑,就是获取Bean的问题,就是监听器和过滤器是在Servlet前加载,还有拦截器,拦截器实质上是过滤器的调用,也是在Servlet前加载的,此时Bean还没有被初始化,所以是获取不到的。

先奉上代码,文章末尾是个人实现思路:

/**
 * @auther fanxuebo
 * @desc 拦截器配置类
 */
@Configuration
public class MyWebAppConfigurer extends WebMvcConfigurerAdapter {

    @Bean
    public SpringContextHolder springContextHolder(){
        return new SpringContextHolder();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //拦截规则:可以定义拦截的url和不拦截的url
        registry.addInterceptor(new UniqueUserInfoInterceptor ()).addPathPatterns("/**").excludePathPatterns("/xplatform/index/login").excludePathPatterns ("/xplatform/index/logout");
        super.addInterceptors(registry);
    }
}
 
/**
 * @Auther: fanxuebo
 * @Description: 保存spring的ApplicationContext容器, 方便其他地方获取Bean
 */
public class SpringContextHolder implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        SpringContextHolder.applicationContext = context;
    }

    /**
     * @Auther: fanxuebo
     * @Description: 获取 ApplicationContext 容器
     */
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    /**
     * @Auther: fanxuebo
     * @Description: 获取 bean
     */
    public static Object getBean(String name) {
        return applicationContext.getBean (name);
    }

}
/**
 * @auther fanxuebo
 * @desc 自定义拦截器,用户判断用户是否登陆,同一用户限制一个地方登陆
         * 
 */
public class UniqueUserInfoInterceptor implements HandlerInterceptor {

    private static Logger LOGGER = LoggerFactory.getLogger (UniqueUserInfoInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
    
    // 可以成功获取到Bean啦
        CgpMngUserService cgpMngUserService = (CgpMngUserService) SpringContextHolder.getBean ("cgpMngUserServiceImpl");
    
    // 具体实现登陆的代码自己来完善吧,文章最后我会给出我的思路。
    
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

    }
}
=========================================我是华丽的分割线=========================================

Controller层的登陆方法:

获取登陆表单中的用户名密码去数据库判断是否正确,

登陆成功,

    再查询登陆用户缓存表,判断这个用户是否是登陆状态,是登陆状态,更新token,不是登陆状态,生成token,存储信息。

    将用户名、密码、token存储到cookie,存储格式是:用户名_密码_token,写回到客户端。
    (每次请求都会携带cookie,过滤器中会获取每次请求携带的cookie,来判断用户是否被挤下线)

登录不成功,很简单啦,直接返回错误信息。

public String login(String username, String password) {
    JSONObject jsonObject = new JSONObject ();
    // 验证用户名密码是否正确
    LOGGER.info ("用户登陆明文密码{}", password);
    password = MD5.getMD5ofStr (password);
    LOGGER.info ("用户登陆MD5加密密码{}", password);
    CgpMngUser cgpMngUser = cgpMngUserService.findByUserCodeAndUserPassword (username, password);//查用户表
    if (cgpMngUser != null) {
        // 验证用户是否是登陆状态
        String token = UUID.randomUUID().toString().replace("-", "");
        String remoteAddr = getIpAddr();
        String loginTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
        stringRedisTemplate.opsForValue().set("userCode|" + username, token + "_" + remoteAddr + "_" + loginTime, 1, TimeUnit.DAYS);
        jsonObject.put("status", "0000");
        Cookie cookie = new Cookie("token", username + "_" + token);
        cookie.setMaxAge(Integer.MAX_VALUE);
        cookie.setPath("/");
        response.addCookie(cookie);
        return jsonObject.toJSONString();
    }
    jsonObject.put ("status", "0001");// 登录不成功
    jsonObject.put ("msg", "用户名或密码错误!");
    return jsonObject.toJSONString ();
}

过滤器中方法实现:

获取cookie中的用户名密码,去登陆用户缓存表查询当前登陆用户的token,和cookie中的token做比较
相等,用户是第一次登陆;不想等,用户在其他地方登陆了,阻止访问,返回状态码403。
(因为是前后端完全分离,无法直接重定向,只能返回状态码,前端再去做判断,访问被禁止,跳转回登陆页)

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
    StringRedisTemplate stringRedisTemplate = (StringRedisTemplate) SpringContextHolder.getBean("stringRedisTemplate");
    Cookie[] cookies = request.getCookies();
    if (null != cookies) {
        for (Cookie cookie : cookies) {
            if ("token".equals(cookie.getName())) {
                String username = cookie.getValue().split("_")[0];
                String token = cookie.getValue().split("_")[1];
                String value = stringRedisTemplate.boundValueOps("userCode|" + username).get();
                String[] valueArr = value.split("_");
                String cacheToken = valueArr[0];
                if (token.equals(cacheToken)) {
                    return true;
                }
            }
        }
    }
    LOGGER.info ("执行过滤器UniqueUserInfoInterceptor----获取用户信息失败");
    response.sendError (HttpServletResponse.SC_FORBIDDEN);
    return false;
}

向客户端回写cookie的时候可以只回写用户名+token,登陆用户缓存表可以配置在redis,同时可以记录登录时间及登陆IP,附上获取登陆IP方法

private String getIpAddr() {
    String ip = request.getHeader ("x-forwarded-for");
    if (ip == null || ip.length () == 0 || "unknown".equalsIgnoreCase (ip)) {
        ip = request.getHeader ("Proxy-Client-IP");
    }
    if (ip == null || ip.length () == 0 || "unknown".equalsIgnoreCase (ip)) {
        ip = request.getHeader ("WL-Proxy-Client-IP");
    }
    if (ip == null || ip.length () == 0 || "unknown".equalsIgnoreCase (ip)) {
        ip = request.getRemoteAddr ();
        if (ip.equals ("127.0.0.1")) {
            //根据网卡取本机配置的IP
            InetAddress inet = null;
            try {
                inet = InetAddress.getLocalHost ();
            } catch (Exception e) {
                e.printStackTrace ();
            }
            ip = inet.getHostAddress ();
        }
    }
    // 多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
    if (ip != null && ip.length () > 15) {
        if (ip.indexOf (",") > 0) {
            ip = ip.substring (0, ip.indexOf (","));
        }
    }
    return ip;
}

文章完毕,感谢你的阅读,希望你能有所收获。

2018年7月10日

猜你喜欢

转载自blog.csdn.net/fanxb92/article/details/80986290