package com.uas.sso.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.uas.account.AccountConfig;
import com.uas.account.core.Const;
import com.uas.account.core.Status;
import com.uas.account.core.util.CaptchaUtil;
import com.uas.account.core.util.ExecuteUtils;
import com.uas.account.core.util.QRCodeUtil;
import com.uas.account.entity.*;
import com.uas.account.exception.VisibleError;
import com.uas.account.service.*;
import com.uas.account.viewmodel.AssetView;
import com.uas.account.web.BaseController;
import com.uas.sso.*;
import com.uas.sso.Token;
import com.uas.sso.common.IpHelper;
import com.uas.sso.common.util.HttpUtil;
import com.uas.sso.web.waf.request.WafRequestWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import java.io.IOException;
import java.util.*;


@Controller
@RequestMapping("/sso")
public class LoginController extends BaseController {

	@Autowired
	private UserService userService;
	@Autowired
	private AppService appService;
	@Autowired
	private AssetService assetService;
	@Autowired
	private UserSpaceService userSpaceService;
	@Autowired
	private TokenService tokenService;
	@Autowired
	private SettingService settingService;
	@Autowired
	private SysDataToMallService sysDataToMallService;
	@Autowired
	private UserLockService userLockService;

	private static final int PWD_ERROR_THREE_TIME = 3;

	private static final int PWD_ERROR_FIVE_TIME = 5;

	/**
	 * 登录界面
	 *
	 * @throws Exception
	 */
	@RequestMapping(value = "/login", method = RequestMethod.GET)
	public ModelAndView loginPage(ModelMap model) throws Exception {
		/**
		 * 正常登录 需要过滤sql及脚本注入
		 */
		WafRequestWrapper wr = new WafRequestWrapper(request);
		String returnUrl = wr.getParameter(SSOConfig.ReturnURLParam);
		String baseUrl = wr.getParameter("baseUrl");
		baseUrl = replaceLocalhost(baseUrl);
		Token token = SSOHelper.getToken(request);
		if (token == null) {
			String appId = wr.getParameter(AccountConfig.AppIdParam);
			// 指定登录的app
			if (StringUtils.isEmpty(appId)) {
				appId = AccountConfig.ACCOUNT_CENTER;
			}
			model.addAttribute("appId", appId);
			if (!StringUtils.isEmpty(returnUrl)) {
				model.addAttribute("returnUrl", returnUrl);
			}
			if (!StringUtils.isEmpty(baseUrl)) {
				model.addAttribute("baseUrl", baseUrl);
			}
			// 企业相关信息
			String domain = wr.getParameter("domain");
			if (domain != null) {
				UserSpaceDetail detail = userSpaceService.findByDomain(domain);
				if (detail != null && detail.getStatus().equals(Status.ENABLED.value())) {
					UserSpace userSpace = userSpaceService.findOne(appId, detail.getBusinessCode());
					if (userSpace != null) {
						model.addAttribute("spaceId", userSpace.getId());
						model.addAttribute("spaceName", userSpace.getName());
					}
				}
			}
			// 先尝试查找自身相关资源
			List<AssetView> assets = assetService.findByApp(appId);
			if (CollectionUtils.isEmpty(assets)) {
				App app = appService.findOne(appId);
				// 再尝试查找级联应用相关资源
				if (app != null && !StringUtils.isEmpty(app.getUserControl())) {
					assets = assetService.findByApp(app.getUserControl());
				}
			}
			// 最后尝试使用SSO默认资源
			if (CollectionUtils.isEmpty(assets)) {
				assets = assetService.findByApp(AccountConfig.ACCOUNT_CENTER);
			}
			model.addAttribute("assets", assets);
			return new ModelAndView("sso/login", model);
		} else {
			if (StringUtils.isEmpty(returnUrl)) {
				return new ModelAndView("user/info", model);
			}
			return new ModelAndView("redirect:" + HttpUtil.decodeURL(returnUrl));
		}
	}

	private String replaceLocalhost(String baseUrl) {
		if (baseUrl == null) {
			return baseUrl;
		}
		if (baseUrl.contains("localhost")) {
			baseUrl = baseUrl.replace("localhost", IpHelper.getIpAddr(request));
		} else if (baseUrl.contains("127.0.0.1")) {
			baseUrl = baseUrl.replace("127.0.0.1", IpHelper.getIpAddr(request));
		}
		return baseUrl;
	}

	/**
	 * 登录
	 */
	@RequestMapping(value = "/login", method = RequestMethod.POST)
	@ResponseBody
	public ModelMap login() {
		/**
		 * 正常登录 需要过滤sql及脚本注入
		 */
		WafRequestWrapper wr = new WafRequestWrapper(request);
		String appId = wr.getParameter("appId");
		String spaceId = wr.getParameter("spaceId");
		// UID or dialectUID
		String username = StringUtils.trimAllWhitespace(wr.getParameter("username"));
		String pass = wr.getParameter("password");
		String returnUrl = wr.getParameter("returnUrl");
		String baseUrl = wr.getParameter("baseUrl");
		if (baseUrl != null) {
			request.getSession().setAttribute("baseUrl", baseUrl);
		}
		String captcha = wr.getParameter("captcha");
		// 验证码
		Object randomString = request.getSession().getAttribute("randomString");
		String checkCode = randomString==null?"":randomString.toString();
		// 为了在创建cookie时使用getAttribute方法拿到数据
		String maxage = wr.getParameter(SSOConfig.SSO_COOKIE_MAXAGE);
		// 设置cookie有效期
		if (maxage != null) {
			request.setAttribute(SSOConfig.SSO_COOKIE_MAXAGE, Integer.valueOf(maxage));
		}
		if (StringUtils.isEmpty(appId)) {
			return error("应用id为空");
		}
		if (StringUtils.isEmpty(username)) {
			return error("用户名为空");
		}
		// 检测用户名是否被锁定
        request.getSession().setAttribute("username", username);
		int pwdError = (int) checkPwdError(username).get("pwdError");
		if (pwdError >= PWD_ERROR_FIVE_TIME) {
			return error("403", "密码错误次数已达上限，今日无法登陆");
		}
		if (StringUtils.isEmpty(pass)) {
			return error("密码为空");
		}
		if (pwdError >= PWD_ERROR_THREE_TIME && StringUtils.isEmpty(captcha)) {
			return error("验证码不能为空");
		}
		if (pwdError >= PWD_ERROR_THREE_TIME && !captcha.equalsIgnoreCase(checkCode)) {
			return error("验证码错误");
		}
		App app = appService.findOne(appId);
		if (app != null) {
			App controlApp = StringUtils.isEmpty(app.getUserControl()) ? app : appService.findOne(app.getUserControl());
			// app是否允许个人用户登录
			boolean personalEnable = !StringUtils.isEmpty(app.getPersonalEnable()) && (Const.YES == app.getPersonalEnable());
			// 没有选择企业
			if (StringUtils.isEmpty(spaceId)) {
				final List<UserView> users = getUserByUserName(controlApp, username);// 包含个人注册用户
				if (!CollectionUtils.isEmpty(users)) {
					if (users.size() > 1) {
						for (UserView user : users) {
							String encryPwd = userService.getEncryPassword(controlApp.getEncryFormat(), pass, user.getSalt());
							if (encryPwd.equals(user.getPassword())) {
								// 请选择其中一个企业
								return success(getSpaceSelect(users, personalEnable));
							}
						}
					} else {
						return loginByUser(controlApp, users.get(0), pass, returnUrl);
					}
					inputErrorPwd(username);
					ModelMap data = checkPwdError(username);
					return error("您输入的账号或密码有误").addAllAttributes(data);
				} else {
					return error("您输入的账号或密码有误");
				}
			} else if (personalEnable && Const.SPACEID_PERSONAL_REGISTER.equals(spaceId)) {// 从多个企业中选择个人用户登录，个人登录时spaceid已经设置为1
				UserView user = getUserByUserName(controlApp.getUid(), null, username);
				return loginByUser(controlApp, user, pass, returnUrl);
			} else {// 选择其他企业
				UserView user = getUserByUserName(controlApp.getUid(), Long.parseLong(spaceId), username);
				return loginByUser(controlApp, user, pass, returnUrl);
			}
		} else {
			return error("应用id不存在");
		}
	}

	/**
	 * 登录
	 */
	@RequestMapping(value = "/mobile/login", method = RequestMethod.POST)
	@ResponseBody
	public ModelMap loginByMobile() {
		/**
		 * 正常登录 需要过滤sql及脚本注入
		 */
		WafRequestWrapper wr = new WafRequestWrapper(request);
		String appId = wr.getParameter("appId");
		String spaceId = wr.getParameter("spaceId");
		// UID or dialectUID
		String username = StringUtils.trimAllWhitespace(wr.getParameter("username"));
		String pass = wr.getParameter("password");
		String returnUrl = wr.getParameter("returnUrl");
		String baseUrl = wr.getParameter("baseUrl");
		if (baseUrl != null) {
			request.getSession().setAttribute("baseUrl", baseUrl);
		}
		if (StringUtils.isEmpty(appId)) {
			return error("应用id为空");
		}
		if (StringUtils.isEmpty(username)) {
			return error("用户名为空");
		}
		App app = appService.findOne(appId);
		if (app != null) {
			App controlApp = StringUtils.isEmpty(app.getUserControl()) ? app : appService.findOne(app.getUserControl());
			// app是否允许个人用户登录
			boolean personalEnable = !StringUtils.isEmpty(app.getPersonalEnable()) && (Const.YES == app.getPersonalEnable());
			// 没有选择企业
			if (StringUtils.isEmpty(spaceId)) {
				final List<UserView> users = getUserByUserName(controlApp, username);// 包含个人注册用户
				if (!CollectionUtils.isEmpty(users)) {
					if (users.size() > 1) {
						for (UserView user : users) {
							String encryPwd = userService.getEncryPassword(controlApp.getEncryFormat(), pass, user.getSalt());
							if (encryPwd.equals(user.getPassword())) {
								// 请选择其中一个企业
								return success(getSpaceSelect(users, personalEnable));
							}
						}
					} else {
						return loginByUserInMobile(controlApp, users.get(0), pass, returnUrl);
					}
					return error("您输入的账号或密码有误");
				} else {
					return error("您输入的账号或密码有误");
				}
			} else if (personalEnable && Const.SPACEID_PERSONAL_REGISTER.equals(spaceId)) {// 从多个企业中选择个人用户登录，个人登录时spaceid已经设置为1
				UserView user = getUserByUserName(controlApp.getUid(), null, username);
				return loginByUserInMobile(controlApp, user, pass, returnUrl);
			} else {// 选择其他企业
				UserView user = getUserByUserName(controlApp.getUid(), Long.parseLong(spaceId), username);
				return loginByUserInMobile(controlApp, user, pass, returnUrl);
			}
		} else {
			return error("应用id不存在");
		}
	}

	private ModelMap loginByUserInMobile(App app, UserView user, String pass, String returnUrl) {
		if (user == null) {
			return error("您输入的账号或密码有误");
		} else {
			if (StringUtils.isEmpty((user.getPassword()))) {
				return error("100", "未设置密码");// 使用错误码100来判断
			} else {
				String encryPwd = userService.getEncryPassword(app.getEncryFormat(), pass, user.getSalt());
				if (encryPwd.equals(user.getPassword())) {
					UserSpaceDetail detail = userSpaceService.findByBusinessCode(user.getSpaceUID());
					if (detail != null) {
						user.setSpaceDomain(detail.getDomain());
					}
					/*
					 * 设置登录 Cookie 最后一个参数 true 时添加 cookie 同时销毁当前 JSESSIONID
					 * 创建信任的 JSESSIONID
					 */
					// 通知各个应用用户已经登录
					ModelMap data = new ModelMap();
					data.put("returnUrl", HttpUtil.decodeURL(returnUrl));
					String loginUrl = (String) request.getSession().getAttribute("baseUrl");
					data.put("loginUrl", loginUrl);
					data.put("data", user);
					SSOToken st = new SSOToken(request, user.getUid());
					st.setData(JSON.toJSONString(user));
					SSOHelper.setSSOCookie(request, response, st, true);
					return success(data);
				} else {
					return error("您输入的账号或密码有误");
				}
			}
		}
	}

	/**
	 * 密码输入错误处理
	 * @param username 密码错误账号
	 */
	private void inputErrorPwd(String username) {
		UserLock userLock = userLockService.getOneByUsername(username);
		if (userLock == null) {
			userLock = new UserLock(username);
		}
		try {
			userLock.setErrorCount(userLock.getErrorCount() + 1);
		} catch (NullPointerException e) {
			userLock.setErrorCount(1);
		}
		userLockService.save(userLock);
		request.getSession().setAttribute(username, userLock.getErrorCount());
	}

	/**
	 * 获得密码输入错误次数
	 *
	 * @param username 账号
	 * @return 返回的数据信息
	 * 			isLock：账号是否被锁定
	 * 			needCheckCode：账号是否需要输入验证码
	 * 			pwdError：输错密码次数
	 */
	@RequestMapping(value = "/checkPwd/count", method = RequestMethod.GET)
	@ResponseBody
	public ModelMap checkPwdError(String username) {
		ModelMap data = new ModelMap();
		int pwdErrorCount;
		try {
			pwdErrorCount = userLockService.getOneByUsername(username).getErrorCount();
		} catch (NullPointerException e) {
			pwdErrorCount = 0;
		}
		if (pwdErrorCount >= PWD_ERROR_FIVE_TIME) {
			data.put("isLock", true);
		}
		if (pwdErrorCount >= PWD_ERROR_THREE_TIME) {
			data.put("needCheckCode", true);
		}
		data.put("pwdError", pwdErrorCount);
		return data;
	}

	/**
	 * 登录
	 */
	@RequestMapping(value = "/login/auto", method = RequestMethod.POST)
	@ResponseBody
	public ModelMap loginAuto() {
		// 清除之前的登录状态
		SSOHelper.clearLogin(request, response);
		WafRequestWrapper wr = new WafRequestWrapper(request);
		String returnUrl = wr.getParameter("returnUrl");
		String appId = wr.getParameter("appId");
		String enuu = wr.getParameter("enuu");
		String tel = wr.getParameter("tel");
		String password = wr.getParameter("password");
		String baseUrl = wr.getParameter("baseUrl");
		request.getSession().setAttribute("baseUrl", baseUrl);
		if (StringUtils.isEmpty(appId)) {
			return error("appId为空");
		}
		if (StringUtils.isEmpty(tel)) {
			return error("手机号为空");
		}
		if (StringUtils.isEmpty(password)) {
			return error("密码为空");
		}
		if (StringUtils.isEmpty(enuu)) {
			return error("UU号为空");
		}

		App app = appService.findOne(appId);
		if (app == null) {
			return error("appId不存在");
		}
		App controlApp = StringUtils.isEmpty(app.getUserControl()) ? app : appService.findOne(app.getUserControl());

		UserSpace userSpace = userSpaceService.findByDialectUID(controlApp.getUid(), enuu);
		if (userSpace != null) {
			UserView user = getUserByUserName(controlApp.getUid(), userSpace.getId(), tel);
			return loginByUser(controlApp, user, password, returnUrl);
		}

		SSOHelper.clearLogin(request, response);
		return success(new ModelMap("returnUrl", returnUrl));
	}

	/**
	 * 登录
	 */
	@RequestMapping(value = "mobile/login/auto", method = RequestMethod.POST)
	@ResponseBody
	public ModelMap loginAutoByMobile() {
		WafRequestWrapper wr = new WafRequestWrapper(request);
		String tel = wr.getParameter("tel");
		String appId = wr.getParameter("appId");
		String password = wr.getParameter("password");
		String enuu = wr.getParameter("enuu");
		String returnUrl = wr.getParameter("returnUrl");
		if (StringUtils.isEmpty(appId)) {
			return error("appId为空");
		}
		if (StringUtils.isEmpty(tel)) {
			return error("手机号为空");
		}
		if (StringUtils.isEmpty(password)) {
			return error("密码为空");
		}
		if (StringUtils.isEmpty(enuu)) {
			return error("UU号为空");
		}

		App app = appService.findOne(appId);
		if (app == null) {
			return error("appId不存在");
		}
		App controlApp = StringUtils.isEmpty(app.getUserControl()) ? app : appService.findOne(app.getUserControl());

		UserSpace userSpace = userSpaceService.findByDialectUID(appId, enuu);
		if (userSpace != null) {
			UserView user = getUserByUserName(appId, userSpace.getId(), tel);
			return loginByUser(controlApp, user, password, returnUrl);
		}

		List<UserView> users = userService.findByAppAndUID(appId, enuu);

		if (users.size() != 0) {
			for (UserView user : users) {
				String encryPwd = userService.getEncryPassword(controlApp.getEncryFormat(), password, user.getSalt());
				if (encryPwd.equals(user.getPassword())) {
					return loginByUser(controlApp, user, password, returnUrl);
				}
			}
		}

		SSOHelper.clearLogin(request, response);
		return success(new ModelMap("returnUrl", ""));
	}

	/**
	 * 支持三个唯一账号：邮箱、手机号、ID
	 *
	 * @param app
	 * @param username
	 * @return
	 */
	private List<UserView> getUserByUserName(App app, String username) {
		if (username.contains("@")) {
			return userService.findByAppAndSecondUID(app.getUid(), username);
		} else if (username.matches(Const.REGEXP_MOBILE)) {
			return userService.findByAppAndUID(app.getUid(), username);
		} else {
			if (Const.NO == app.getDialectEnable()) {
				throw new VisibleError("请使用手机号或邮箱地址登录" + app.getDescription());
			}
			return userService.findByAppAndDialectUID(app.getUid(), username);
		}
	}

	private UserView getUserByUserName(String app, Long spaceId, String username) {
		if (username.contains("@")) {
			return userService.findOneViewBySecondUID(app, spaceId, username);
		} else if (username.matches(Const.REGEXP_MOBILE)) {
			return userService.findOneView(app, spaceId, username);
		} else {
			return userService.findOneViewByDialectUID(app, spaceId, username);
		}
	}

	private ModelMap loginByUser(App app, final UserView user, final String pass, String returnUrl) {
		if (user == null) {
			return error("您输入的账号或密码有误");
		} else {
			if (StringUtils.isEmpty((user.getPassword()))) {
				return error("100", "未设置密码");// 使用错误码100来判断
			} else {
				try {
					giveMallLastLoginTime(user, pass);
				} catch (Exception e) {
					return error("密码不能为空");
				}
				String encryPwd = userService.getEncryPassword(app.getEncryFormat(), pass, user.getSalt());
				if (encryPwd.equals(user.getPassword())) {
					UserSpaceDetail detail = userSpaceService.findByBusinessCode(user.getSpaceUID());
					if (detail != null) {
                        user.setSpaceDomain(detail.getDomain());
                    }
					/*
					 * 设置登录 Cookie 最后一个参数 true 时添加 cookie 同时销毁当前 JSESSIONID
					 * 创建信任的 JSESSIONID
					 */
					// 通知各个应用用户已经登录
					ModelMap data = new ModelMap();
					data = addOtherAppRequestData(user, data, request.getSession().getAttribute("baseUrl"));
					data.put("returnUrl", HttpUtil.decodeURL(returnUrl));
					SSOToken st = new SSOToken(request, user.getUid());
					st.setData(JSON.toJSONString(user));
					SSOHelper.setSSOCookie(request, response, st, true);
					return success(data);
				} else {
                    String username = String.valueOf(request.getSession().getAttribute("username"));
					inputErrorPwd(username);
					ModelMap data = checkPwdError(username);
					return error("您输入的账号或密码有误").addAllAttributes(data);
				}
			}
		}
	}

	private void giveMallLastLoginTime(final UserView user, String pass) throws Exception {
		final Short level = checkPasswordLevel(pass);
		ExecuteUtils.asyncExecute(new Runnable() {
			@Override
			public void run() {
				try {
					// 从userDetail中取出上次登录时间并放入user中同步至商城
					user.setPwdSecLevel(level);
					UserDetail userDetail = userService.findOne(user.getUid());
					user.setLastLoginTime(userDetail.getLastLoginTime()==null?System.currentTimeMillis():userDetail.getLastLoginTime());
					// 更新userDetail上次登录时间
					userDetail.setLastLoginTime(System.currentTimeMillis());
					userService.save(userDetail);
					sysDataToMallService.user(user);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	/**
	 * 添加其他应用的请求数据
	 * @param user 用户信息
	 * @param data 用户添加返回页面数据
	 * @param loginUrl 其他登录地址，一般用于本地
	 */
	private ModelMap addOtherAppRequestData(UserView user, ModelMap data, Object loginUrl) {
		// 需要通知的应用数量
		int count = 0;
		List<App> apps = appService.findAll();
		List<String> loginUrls = new ArrayList<>();
		boolean hasLoginUrl = false;
		for (App app : apps) {
			if (StringUtils.isEmpty(app.getLoginUrl())) {
				continue;
			}
			if (app.getLoginUrl().equals(loginUrl)) {
				hasLoginUrl = true;
			}
			loginUrls.add(app.getLoginUrl());
			count++;
		}

		// 添加baseUrl
		if (!hasLoginUrl && !StringUtils.isEmpty(loginUrl)) {
			loginUrls.add(loginUrl.toString());
			count++;
		}

		data.put("count", count);
		data.put("loginUrls", loginUrls);

		// 添加传递数据
		JSONObject jsonObject = JSON.parseObject(JSON.toJSONString(user));
		Integer maxage = (Integer) request.getAttribute(SSOConfig.SSO_COOKIE_MAXAGE);
		jsonObject.put("maxage", maxage);
		data.put("data", jsonObject);
		return data;
	}

	private ModelMap getSpaceSelect(List<UserView> users, boolean personalEnable) {
		List<Map<String, Object>> spaces = new ArrayList<Map<String, Object>>();
		for (UserView user : users) {
			Map<String, Object> space = new HashMap<String, Object>();
			if (!StringUtils.isEmpty(user.getSpaceId())) {
				space.put("id", StringUtils.isEmpty(user.getSpaceId()) ? 0L : user.getSpaceId());
				String getSpaceName = StringUtils.isEmpty(user.getSpaceName()) ? String.format("%s(个人)", user.getName()) : user
						.getSpaceName();
				space.put("name", getSpaceName);
				spaces.add(space);
			} else {
				if (personalEnable) {
					space.put("id", 1L);//个人登录时spaceid设为1
					String getSpaceName = String.format("%s(个人)", user.getName());
					space.put("name", getSpaceName);
					spaces.add(space);
				}
			}

		}
		return new ModelMap("spaces", spaces);
	}

	/**
	 * 跨域询问，回复子系统是否登录
	 *
	 * @throws IOException
	 */
	@RequestMapping("/login/ask")
	@ResponseBody
	public void replyAsk() throws IOException {
		String callback = request.getParameter("callback");
		SSOToken token = SSOHelper.getToken(request);
		if (token != null) {
			String askData = request.getParameter("askData");
			if (!StringUtils.isEmpty(askData)) {
				// 下面开始验证票据，签名新的票据每一步都必须有。
				AuthToken at = SSOHelper.replyCiphertext(request, askData);
				if (at != null) {
					App app = appService.findOne(at.getApp());
					if (app != null && StringUtils.isEmpty(app.getPublicKey()) && !StringUtils.isEmpty(app.getUserControl())) {
						app = appService.findOne(app.getUserControl());
					}
					if (app == null) {
						printJsonP(callback, error("403", "非法签名"));
						return;
					}
					// 对应系统公钥验证签名
					at = at.verify(app.getPublicKey());
					if (at != null) {
						at.setUid(token.getUid());// 设置绑定用户ID
						at.setTime(token.getTime());// 设置登录时间
						// 更安全的做法是使用at.getUuid() 作为 key 设置 authToken
						// 至分布式缓存中，然后 这里根据UUID验证和赋值
						at.setData(token.getData());

						// 2、SSO 的私钥签名
						at.sign(SSOConfig.getInstance().getCenterPrivateKey());

						// 3、生成回复密文票据
						printJsonP(callback, success(at.encryptAuthToken()));
					} else {
						// 非法签名, 可以重定向至无权限界面，App自己处理
						printJsonP(callback, error("403", "非法签名"));
					}
				} else {
					// 非法签名, 可以重定向至无权限界面，App自己处理
					printJsonP(callback, error("403", "非法签名"));
				}
			}
		} else {
			// 未登录
			printJsonP(callback, error("404", "未登录"));
		}
	}

	/**
	 * 跨域询问，随子系统一起退出
	 *
	 * @throws IOException
	 */
	@RequestMapping(value = "/logout/ask", method = RequestMethod.GET)
	@ResponseBody
	public void replyAskOut() throws IOException {
		String callback = request.getParameter("callback");
		SSOToken token = SSOHelper.getToken(request);
		if (token != null) {
			// 已登录
			printJsonP(callback, error("404", "已登录"));
		} else {
			printJsonP(callback, success());
		}
	}

	/**
	 * 随子系统一起退出
	 *
	 * @throws IOException
	 */
	@RequestMapping(value = "/logout", method = RequestMethod.GET)
	@ResponseBody
	public ModelAndView logout() throws IOException {
		String callback = request.getParameter("callback");
		String returnURL = request.getParameter("returnURL");
		String baseUrl = request.getParameter("baseUrl");
		String appId = request.getParameter("appId");
		SSOToken token = SSOHelper.getToken(request);
		ModelMap modelMap = new ModelMap();
		modelMap.addAttribute("callback", callback);
		modelMap.addAttribute("returnURL", HttpUtil.decodeURL(returnURL));
		modelMap.addAttribute("appId", appId);
		modelMap.addAttribute("logoutUrls", getOtherLogoutUrls(baseUrl));

		if (token != null) {
			SSOHelper.clearLogin(request, response);
		}
		return new ModelAndView("sso/logout", modelMap);
	}

	private Set<String> getOtherLogoutUrls(String baseUrl) {
		List<App> apps = appService.findAll();
		Set<String> logoutUrls = new HashSet<>();
		for (App app : apps) {
			if (!StringUtils.isEmpty(app.getLogoutUrl())) {
				logoutUrls.add(app.getLogoutUrl());
			}
		}
		logoutUrls.add(baseUrl);
		return logoutUrls;
	}

	/**
	 * 账户中心退出
	 *
	 * @throws IOException
	 */
	@RequestMapping(value = "/logoutAccount", method = RequestMethod.GET)
	@ResponseBody
	public ModelMap logoutAccount() throws IOException {
		SSOHelper.clearLogin(request, response);
		return success();
	}
	/**
	 * 获取登录用二维码
	 *
	 * @param clientId
	 *            客户端标识，请放在cookie里面以确保一致
	 * @throws Exception
	 */
	@RequestMapping(value = "/qrcode", method = RequestMethod.GET)
	public ModelAndView getQRCode(String clientId) throws Exception {
		// 扫码链接用到的token
		com.uas.account.entity.Token loginToken = new com.uas.account.entity.Token(clientId, 300);
		tokenService.save(loginToken);
		ServletOutputStream output = response.getOutputStream();
		QRCodeUtil.encode("https://account.ubtob.com/sso/qrcode/valid?token=" + loginToken.getId(), output);
		output.flush();
		output.close();
		return null;
	}

	/**
	 * 手机端扫码后验证
	 *
	 * @param uid
	 * @param token
	 */
	@RequestMapping("/qrcode/valid")
	public ModelAndView validByQRCode(String uid, @RequestParam(required = true) String token) {
		if (StringUtils.isEmpty(uid)) {// 可能是其他设备扫描的，跳转app下载页
			Setting appDownloadUrl = settingService.findOne("appDownloadUrl");
			if (null != appDownloadUrl) {
				return new ModelAndView("redirect:" + appDownloadUrl.getValue());
			}
		}
		com.uas.account.entity.Token loginToken = tokenService.findOne(token);
		if (null != loginToken) {
			List<User> users = userService.findByUID(uid);
			if (!CollectionUtils.isEmpty(users)) {
				com.uas.account.entity.Token clientToken = new com.uas.account.entity.Token(loginToken.getBind().toString(), uid, 300);
				tokenService.save(clientToken);
				tokenService.delete(loginToken.getId());
				return new ModelAndView("sso/qr_success", success(uid));
			}
		} else {
			return new ModelAndView("sso/error", error("二维码已过期"));
		}
		return new ModelAndView("sso/error", error("未找到用户"));
	}

	/**
	 * 客户端获取扫描结果并登录
	 *
	 * @param clientId
	 * @return
	 */
	@RequestMapping(value = "/qrcode/login", method = RequestMethod.POST)
	@ResponseBody
	public ModelMap checkLoginByQRCode(String clientId) {
		com.uas.account.entity.Token clientToken = tokenService.findOne(clientId);
		if (null == clientToken) {
			return error("未扫码");
		}
		/**
		 * 正常登录 需要过滤sql及脚本注入
		 */
		WafRequestWrapper wr = new WafRequestWrapper(request);
		String appId = wr.getParameter("appId");
		String spaceId = wr.getParameter("spaceId");
		// UID or dialectUID
		String username = clientToken.getBind().toString();
		String returnUrl = wr.getParameter("returnUrl");
		if (!StringUtils.isEmpty(appId)) {
			App app = appService.findOne(appId);
			if (app != null) {
				App controlApp = StringUtils.isEmpty(app.getUserControl()) ? app : appService.findOne(app.getUserControl());
				// app是否允许个人用户登录
				boolean personalEnable = !StringUtils.isEmpty(app.getPersonalEnable()) && (Const.YES == app.getPersonalEnable());
				if (StringUtils.isEmpty(spaceId)) {
					List<UserView> users = getUserByUserName(controlApp, username);
					if (!CollectionUtils.isEmpty(users)) {
						if (users.size() > 1) {
							return success(getSpaceSelect(users, personalEnable));
						} else {
							return loginByUser(controlApp, users.get(0), returnUrl, clientToken);
						}
					} else {
						return error("您输入的账号或密码有误");
					}
				} else {
					UserView user = getUserByUserName(controlApp.getUid(), Long.parseLong(spaceId), username);
					return loginByUser(controlApp, user, returnUrl, clientToken);
				}
			}
		}
		return error("参数错误");
	}

	private ModelMap loginByUser(App app, UserView user, String returnUrl, com.uas.account.entity.Token clientToken) {
		if (user == null) {
			return error("您输入的账号或密码有误");
		} else {
			UserSpaceDetail detail = userSpaceService.findByBusinessCode(user.getSpaceUID());
			if (detail != null) {
				user.setSpaceDomain(detail.getDomain());
			}
			/*
			 * 设置登录 Cookie 最后一个参数 true 时添加 cookie 同时销毁当前 JSESSIONID 创建信任的
			 * JSESSIONID
			 */
			SSOToken st = new SSOToken(request, user.getUid());
			st.setData(JSON.toJSONString(user));
			SSOHelper.setSSOCookie(request, response, st, true);
			tokenService.delete(clientToken.getId());
			return success(new ModelMap("returnUrl", HttpUtil.decodeURL(returnUrl)));
		}
	}

	@RequestMapping(value = "/checkcode/login", method = RequestMethod.GET)
	@ResponseBody
	public void checkCode() {
		try {
			CaptchaUtil.outputCaptcha(request, response);
		} catch (ServletException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
