document
API test

授权登录

POST

Description or Example

# 核心代码 ```java /** * oauth2.0的回调方法 * @param code 授权码 * @return */ @RequestMapping("/oauth2.0/gitee/success") public String giteeSuccess(@RequestParam String code, RedirectAttributes redirectAttributes, HttpSession session) { OauthAccessTokenBO giteeAccessToken = giteeComponent.getGiteeAccessToken(code); if (giteeAccessToken == null || giteeAccessToken.getError() != null || giteeAccessToken.getErrorDescription() != null) { // 获取访问令牌失败 assert giteeAccessToken != null; String errorDescription = giteeAccessToken.getErrorDescription(); // 错误信息 redirectAttributes.addFlashAttribute("errors", new HashMap<String, String>() {{ this.put("msg", errorDescription); }}); return "redirect:http://auth.bitmall.com/login.html"; } // 获取访问令牌非常得成功, 并继续登录 OauthLoginTO oauthLoginTO = new OauthLoginTO(); BeanUtils.copyProperties(giteeAccessToken, oauthLoginTO); R info = authMemberService.oauthLogin(oauthLoginTO); if (info.getCode() != 0) { String msg = info.getData("msg", new TypeReference<String>() { }); redirectAttributes.addFlashAttribute("errors", new HashMap<String, String>(){{this.put("msg", msg);}}); return "redirect:http://auth.bitmall.com/login.html"; } // 登录成功捏 // 放入对应实现的会话域中 session.setAttribute(UserConstant.SESSION_USER_NAME, info.getData(new TypeReference<MemberVO>(){})); return "redirect:http://www.bitmall.com/"; } ``` ```java @RequestMapping("/member/member/oauthLogin") R oauthLogin(@RequestBody OauthLoginTO oauthLoginTO); ``` ```java @Component public class GiteeComponent { @Autowired private AccessTokenProperties accessTokenProperties; /** * 获取访问令牌对象 * @param code 授权码 * @return 访问令牌对象 */ public OauthAccessTokenBO getGiteeAccessToken(String code) { HashMap<String, String> map = new HashMap<String, String>() { { this.put("grant_type", accessTokenProperties.getGrantType()); this.put("code", code); this.put("client_id", accessTokenProperties.getClientId()); this.put("redirect_uri", accessTokenProperties.getRedirectUri()); this.put("client_secret", accessTokenProperties.getClientSecret()); } }; // 这里一定有响应, 不需要捕获异常 HttpClient.HttpResult result = HttpClient.request("https://gitee.com/oauth/token?grant_type=authorization_code", null, map, "UTF-8", "POST"); return JSON.parseObject(result.content, OauthAccessTokenBO.class); } } ``` ```java @RequestMapping("/oauthLogin") public R oauthLogin(@RequestBody OauthLoginTO oauthLoginTO) { MemberEntity member = memberService.register2Login(oauthLoginTO); if (member == null) { return R.error(OAUTH_LOGIN_FAILURE.getCode(), OAUTH_LOGIN_FAILURE.getMessage()); } return R.ok().setData(member); } ``` ```java @Override public MemberEntity register2Login(OauthLoginTO oauthLoginTO) { // 1. 通过访问令牌获取数据 String accessToken = oauthLoginTO.getAccessToken(); HttpClient.HttpResult result = HttpClient.httpGet("https://gitee.com/api/v5/user", null, new HashMap<String, String>() {{ this.put("access_token", accessToken); }}, "UTF-8"); String userBOJSON = result.content; // 这里一定能找出来, 不需要捕获异常 UserBO userBO = JSON.parseObject(userBOJSON, UserBO.class); if (StringUtils.isNotBlank(userBO.getMessage())) return null; // 当错误信息不为空时, 获取信息失败, 不允许注册登录 // 2. 通过id查询, 判断是否创建了该账户 LambdaQueryWrapper<MemberEntity> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(MemberEntity::getUid, userBO.getId()); MemberEntity member = this.getOne(queryWrapper); if (member == null) { // 用户尚未注册, 需要手动注册 BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); String encodePassword = bCryptPasswordEncoder.encode(userBO.getName()); long defaultLevel = memberLevelService.getDefaultLevel(); // 这里判断email和用户名是否唯一 isUsernameSignal(userBO.getLogin()); isEmailSignal(userBO.getEmail()); member = new MemberEntity(); member.setUid(userBO.getId()) .setNickname(userBO.getName()) .setUsername(userBO.getLogin()) .setPassword(encodePassword) .setHeader(userBO.getAvatarUrl()) .setEmail(userBO.getEmail()) .setLevelId(defaultLevel) .setAccessToken(oauthLoginTO.getAccessToken()) .setExpiredTime(oauthLoginTO.getExpiresIn()); this.save(member); return member; } // 有对应的用户, 不需要注册, 但是需要更新访问令牌等信息 member.setAccessToken(oauthLoginTO.getAccessToken()) .setExpiredTime(oauthLoginTO.getExpiresIn()); this.updateById(member); return member; } ``` # Bug修复 ## 访问令牌的丢失 > 这个的本质原因是微服务调用没有用@RequestBody封装成Json传输, 这导致了丢失问题 ## 数据重复问题 > 即第三方登录注册的时候, 仍可能出现邮箱冲突等问题, 需要做出对应的判断 # 知识点 ## 为什么要授权登录? > 通常情况下, 允许授权登录的一般是QQ, WeChat, 微博等社交媒体, 这些社交媒体具有非常庞大的用户量, 使用授权登录, 这些用户可以免于当前网站繁琐的注册流程, 能够吸引用户 > **人话, 懒得注册, 方便用户** ## Oauth的介绍 ### Oauth2.0是什么 > Oauth2.0: 对于用户相关的OpenAPI(如获取用户的各类信息), 为了保护用户数据的安全和隐私, 第三方网站访问用户数据都需要显式向用户争取授权 > **人话: Oauth2.0协议是一个数据共享协议, 具体来说是第三方想要共享用户数据的协议, 遵循该协议的情况下, 所有的请求或响应的参数都是一致的** ### Oauth2.0的流程 ![image.png](https://cos.easydoc.net/13568421/files/llhpsery.png) > 访问令牌在一定的时间内, 是不变的, 而且一个授权码只能获取一次访问令牌, 因此, 一段时间内, 获取的访问令牌都是一致的 ### Gitee授权登录架构 [官网教程](https://gitee.com/api/v5/oauth_doc#/list-item-3) > 从这里可以总结出来, Gitee遵循Oauth2.0协议的前提下, 整个流程和协议是一模一样的 #### 特性 ![image.png](https://cos.easydoc.net/13568421/files/llhqz8o5.png) > 一个授权码只能获取一次访问令牌 ![image.png](https://cos.easydoc.net/13568421/files/llhr1t69.png) > 不同的授权码访问令牌不同, 每一次获取的访问令牌都不一样 ### Github授权登录架构 [官网教程](https://docs.github.com/zh/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app) 其他略, 因为DNS污染, GitHub授权登录非常的麻烦, 需要挂梯子, 梯子和本地域名冲突, 因此不用这个演示 ### 为什么第一次登录需要注册 > 以为第一次登录没有任何的会员信息, 假设不注册, 每一次进来都是新的, 你充了钱都识别不出来 ## 架构说明 ### 为什么获取访问令牌在后端发请求? > 如果前端发请求获取访问令牌, 这回有一个非常严重的问题, 即使这个发的是Post请求, 不可避免的是, 授权码会被相应到前端, 且secret也会保存在前端, 很容易被别人扒出来, 这样恶意的人就会通过这些参数获取访问令牌, 从而盗取我们的信息, 非常危险 > 为了保证访问令牌的安全性, 因此在后端发请求 ### 时序图 ![image.png](https://cos.easydoc.net/13568421/files/lli23z31.png)