开发之前
需求:网站接入qq,sina微博登录,本文最后付效果图:
说明:大部分网站本身是需要用户概念的,很多操作依附于用户,而qq或微博作为一种登录方式指向用户而已,我参考了一下其他网站的做法,
一般有如下两种做法:
1,强制绑定:用户第一次通过qq登录时必须与该网站账户绑定,也就是用户必须要先有一个此网站账户才能登录成功
2,互相独立,用户第一次通过qq登录时直接重新为用户注册一个账户,如以用户名为qq_123456直接注册一个账户,与其他账户无关;
站在用户角度考虑下,可能需要更多的选择性,因此我是如下考虑的:
用户登录后在个人中心中也可设置绑定。
---------------------------------------------------------------------------------------------------
文档说明
现在大部分第三方的登录OAuth2.0为标准,所以开发流程基本都一致,一般都是一下步骤:
1,申请接入,获取appid&appkey(接入后又第三方发放)
2,用户登录第三方下发token,
3,通过token获取用户唯一标示,一般是一个openId
api地址:
qq:http://wiki.connect.qq.com/api列表
sina:http://open.weibo.com/wiki/授权机制
qq&sina也提供了java sdk
https://github.com/sunxiaowei2014/weibo4j-oauth2-beta3.1.1/
http://qzonestyle.gtimg.cn/qzone/vas/opensns/res/doc/qqConnect_Server_SDK_java_v2.0.zip
sina的虽然开源了,但里面很多代码写的有问题,用的之后需要注意
------------------------------------------------------------------------------------------------------------
开发
数据结构方面需要加以一张表用来维护登录方式和用户关联(通过openId以及登录方式确定唯一性)
网站引入,appid,appkey,回调地址的配置有不再赘述了
代码基本上参照sdk中的demo就可以了,
简单贴一下controller(springMVC架构)中的代码吧
AUthController为父类,存放一些第三方登录的通用方法,BindController为用户绑定提供方法,第三方登录的controller基本上就是一个登录跳转方法,一个回调方法,以及一些接口api的调用
public abstract class AuthController extends BaseController { @Resource private JavaMailSender mailSender; @Resource private IBAuthService authService; protected LoginUser getU(BAuth auth){ LoginUser loginUser= new LoginUser(); loginUser.setAccessToken(auth.getAccessToken()); loginUser .setIcon(auth.getIcon() == null ? IPortalConstants.defaultIconUrl : auth.getIcon()); loginUser.setId(auth.getUser().getId()); loginUser.setLoginType(auth.getType()); loginUser.setNickName(auth.getNickName() == null ? auth.getUser() .getNickName() : auth.getNickName()); loginUser.setOpenId(auth.getOpenId()); return loginUser; } protected LoginUser getU(User user){ LoginUser loginUser= new LoginUser(); loginUser .setIcon(user.getIcon() == null ? IPortalConstants.defaultIconUrl : user.getIcon()); loginUser.setId(user.getId()); loginUser.setLoginType(AuthType.bresume.getCode()); loginUser.setNickName( user.getNickName()); return loginUser; } protected boolean setUser2Session(BAuth auth){ LoginUser loginuser = this.getU(auth); SessionContextHolder.getSession().setAttribute(IPortalConstants.SESSION_KEY_LOGIN_USER, loginuser); return true; } protected boolean setUser2Session(User user){ LoginUser loginuser = this.getU(user); SessionContextHolder.getSession().setAttribute(IPortalConstants.SESSION_KEY_LOGIN_USER, loginuser); return true; } protected void sendRegisterMail(User user,String code) { PropertiesLoader loader = new PropertiesLoader("mail.properties"); Map<String, Object> map = new HashMap<String, Object>(); Email email = new Email(); email.setSender(loader.getProperty("mail.from")); email.setAddress(user.getEmail()); email.setSubject(loader.getProperty("mail.register.success.subject")); // 从模板生成 HashMap<String, Object> param = new HashMap<String, Object>(); param.put("userName", user.getUserName()); param.put("userId", user.getId()); param.put("code", code); email.setContent(MailUtils.getMailText(param, loader.getProperty("mail.register.success.content"))); map.put("email", email); MailUtils.sendMailByAsynchronousMode(map, mailSender); } protected String callBack(Model model,BAuth newAuth){ BAuth oldAuth = authService.findOne(newAuth.getOpenId(),newAuth.getType()); if (oldAuth != null && oldAuth.getUser() != null) { // 判定有登录记录 //刷新accessToken oldAuth.setAccessToken(newAuth.getAccessToken()); oldAuth.setExpiresIn(newAuth.getExpiresIn()); oldAuth.setIcon(newAuth.getIcon()); oldAuth.setNickName(newAuth.getNickName()); oldAuth.setRefreshAccessTime(new Date()); authService.update(oldAuth); this.setUser2Session(oldAuth); return "redirect:/index"; } else if(oldAuth==null) { // 判定首次登录,记录 oldAuth = new BAuth(); oldAuth.setAccessToken(newAuth.getAccessToken()); oldAuth.setExpiresIn(newAuth.getExpiresIn()); oldAuth.setCreatedTime(new Date()); oldAuth.setIcon(newAuth.getIcon()); oldAuth.setNickName(newAuth.getNickName()); oldAuth.setOpenId(newAuth.getOpenId()); oldAuth.setRefreshAccessTime(new Date()); oldAuth.setType(newAuth.getType()); authService.save(oldAuth); //用户绑定,跳转页面 model.addAttribute("openId", newAuth.getOpenId()); model.addAttribute("loginFrom", newAuth.getType()); return "site/bindAuth.jsp"; }else{ // 登录过但因某种原因为绑定账户 oldAuth.setAccessToken(newAuth.getAccessToken()); oldAuth.setExpiresIn(newAuth.getExpiresIn()); oldAuth.setIcon(newAuth.getIcon()); oldAuth.setNickName(newAuth.getNickName()); oldAuth.setRefreshAccessTime(new Date()); authService.update(oldAuth); //用户绑定,跳转页面 model.addAttribute("openId", newAuth.getOpenId()); model.addAttribute("loginFrom", newAuth.getType()); return "site/bindAuth.jsp"; } } }
@RequestMapping("/") @Controller public class BindController extends AuthController { @Resource private IUserService userService; @Resource private IBAuthService authService; @Resource private IUserVerifiedService verifiedService; @Resource private JavaMailSender mailSender; @RequestMapping("/ingore-bind") public String ingore_bind( @RequestParam(value = "loginFrom", required = true) Integer loginFrom, @RequestParam(value = "openId", required = true) String openId, ModelMap model, HttpServletResponse response) { BAuth auth = authService.findOne(openId, loginFrom); if (auth == null) { return "404"; } if (auth.getUser() == null) { User user = new User(); /* * user.setUserName(userName); user.setPassword(password); */ // user.setEmail(email); user.setNickName(auth.getNickName()); user.setIcon(auth.getIcon()); user.setRegisterType(AuthType.fromCode(loginFrom).getRt().getType()); user.setType(UserType.PERSIONAL.getCode()); user.setLevel(0); userService.registerFromAuth(user); auth.setUser(user); authService.save(auth); } this.setUser2Session(auth); return "redirect:/index"; } @RequestMapping("/login-bind") public @ResponseBody JSONObject bind( @RequestParam(value = "loginFrom", required = true) Integer loginFrom, @RequestParam(value = "openId", required = true) String openId, @RequestParam(value = "email", required = true) String email, @RequestParam(value = "password", required = true) String password, ModelMap model, HttpServletResponse response) { BAuth auth = authService.findOne(openId, loginFrom); if (auth == null) { return this.toJSONResult(false,"404"); } if (auth.getUser() == null) { try { // 登陆校验 User user = userService.loginCheck(email, password); auth.setUser(user); authService.update(auth); } catch (CoreException e) { if (e.getErrorCode() == PortalErrorCode.USER_PASSWORD_ERROR_TIMES_EXCEED_ERROR) { return this.toJSONResult(false, this.getMessage(e, e.getArgs())); } else { return this.toJSONResult(false, this.getMessage(e)); } } } this.setUser2Session(auth); return this.toJSONResult(true); } @RequestMapping("/regist-bind") public @ResponseBody JSONObject registBind( @RequestParam(value = "loginFrom", required = true) Integer loginFrom, @RequestParam(value = "openId", required = true) String openId, @RequestParam(value = "email", required = true) String email, @RequestParam(value = "password", required = true) String password, ModelMap model, HttpServletResponse response) { BAuth auth = authService.findOne(openId, loginFrom); if (auth == null) { return this.toJSONResult(false); } if (auth.getUser() == null) { User user=new User(); // user.setUserName(userName); user.setPassword(password); user.setEmail(email); try { user.setRegisterType(RegisterType.PORTAL_REGISTER.getType()); user.setType(UserType.PERSIONAL.getCode()); user.setLevel(0); user.setNickName(auth.getNickName()); user.setIcon(auth.getIcon()); userService.register(user); //生成邮箱验证码 UserVerified uv = new UserVerified(user); verifiedService.save(uv); // 发送注册成功的邮件 if (CommonUtils.isNotEmpty(user.getEmail())) { sendRegisterMail(user,uv.getCode()); } } catch (CoreException e) { return this.toJSONResult(false, this.getMessage(e)); } auth.setUser(user); authService.save(auth); } this.setUser2Session(auth); return this.toJSONResult(true); } }
@RequestMapping("/") @Controller public class QQController extends AuthController { @Resource private IBAuthService authService; @Resource private IUserService userService; @RequestMapping("/qqlogin") public void index(HttpServletRequest request, HttpServletResponse response, Model model) throws IOException { response.setContentType("text/html;charset=utf-8"); try { response.sendRedirect(new Oauth().getAuthorizeURL(request)); LOGGER.info("login by qq"); } catch (QQConnectException e) { e.printStackTrace(); } } @RequestMapping("/qq_callback") public String callback(HttpServletRequest request, HttpServletResponse response, Model model) { try { AccessToken accessTokenObj = (new Oauth()) .getAccessTokenByRequest(request); String accessToken = null, openID = null; long tokenExpireIn = 0L; if (accessTokenObj.getAccessToken().equals("")) { LOGGER.error("QQ Login failed,caused by 没有获取到响应参数"); return "404"; } accessToken = accessTokenObj.getAccessToken(); tokenExpireIn = accessTokenObj.getExpireIn(); LOGGER.info("Get accessToken from qq,accessToken:" + accessToken + ",tokenExpireIn" + tokenExpireIn); // 利用获取到的accessToken 去获取当前用的openid OpenID openIDObj = new OpenID(accessToken); openID = openIDObj.getUserOpenID(); LOGGER.info("利用获取到的accessToken:" + accessToken + ", 去获取到当前用户openid:" + openID + "."); String icon = null, nickName = null; // 去获取用户在Qzone的昵称等信息 UserInfo qzoneUserInfo = new UserInfo(accessToken, openID); UserInfoBean userInfoBean = qzoneUserInfo.getUserInfo(); if (userInfoBean.getRet() == 0) { nickName = userInfoBean.getNickname(); // userInfoBean.getGender(); icon = userInfoBean.getAvatar().getAvatarURL30(); // userInfoBean.getAvatar().getAvatarURL50(); // userInfoBean.getAvatar().getAvatarURL100(); } else { LOGGER.error("很抱歉,我们没能正确获取到您的信息,原因是:" + userInfoBean.getMsg()); } BAuth newAuth = new BAuth(); newAuth.setAccessToken(accessToken); newAuth.setExpiresIn(tokenExpireIn); newAuth.setIcon(icon); newAuth.setNickName(nickName); newAuth.setOpenId(openID); newAuth.setType(AuthType.QQ.getCode()); return this.callBack(model, newAuth); // 通过openid判断首次登录与否 /* BAuth bauth = authService.findOne(openID, AuthType.QQ.getCode()); if (bauth != null && bauth.getUser() != null) { // 判定有登录记录 //刷新accessToken bauth.setAccessToken(accessToken); bauth.setExpiresIn(tokenExpireIn); bauth.setIcon(icon); bauth.setNickName(nickName); bauth.setRefreshAccessTime(new Date()); authService.update(bauth); this.setUser2Session(bauth); return "redirect:/index"; } else if(bauth==null) { // 判定首次登录,记录 bauth = new BAuth(); bauth.setAccessToken(accessToken); bauth.setCreatedTime(new Date()); bauth.setExpiresIn(tokenExpireIn); bauth.setIcon(icon); bauth.setNickName(nickName); bauth.setOpenId(openID); bauth.setRefreshAccessTime(new Date()); bauth.setType(AuthType.QQ.getCode()); authService.save(bauth); //用户绑定,跳转页面 model.addAttribute("openId", openID); model.addAttribute("loginFrom", AuthType.QQ.getCode()); return "site/bindAuth.jsp"; }else{ // 登录过但因某种原因为绑定账户 bauth.setAccessToken(accessToken); bauth.setExpiresIn(tokenExpireIn); bauth.setIcon(icon); bauth.setNickName(nickName); bauth.setRefreshAccessTime(new Date()); authService.update(bauth); //用户绑定,跳转页面 model.addAttribute("openId", openID); model.addAttribute("loginFrom", AuthType.QQ.getCode()); return "site/bindAuth.jsp"; }*/ } catch (QQConnectException e) { e.printStackTrace(); } return "redirect:/index"; } @RequestMapping("/qqss") public void talk(HttpServletRequest request, HttpServletResponse response, Model model) throws IOException { response.setContentType("text/html;charset=utf-8"); request.setCharacterEncoding("utf-8"); String con = request.getParameter("con"); HttpSession session = request.getSession(); String accessToken = (String) session.getAttribute("demo_access_token"); String openID = (String) session.getAttribute("demo_openid"); System.out.println(accessToken); System.out.println(openID); // 请开发者自行校验获取的con值是否有效 if (con != "") { Topic topic = new Topic(accessToken, openID); try { GeneralResultBean grb = topic.addTopic(con); if (grb.getRet() == 0) { response.getWriter() .println( "<a href="http://www.qzone.com" target="_blank">您的说说已发表成功,请登录Qzone查看</a>"); } else { response.getWriter().println( "很遗憾的通知您,发表说说失败!原因: " + grb.getMsg()); } } catch (QQConnectException e) { System.out.println("抛异常了?"); } } else { System.out.println("获取到的值为空?"); } } }
@RequestMapping("/") @Controller public class SinaController extends AuthController { @Resource private IBAuthService authService; @Resource private IUserService userService; @RequestMapping("/sinalogin") public void index(HttpServletRequest request, HttpServletResponse response, Model model) throws IOException { response.setContentType("text/html;charset=utf-8"); try { response.sendRedirect(new Oauth().authorize("code")); LOGGER.info("login by weibo"); } catch (WeiboException e) { e.printStackTrace(); } } @RequestMapping("/weibo_callback") public String callback(HttpServletRequest request, HttpServletResponse response, Model model) throws IOException { try { Oauth oauth = new Oauth(); String code = request.getParameter("code"); LOGGER.info("code: " + code); AccessToken accessTokenObj = oauth.getAccessTokenByCode(code); if (accessTokenObj == null) { LOGGER.error("AccessToken 获取失败,code:" + code); } String accessToken = accessTokenObj.getAccessToken(); String openId = accessTokenObj.getUID(); String expireInStr = accessTokenObj.getExpireIn(); Users um = new Users(accessToken); User user = um.showUserById(openId); LOGGER.info(user.toString()); BAuth newAuth = new BAuth(); newAuth.setAccessToken(accessToken); newAuth.setExpiresIn(expireInStr != null ? Long .parseLong(expireInStr) : 3600); newAuth.setIcon(user.getAvatarLarge()); newAuth.setNickName(user.getScreenName()); newAuth.setOpenId(openId); newAuth.setType(AuthType.SINA.getCode()); return this.callBack(model, newAuth); } catch (WeiboException e) { if (401 == e.getStatusCode()) { LOGGER.error("Unable to get the access token."); } else { e.printStackTrace(); } } return "redirect:/index"; } }
-------------------------------------------------------------------------------------------------------------
页面效果
注:该流程为用户首次使用第三方登录时流程
1,登录页面放置第三方登录图标
2,点击图标接入第三方接口,可跳转至第三方登录界面
3,第三方登录完成,用户账户绑定
4,用户登录后,可在个人设置中管理第三方登录的绑定