社交登录
使用oauth2技术。
需要解决的问题一:
在社交登录授权登录后,该gitee、weibo用户需要关联本ershoumall系统的会员账户信息ershoumall-member,故需远程调用oauth2login方法。
由于我在远程调用方法中声明了两个实体类,且使用了两次@RequestBody,导致控制台报500错误,仅仅简单显示远程调用具体方法出现问题。没有其他任何说明。如下:
@PostMapping("member/member/oauth2/login")
public R oauthlogin(@RequestBody GiteeVo giteeVo) throws Exception;
错误举例如下:
@PostMapping(“member/member/oauth2/login”)
public R oauthlogin(@RequestBody AccessToken token,@RequestBody UserInfo) throws Exception;由于注解底层原理特性,一个方法仅允许使用一次@RequestBody注解。故将该两个实体类属性BeanUtils.copyProperties()到新对象giteeVo中。
@Data
public class GiteeVo {
private String access_token;
private String token_type;
private long expires_in;
private String refresh_token;
private String scope;
private long created_at;
// 注意,我合并了。
private long id;
private String login;
private String name;
private String avatar_url;
private String url;
private String html_url;
private String followers_url;
private String following_url;
private String gists_url;
private String starred_url;
private String subscriptions_url;
private String organizations_url;
private String repos_url;
private String events_url;
private String received_events_url;
private String type;
private String blog;
private String weibo;
private String bio;
private long public_repos;
private long public_gists;
private long followers;
private long following;
private long stared;
private long watched;
private String updated_at;
private String email;
}
- OAuth2Controller
@GetMapping("/oauth2/gitee/success")
public String gitee(@RequestParam("code") String code, HttpSession session, HttpServletResponse servletResponse, HttpServletRequest request) throws Exception {
Map<String, String> header = new HashMap<>();
Map<String, String> query = new HashMap<>();
Map<String, String> map = new HashMap<>();
map.put("client_id", "6d977af422e89d8b2b58811dcfxx5033ee401ee473562fa66426c59edc173ba9");
map.put("client_secret", "b07d4364a9a171602332cb3bd20cxxa60ade23526e8fb2c21900b472f8a860db");
map.put("grant_type", "authorization_code");
map.put("redirect_uri", "http://auth.xxmall.com/oauth2/gitee/success");
// todo 检查以上是否加.auth
map.put("code", code);
HttpResponse response = HttpUtils.doPost("https://gitee.com", "/oauth/token", "post", header, query, map);
if (response.getStatusLine().getStatusCode() == 200) {
String tokenJson = EntityUtils.toString(response.getEntity());
log.info(tokenJson);
GiteeAccessToken token = JSON.parseObject(tokenJson, GiteeAccessToken.class);
Map<String, String> query2 = new HashMap<>();
query2.put("access_token", token.getAccess_token());
HttpResponse response_userInfo = HttpUtils.doGet("https://gitee.com", "/api/v5/user", "get", new HashMap(), query2);
String giteeUserInfoJson = EntityUtils.toString(response_userInfo.getEntity());
log.info(giteeUserInfoJson);
GiteeUserInfo giteeUserInfo = JSON.parseObject(giteeUserInfoJson, GiteeUserInfo.class);
// 针对Gitee,个人独特的处理
GiteeVo giteeVo = new GiteeVo();
BeanUtils.copyProperties(token, giteeVo);
BeanUtils.copyProperties(giteeUserInfo, giteeVo);
R oauthlogin = memberFeignService.oauthlogin(giteeVo);
if (oauthlogin.getCode() == 0) {
MemberRespVo data = oauthlogin.getData("data", new TypeReference<MemberRespVo>() {
});
log.info("登录成功:用户:{}", data.toString());
return "redirect:http://xxshoumall.com";
} else {
return "redirect:http://auth.xxshoumall.com/login.html";
}
} else {
return "redirect:http://auth.xxshoumall.com/login.html";
}
}
- MemberController
@PostMapping("/oauth2/login")
public R oauthlogin(@RequestBody GiteeVo giteeVo) throws Exception {
MemberEntity entity = memberService.login2(giteeVo);
if(entity!=null){
return R.ok().setData(entity);
}else{
return R.error(BizCodeEnum.LOGINACCT_PASSWORD_INVAILD_EXCEPTION.getCode(),BizCodeEnum.LOGINACCT_PASSWORD_INVAILD_EXCEPTION.getMessage());
}
}
3.MemberServiceImpl
/**
* 社交登录
* @param
* @return
* @throws Exception
*/
@Override
public MemberEntity login2(GiteeVo giteeVo) throws Exception {
String uid = String.valueOf(giteeVo.getId()); // 老师的uid是gitee的id,注意类型转换
//1、判断当前社交用户是否已经登录过系统;
MemberDao memberDao = this.baseMapper;
MemberEntity memberEntity = memberDao.selectOne(new QueryWrapper<MemberEntity>().eq("social_uid", uid)); // 实际就是gitee中仅存的id,只是我没有改名
if (memberEntity != null) {
//这个用户已经注册
MemberEntity update = new MemberEntity();
update.setId(memberEntity.getId());
update.setAccessToken(giteeVo.getAccess_token());
update.setExpiresIn(giteeVo.getExpires_in());
memberDao.updateById(update);
memberEntity.setAccessToken(giteeVo.getAccess_token());
memberEntity.setExpiresIn(giteeVo.getExpires_in());
System.out.println("140...");
return memberEntity;
}else{
//2、没有查到当前社交用户对应的记录我们就需要注册一个
MemberEntity regist = new MemberEntity();
try{
Map<String,String> query = new HashMap<>();
query.put("access_token",giteeVo.getAccess_token());
// query.put("uid",String.valueOf(giteeUserInfo.getId())); // gitee这里请求参数不需要携带uid,微博才需要携带
HttpResponse response = HttpUtils.doGet("https://gitee.com", "/api/v5/user", "get", new HashMap<String, String>(), query);
if(response.getStatusLine().getStatusCode() == 200){
//查询成功
String json = EntityUtils.toString(response.getEntity());
JSONObject jsonObject = JSON.parseObject(json);
String name = jsonObject.getString("name");
String email = jsonObject.getString("email");
regist.setNickname(name);
regist.setEmail(email);
}
}catch (Exception e){
System.out.println("----------MemberServiceImpl-login(GiteeAccessToken giteeAccessToken,GiteeUserInfo giteeUserInfo)--------------------------"+e.getMessage());
}
regist.setSocialUid(String.valueOf(giteeVo.getId()));
regist.setAccessToken(giteeVo.getAccess_token());
regist.setExpiresIn(giteeVo.getExpires_in());
memberDao.insert(regist);
System.out.println("171...");
return regist;
}
}
需要解决的问题二:session共享(不同步)问题
温顾:session是存储在服务端的一个对象,服务器创建session对象,传给浏览器一个jsessionId,浏览器通过cookie这个对象存入jsessionId,从此浏览器会携带此cookie对象去请求。
但是:微服务不同服务模块间,session数据不共享,如下实例:
cookie只存在于auth.ershoumall.com该Domain下,在ershoumall.com下不存在,该如何解决(不能跨不同域名共享,或同域名但分布式部署情况不同服务器)捏?
法一:session复制同步。在大型分布式集群系统情况下,它将占用大量玩过带宽,每一台服务器冗余存储全量数据,消耗巨大性能低下。
法二:仅存在客户端cookie中,非常不安全。
法三(较好):利用nginx负载均衡hash一致性,只需要该nginx配置,可以支持水平扩展。缺点:web-server一旦重启会导致session丢失。当服务器水平扩展后,一部分用户路由不到正确的session
法四(采用)统一存储:负载均衡到server再到redis/db集群,后台统一存储session
使用SpringSession,即可帮我将jsessionId自动存入已连接的redis中。
- 配置文件中编写spring.session.store为redis,且连好redis
- 各服务启动类标明@SessionRedis
- 各服务模块分别声明@Confugration配置sessio父子域为.ershoumall.com。
如果访问的父域名都不同,需要单点登录。
手机号/账号密码登录
简洁。