開發之前
需求:網站接入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 {@Resourceprivate JavaMailSender mailSender;@Resourceprivate 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 {@Resourceprivate IUserService userService;@Resourceprivate IBAuthService authService;@Resourceprivate IUserVerifiedService verifiedService;@Resourceprivate 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 {@Resourceprivate IBAuthService authService;@Resourceprivate 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 去獲取當前用的openidOpenID 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) {// 判定有登錄記錄//刷新accessTokenbauth.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 {@Resourceprivate IBAuthService authService;@Resourceprivate 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,用戶登錄后,可在個人設置中管理第三方登錄的綁定
?