这篇文章主要说明一下在线教育系统的登录问题
主要介绍 - 单点登录概念、微信扫面登录 (Java 后端)
文章目录
1. 单点登录
1.1 简介
- 单点登录 (Single Sign On – SSO)
- 在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统
1.2 常见的三种单点登录方式
1.2.1 session 广播机制实现
- 登录成功后,把用户数据放到 session 里面;
- 判断是否登录,从 session 中获取数据,可以获取到登录
session.setAttribute("user",user);
session.getAttribute("user");
1.2.2 使用 cookie + redis 实现
-
在项目中任何一个模块进行登录,登录之后,把数据放到两个地方
redis:在 key 生成唯一随机值(ip、用户id等等);在 value 中存储用户数据 cookie:把 redis 里面生成的 key 值放到 cookie 里面
-
访问项目中的其他模块,发送请求带着 cookie 进行发送,获取 redis 值
把 cookie 获取值,到 redis 进行查询, 根据 key 进行查询,如果查询到数据就是登录
1.2.3 使用 token 实现
-
在项目中某个模块进行登录,登录之后,按照一定的规则生成字符串,把登录之后的用户信息包含到生成字符串中,返回字符串
可以把字符串通过 cookie 返回; 把字符串通过地址栏返回
-
再去访问项目其他模块,每次访问在地址栏带着生成字符串,可以根据地址栏字符串获取用户信息
1.2.4 JWT
- token 是按照一定规则生成字符串,包含用户信息
- 规则是怎么样的,不一定
- 一般采用官方规则(JWT)
JWT 生成字符串包含三部分:
- JWT 头信息;
- 有效载荷,包含主体信息(用户信息);
- 签名哈希(防伪标志)
2. 登录
- 实现界面
2.1 普通登录
- 获取登录手机号、密码
- 判断手机号、密码是否为空
- 判断手机号是否正确 – 和数据库中的手机号进行比较
- 判断密码似乎否正确 – 把输入的密码进行加密,再和数据库密码进行比较
- 使用 JWT 工具类,生成并返回 token
//登录的方法
@Override
public String login(UcenterMember member) {
//获取登录手机号和密码
String mobile = member.getMobile();
String password = member.getPassword();
//手机号和密码非空判断
if(StringUtils.isEmpty(mobile) || StringUtils.isEmpty(password)) {
throw new GuliException(20001,"登录失败");
}
//判断手机号是否正确
QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>();
wrapper.eq("mobile",mobile);
UcenterMember mobileMember = baseMapper.selectOne(wrapper);
//判断查询对象是否为空
if(mobileMember == null) {//没有这个手机号
throw new GuliException(20001,"登录失败");
}
//判断密码
//因为存储到数据库密码肯定加密的
//把输入的密码进行加密,再和数据库密码进行比较
//加密方式 MD5
if(!MD5.encrypt(password).equals(mobileMember.getPassword())) {
throw new GuliException(20001,"登录失败");
}
//登录成功
//生成token字符串,使用jwt工具类
String jwtToken = JwtUtils.getJwtToken(mobileMember.getId(), mobileMember.getNickname());
return jwtToken;
}
2.2 微信扫描登录
2.2.1 OAuth2
- OAuth2 是针对特定问题一种解决方案
- 主要解决两个问题: 1.开放系统间授权 2.分布式访问问题
开放系统间授权方式:
- 密码用户名复制、
- 通用开发者key(万能钥匙)、
- 办法令牌
分布式访问(单点登录)
1. 登陆成功之后,按照一定的规则生成字符串,字符串包含用户信息
2. 把生成的字符串通过路径传播,或者cookie
3. 后面再发送请求的时候,每次带着字符串进行发送;获取字符串,从字符串中获取用户信息登录
OAuth2解决方案:令牌机制,按照一定规则生成字符串,字符串中包含用户信息
2.2.2 准备工作
简而言之:获得 app_id 和 app_secret
# 微信开放平台 -- appid
wx.open.app_id=wxed9954c01bb89b47
# 微信开放平台 -- appsecret
wx.open.app_secret=a7482517235173ddb4083788de60b90e
# 微信开放平台 -- 重定向url
wx.open.redirect_url=http://guli.shop/api/ucenter/wx/callback
2.2.3 生成微信扫描二维码
- 直接请求微信提供固定的地址(向地址后面拼接参数)
- 返回地址(地址中就是二维码)
// 生成微信扫描二维码
@GetMapping("login")
public String getWxCode() {
//固定地址,后面拼接参数
// String url = "https://open.weixin.qq.com/" +
// "connect/qrconnect?appid="+ ConstantWxUtils.WX_OPEN_APP_ID+"&response_type=code";
// 微信开放平台授权baseUrl %s相当于?代表占位符
String baseUrl = "https://open.weixin.qq.com/connect/qrconnect" +
"?appid=%s" +
"&redirect_uri=%s" +
"&response_type=code" +
"&scope=snsapi_login" +
"&state=%s" +
"#wechat_redirect";
//对redirect_url进行URLEncoder编码
String redirectUrl = ConstantWxUtils.WX_OPEN_REDIRECT_URL;
try {
redirectUrl = URLEncoder.encode(redirectUrl, "utf-8");
}catch(Exception e) {
}
//设置%s里面值
String url = String.format(
baseUrl,
ConstantWxUtils.WX_OPEN_APP_ID,
redirectUrl,
"kc"
);
//重定向到请求微信地址里面
System.out.println(url);
return "redirect:"+url;
}
2.2.4 扫描二维码
步骤:
-
扫描之后,执行本地的 callback 方法,在跳转时传递过来两个值(state-原样传递)(code-类似于手机验证码);
-
拿到第一步的code,请求微信提供固定的地址,获取两个值;
https://api.weixin.qq.com/sns/oauth2/access_token
- access_token:访问凭证
- openid:每个微信唯一的标识
-
拿到 access_token 和 openid ,再去请求一个微信的固定地址,最终可以得到微信扫描人的信息
https://api.weixin.qq.com/sns/userinfo
@Autowired
private UcenterMemberService memberService;
//2 获取扫描人信息,添加数据
@GetMapping("callback")
public String callback(String code, String state) {
try {
//1 获取code值,临时票据,类似于验证码
//2 拿着code请求 微信固定的地址,得到两个值 accsess_token 和 openid
String baseAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" +
"?appid=%s" +
"&secret=%s" +
"&code=%s" +
"&grant_type=authorization_code";
//拼接三个参数 :id 秘钥 和 code值
String accessTokenUrl = String.format(
baseAccessTokenUrl,
ConstantWxUtils.WX_OPEN_APP_ID,
ConstantWxUtils.WX_OPEN_APP_SECRET,
code
);
//请求这个拼接好的地址,得到返回两个值 accsess_token 和 openid
//使用httpclient发送请求,得到返回结果
String accessTokenInfo = HttpClientUtils.get(accessTokenUrl);
//从accessTokenInfo字符串获取出来两个值 accsess_token 和 openid
//把accessTokenInfo字符串转换map集合,根据map里面key获取对应值
//使用json转换工具 Gson
Gson gson = new Gson();
HashMap mapAccessToken = gson.fromJson(accessTokenInfo, HashMap.class);
String access_token = (String)mapAccessToken.get("access_token");
String openid = (String)mapAccessToken.get("openid");
//把扫描人信息添加数据库里面
//判断数据表里面是否存在相同微信信息,根据openid判断
UcenterMember member = memberService.getOpenIdMember(openid);
if(member == null) {//memeber是空,表没有相同微信数据,进行添加
//3 拿着得到accsess_token 和 openid,再去请求微信提供固定的地址,获取到扫描人信息
//访问微信的资源服务器,获取用户信息
String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
"?access_token=%s" +
"&openid=%s";
//拼接两个参数
String userInfoUrl = String.format(
baseUserInfoUrl,
access_token,
openid
);
//发送请求
String userInfo = HttpClientUtils.get(userInfoUrl);
System.out.println(userInfo);
//获取返回userinfo字符串扫描人信息
HashMap userInfoMap = gson.fromJson(userInfo, HashMap.class);
String nickname = (String)userInfoMap.get("nickname");//昵称
String headimgurl = (String)userInfoMap.get("headimgurl");//头像
member = new UcenterMember();
member.setOpenid(openid);
member.setNickname(nickname);
member.setAvatar(headimgurl);
memberService.save(member); // 保存新用户
}
//使用jwt根据member对象生成token字符串
String jwtToken = JwtUtils.getJwtToken(member.getId(), member.getNickname());
//最后:返回首页面,通过路径传递token字符串
return "redirect:http://localhost:3000?token="+jwtToken;
}catch(Exception e) {
throw new GuliException(20001,"登录失败");
}
}