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)