package com.uas.sso.controller; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.uas.account.AccountConfig; import com.uas.sso.*; import com.uas.sso.common.util.HttpUtil; import com.uas.sso.core.Const; import com.uas.sso.entity.*; import com.uas.sso.entity.Token; import com.uas.sso.service.*; import com.uas.sso.util.AccountTypeUtils; import com.uas.sso.util.BeanUtil; import com.uas.sso.util.CaptchaUtil; import com.uas.sso.util.encry.HmacUtils; import com.uas.sso.web.waf.request.WafRequestWrapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.ui.ModelMap; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.ModelAndView; import java.io.IOException; import java.util.*; /** * 登录controller * * @author wangmh * @date 2018/1/5 */ @RequestMapping("/sso/login") @RestController public class LoginController extends BaseController { /** * 密码输错3次 */ private static final int PWD_ERROR_FIVE_TIME = 5; /** * 密码输错3次 */ private static final int PWD_ERROR_THREE_TIME = 3; /** * 登录验证码存session的key值 */ private static final String LOGIN_CAPTCHA = "loginCaptcha"; /** * 优软云首页地址 */ private static final String HOME_PAGE = "https://www.ubtob.com/"; @Autowired private AppService appService; @Autowired private UserService userService; @Autowired private UserspaceService userspaceService; @Autowired private UserAccountService userAccountService; @Autowired private PersonalAccountService personalAccountService; @RequestMapping(method = RequestMethod.POST) public ModelMap login() { // 获取登录信息 WafRequestWrapper wr = new WafRequestWrapper(request); String appId = wr.getParameter("appId"); String spaceUU = wr.getParameter("spaceUU"); String username = StringUtils.trimAllWhitespace(wr.getParameter("username")); String password = wr.getParameter("password"); String captcha = wr.getParameter("captcha"); String returnUrl = wr.getParameter("returnUrl"); String baseUrl = wr.getParameter("baseUrl"); // 校验空参数 if (StringUtils.isEmpty(username)) { return error("用户名不能为空"); } if (StringUtils.isEmpty(password)) { return error("密码不能为空"); } // 设置baseUrl if (!StringUtils.isEmpty(baseUrl)) { request.getSession().setAttribute("baseUrl", baseUrl); } // 校验appId,appId为空的话默认为sso appId = StringUtils.isEmpty(appId) ? SSOHelper.getSSOService().getConfig().getAppName() : appId; App app = appService.findOne(appId); if (app == null) { return error("应用id不存在"); } // 校验returnUrl,为空默认为优软云 if (StringUtils.isEmpty(returnUrl)) { returnUrl = HOME_PAGE; } // 获取用户基本信息 User user = userService.findByUsername(username); if (user == null) { return error("用户名或密码错误"); } // 校验账户密码输错次数 int pwdErrorCount = user.getUserRecord() == null ? 0 : user.getUserRecord().getPwdErrorCount(); Object loginCaptcha = request.getSession().getAttribute(LOGIN_CAPTCHA); String checkCode = loginCaptcha == null ? "" : loginCaptcha.toString(); if (pwdErrorCount >= PWD_ERROR_FIVE_TIME) { return error("403", "密码错误次数已达上限,今日无法登陆"); } // 校验账号是否被锁定,5次输错密码 if (pwdErrorCount >= PWD_ERROR_THREE_TIME && StringUtils.isEmpty(captcha)) { return error("404", "验证码不能为空"); } if (pwdErrorCount >= PWD_ERROR_THREE_TIME && !captcha.equalsIgnoreCase(checkCode)) { return error("验证码错误"); } // 校验密码 String encryPwd = userService.getEncryPassword(Const.ENCRY_FORMAT, password, user.getSalt()); if (!encryPwd.equals(user.getPassword())) { pwdErrorCount = inputErrorPwd(user.getUserRecord()); return error("您输入的账号或密码有误").addAttribute("errorCount", pwdErrorCount); } // 校验密码强度,如果和存储的不同,则保存 int strength = checkPasswordLevel(password).getValue(); if (strength != user.getPasswordLevel()) { user.setPasswordLevel(strength); userService.save(user); } // 登录 App controlApp = StringUtils.isEmpty(app.getUserControl()) ? app : appService.findOne(app.getUserControl()); boolean personalEnable = Const.YES == controlApp.getPersonalEnable(); if (StringUtils.isEmpty(spaceUU)) { /*企业uu号为空,让用户选择企业*/ // 找到用户账号信息 List userAccounts = userAccountService.findByUserUU(appId, user.getUserUU()); // 没有记录 if (CollectionUtils.isEmpty(userAccounts)) { // 没有记录如果当前应用允许个人账号的话,查找个人账号 UserAccount userAccount = personalAccountService.findOneByUserUU(appId, user.getUserUU()); if (!personalEnable) { // 不支持个人账号则跳转优软云 returnUrl = HOME_PAGE; } return loginByUser(userAccount, returnUrl, user.getUserRecord()); } // 应用允许个人账号,并且账号未绑定企业,或者只绑定了一个企业,直接登录 if (userAccounts.size() == 1) { return loginByUser(userAccounts.get(0), returnUrl, user.getUserRecord()); } // 返回企业id和名称 return success(getSpaceSelect(userAccounts, personalEnable)); } else if (personalEnable && Long.valueOf(spaceUU).equals(Const.SPACEUU_PERSONAL)) { // 使用个人账号登录 UserAccount userAccount = personalAccountService.findOneByUserUU(appId, user.getUserUU()); return loginByUser(userAccount, returnUrl, user.getUserRecord()); } else { // 带企业登录 UserAccount userAccount = getUserAccountByUserName(controlApp.getUid(), username, spaceUU); return loginByUser(userAccount, returnUrl, user.getUserRecord()); } } /** * 根据企业uu号和用户uu号获取登录token,有效期1分钟 * @param enUU 企业uu号 * @param userUU 用户uu号 * @param timestamp 请求时间,用于判断请求是否有效,有效期10秒 * @param signature 加密信息,使用企业私钥加密请求参数 * @return tokenId,token存入ModelMap对象,值为 {userUU:用户uu号,spaceUU:企业uu号} */ @RequestMapping(value = "/getToken", method = RequestMethod.GET) public ModelMap getToken(String enUU, String userUU, @RequestParam(name = "_timestamp") Long timestamp, @RequestParam(name = "_signature") String signature) { ModelMap map = new ModelMap(); // 根据企业uu号找到企业信息 Userspace userspace = userspaceService.findOne(Long.valueOf(enUU)); if (userspace == null) { return error("没有找到企业"); } // 校验请求 checkRequest(userspace.getAccessSecret()); // 将企业信息、企业管理员信息写入SystemSession User user = userService.findOne(Long.valueOf(userUU)); if (user == null) { return error("没有找到用户"); } if (!userspace.getUsers().contains(user)) { return error("该用户不在当前企业"); } map.put("spaceUU", enUU); map.put("userUU", userUU); // token有效期,单位秒 int expires_in = 1 * 60; Token token = new Token(map, expires_in); tokenService.save(token); return success(token.getId()); } /** * token代理页面 * @return */ @RequestMapping(value = "/proxy", method = RequestMethod.GET) public ModelAndView loginProxyByToken() { WafRequestWrapper wr = new WafRequestWrapper(request); String returnUrl = wr.getParameter("returnURL"); String appId = wr.getParameter("appId"); String token = wr.getParameter("token"); String baseUrl = wr.getParameter("baseURL"); String isLoginAll = wr.getParameter("isLoginAll"); ModelMap data = new ModelMap(); data.put("returnUrl", returnUrl); data.put("appId", appId); data.put("token", token); data.put("baseUrl", baseUrl); data.put("isLoginAll", isLoginAll == null ? true : isLoginAll); return new ModelAndView("/sso/proxyByToken", data); } /** * erp和uu互联跳转 * 代理登录,根据tokenId拿到当前用户登录的用户uu号和企业uu号进行登录 * * @param token tokenId * @param appId 应用id * @param returnUrl 跳转页面 * @param baseUrl 而外登录接口 * @param isLoginAll 是否登录默认应用 * @return */ @RequestMapping(value = "/proxy", method = RequestMethod.POST) public ModelMap loginProxyByToken(String token, String appId, String returnUrl, String baseUrl, @RequestParam(defaultValue = "true") boolean isLoginAll) { Token tk = tokenService.findOne(token); if (null != tk) { JSONObject data = JSON.parseObject(JSON.toJSONString(tk.getBind())); Long userUU = data.getLong("userUU"); Long spaceUU = data.getLong("spaceUU"); request.getSession().setAttribute("baseUrl", baseUrl); App app = appService.findOne(appId); if (app != null) { app = StringUtils.isEmpty(app.getUserControl()) ? app : appService.findOne(app.getUserControl()); } if (app == null) { app = appService.findOne(AccountConfig.ACCOUNT_CENTER); } UserAccount userAccount = userAccountService.findOneByUserUU(app.getUid(), userUU, spaceUU); return loginByUser(userAccount, returnUrl, null); } return error("验证信息已过期"); } /** * 密码输错处理 * * @param userRecord 用户登录记录 * @return */ private int inputErrorPwd(UserRecord userRecord) { // 密码输错次数+1 int pwdErrorCount = userRecord.getPwdErrorCount(); userRecord.setPwdErrorCount(++pwdErrorCount); userService.save(userRecord); // 设置返回值 return pwdErrorCount; } /** * 根据用户名获得用户账号信息 * * @param appId 应用 * @param username 用户名 * @return */ private List getUserAccountByUserName(String appId, String username) { String type = AccountTypeUtils.getAccountType(username); if (AccountTypeUtils.MOBILE.equals(type)) { // 手机号登录 return userAccountService.findByMobile(appId, username); } if (AccountTypeUtils.EMAIL.equals(type)) { // 邮箱登录 return userAccountService.findByEmail(appId, username); } if (AccountTypeUtils.UU_NUMBER.equals(type)) { // uu号登录 return userAccountService.findByUserUU(appId, Long.valueOf(username)); } // 其余情况 return null; } /** * 根据用户名和企业uu找到用户信息 * * @param appId 应用 * @param username 用户名 * @param spaceUU 企业uu号 * @return */ private UserAccount getUserAccountByUserName(String appId, String username, String spaceUU) { String type = AccountTypeUtils.getAccountType(username); if (AccountTypeUtils.MOBILE.equals(type)) { // 手机号登录 return userAccountService.findOneByMobile(appId, username, Long.valueOf(spaceUU)); } if (AccountTypeUtils.EMAIL.equals(type)) { // 邮箱登录 return userAccountService.findOneByEmail(appId, username, Long.valueOf(spaceUU)); } if (AccountTypeUtils.UU_NUMBER.equals(type)) { // uu号登录 return userAccountService.findOneByUserUU(appId, Long.valueOf(username), Long.valueOf(spaceUU)); } // 其余情况 return null; } /** * 用户信息没问题,直接登录 * * @param userAccount 用户账号信息 * @param returnUrl 跳转url * @param userRecord 用户登录记录,便于记录登录时间,不能直接new出来 * @return */ private ModelMap loginByUser(UserAccount userAccount, String returnUrl, UserRecord userRecord) { /* * 设置登录 Cookie 最后一个参数 true 时添加 cookie 同时销毁当前 JSESSIONID * 创建信任的 JSESSIONID */ SSOToken st = new SSOToken(request, userAccount.getMobile()); st.setData(JSON.toJSONString(userAccount)); SSOHelper.setSSOCookie(request, response, st, true); // 设置登录时间,并将密码输错次数设为0,为空则不设置 if (userRecord != null) { userRecord.setLastLoginTime(System.currentTimeMillis()); userRecord.setPwdErrorCount(0); userService.save(userRecord); } // 设置返回值,通知各个应用用户已经登录 ModelMap data = new ModelMap(); data = addOtherAppRequestData(userAccount, data, request.getSession().getAttribute("baseUrl"), true); data.put("returnUrl", HttpUtil.decodeURL(returnUrl)); return success(data); } private ModelMap addOtherAppRequestData(UserAccount userAccount, ModelMap data, Object loginUrl, boolean isLoginAll) { List apps = appService.findAll(); List loginUrls = new ArrayList<>(); boolean hasLoginUrl = false; if (isLoginAll) { for (App app : apps) { if (StringUtils.isEmpty(app.getLoginUrl())) { continue; } if (app.getLoginUrl().equals(loginUrl)) { hasLoginUrl = true; } loginUrls.add(app.getLoginUrl()); } } // 添加baseUrl if (!hasLoginUrl && !StringUtils.isEmpty(loginUrl)) { loginUrls.add(loginUrl.toString()); } data.put("loginUrls", loginUrls); // 添加传递数据 JSONObject jsonObject = JSON.parseObject(JSON.toJSONString(userAccount)); Integer maxage = (Integer) request.getAttribute(SSOConfig.SSO_COOKIE_MAXAGE); jsonObject.put("maxage", maxage); data.put("data", jsonObject); return data; } /** * 获取选择企业信息(id:企业uu号,name:名称) * * @param userAccounts 用户账户信息 * @param personalEnable 该应用是否允许个人账户 * @return */ private ModelMap getSpaceSelect(List userAccounts, boolean personalEnable) { List> spaces = new ArrayList>(); Map space = null; // 设置带企业账号 for (UserAccount userAccount : userAccounts) { space = new HashMap(2); space.put("id", userAccount.getSpaceUU()); space.put("name", userAccount.getSpaceName()); spaces.add(space); } // 设置个人账号 if (personalEnable) { space = new HashMap(2); space.put("id", Const.SPACEUU_PERSONAL); space.put("name", String.format("%s(个人)", userAccounts.get(0).getVipName())); spaces.add(space); } return new ModelMap("spaces", spaces); } @RequestMapping(value = "/checkCode", method = RequestMethod.GET) public void checkCode() { try { // 获取验证码 CaptchaUtil.outputCaptcha(request, response, LOGIN_CAPTCHA); } catch (IOException e) { e.printStackTrace(); } } /** * 获得密码输错次数 * * @param username 用户名 * @return */ @RequestMapping(value = "/getPwdErrorCount", method = RequestMethod.GET) public ModelMap getPwdErrorCount(String username) { if (StringUtils.isEmpty(username)) { return error("用户名不能为空"); } return success(userService.getPwdErrorCount(username.trim())); } /** * 退出接口,测试接口 * @return */ @RequestMapping(value = "/logoutAccount", method = RequestMethod.GET) public ModelMap logoutAccount() { SSOHelper.clearLogin(request, response); return success(); } /** * 判断是否登录, 测试接口 * @return */ @RequestMapping(value = "/isLogin", method = RequestMethod.GET) public ModelMap isLogin() { SSOToken token = SSOHelper.getToken(request); return success(new ModelMap("isLogin", token != null)); } /** * 页面样式 * @param appId 应用id * @return */ @RequestMapping(value = "/page/style", method = RequestMethod.GET) public ModelMap getPageStyle(@RequestParam(defaultValue = "sso") String appId) { App app = appService.findOne(appId); App defaultApp = appService.findOne("sso"); PageStyle pageStyle = app == null ? defaultApp.getPageStyle() : app.getPageStyle(); BeanUtil.copyProperties(pageStyle, defaultApp.getPageStyle(), true); return success(defaultApp.getPageStyle()); } /** * 跨域询问,回复子系统是否登录 * * @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 ModelMap logout() throws IOException { System.err.print(request.getContextPath()); System.err.print(request.getHeaderNames().toString()); System.err.print(request.getCookies()); String callback = request.getParameter("callback"); String returnURL = request.getParameter("returnURL") == null ? HOME_PAGE : request.getParameter("returnURL"); String baseUrl = request.getParameter("baseUrl"); String appId = request.getParameter("appId") == null ? "sso" : 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 success(modelMap); } private Set getOtherLogoutUrls(String baseUrl) { List apps = appService.findAll(); Set logoutUrls = new HashSet<>(); for (App app : apps) { if (!StringUtils.isEmpty(app.getLogoutUrl())) { logoutUrls.add(app.getLogoutUrl()); } } logoutUrls.add(baseUrl); return logoutUrls; } }