Oauth2方式实现单点登录
下面先简单介绍一下Oauth2的原理Oauth2是什么?Oauth2是一种授权机制,用来授权给第三方应用,获取用户数据。Oauth2是什么的解释 这是阮一峰老师的解释,因此解释的比较好了,就不在此重复了。Oauth2 的原理Oauth2 有四种授权模型授权码,隐藏式,密码式,凭证式 目前主流的形式是授权码方式。我们项目中使用的也是授权码方式。 这里就只介绍一下授权码方式 。授权码方式这块的内容完全
下面先简单介绍一下Oauth2的原理
Oauth2是什么?
Oauth2是一种授权机制,用来授权给第三方应用,获取用户数据。
Oauth2是什么的解释 这是阮一峰老师的解释,因此解释的比较好了,就不在此重复了。
Oauth2 的原理
Oauth2 有四种授权模型 授权码,隐藏式,密码式,凭证式 目前主流的形式是授权码方式。 我们项目中使用的也是授权码方式。 这里就只介绍一下授权码方式 。
授权码方式
这块的内容完全是引用于Oauth2原理 只是为了方便大家截阅读不用在来回跳转。 其他的几种方式可以去这篇文章中查看。
授权码(authorization code)方式,指的是第三方应用先申请一个授权码,然后再用该码获取令牌。
这种方式是最常用的流程,安全性也最高,它适用于那些有后端的 Web 应用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。
第一步,A 网站提供一个链接,用户点击后就会跳转到 B 网站,授权用户数据给 A 网站使用。下面就是 A 网站跳转 B 网站的一个示意链接。
https://b.com/oauth/authorize?
response_type=code&
client_id=CLIENT_ID&
redirect_uri=CALLBACK_URL&
scope=read
上面 URL 中,response_type参数表示要求返回授权码(code),client_id参数让 B 知道是谁在请求,redirect_uri参数是 B 接受或拒绝请求后的跳转网址,scope参数表示要求的授权范围(这里是只读)。
第二步,用户跳转后,B 网站会要求用户登录,然后询问是否同意给予 A 网站授权。用户表示同意,这时 B 网站就会跳回redirect_uri参数指定的网址。跳转时,会传回一个授权码,就像下面这样。https://a.com/callback?code=AUTHORIZATION_CODE
上面 URL 中,code参数就是授权码。
第三步,A 网站拿到授权码以后,就可以在后端,向 B 网站请求令牌。
https://b.com/oauth/token?
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET&
grant_type=authorization_code&
code=AUTHORIZATION_CODE&
redirect_uri=CALLBACK_URL
上面 URL 中,client_id参数和client_secret参数用来让 B 确认 A 的身份(client_secret参数是保密的,因此只能在后端发请求),grant_type参数的值是AUTHORIZATION_CODE,表示采用的授权方式是授权码,code参数是上一步拿到的授权码,redirect_uri参数是令牌颁发后的回调网址。
第四步,B 网站收到请求以后,就会颁发令牌。具体做法是向redirect_uri指定的网址,发送一段 JSON 数据。
{
"access_token":"ACCESS_TOKEN",
"token_type":"bearer",
"expires_in":2592000,
"refresh_token":"REFRESH_TOKEN",
"scope":"read",
"uid":100101,
"info":{...}
}
上面 JSON 数据中,access_token字段就是令牌,A 网站在后端拿到了。
单点登录的实现
下面我们就要开始动手了。
第一步,配置哪些应用可以到我们服务器中来获取认证。
可以看下图里面的关键往上,clientId,redirectUri 就是我们在上面的原理介绍中包括的, 这里还有一个关键的信息 就是clientSecret
。 因此我们不能随便让人拿到clientId 就能来我们服务器中认证。 我们需要一点案例机制。就是生成一个密钥,告诉第三方的应用,让他请求授权的时候,把这个密钥带上,否则就是非法请求。 其他的参数只是为了管理方便使用。
第二步: 获取授权码
下面可以看看真实的请求路径
http://127.0.0.1:9999/tboot/oauth2/authorize?username=superman&password=123456&code=560p&client_id=1287990317873762304&redirect_uri=http:%2F%2F127.0.0.1:10001%2F&state=1234
下面看看java代码
@RequestMapping(value = "/authorize", method = RequestMethod.GET)
@ApiOperation(value = "认证获取code")
public Result<Object> authorize(@ApiParam("用户名") @RequestParam String username,
@ApiParam("密码") @RequestParam String password,
@ApiParam("客户端id") @RequestParam String client_id,
@ApiParam("成功授权后回调地址") @RequestParam String redirect_uri,
@ApiParam("授权类型为code") @RequestParam(required = false, defaultValue = "code") String response_type,
@ApiParam("客户端状态值") @RequestParam String state
){
Client client = clientService.getById(client_id);
if(client==null){
return ResultUtil.error("客户端client_id不存在");
}
User user = userService.findByUsername(username);
if(user==null){
return ResultUtil.error("用户名不存在");
}
if(!new BCryptPasswordEncoder().matches(password, user.getPassword())){
return ResultUtil.error("用户密码不正确");
}
// 判断回调地址
if(!client.getRedirectUri().equals(redirect_uri)){
return ResultUtil.error("回调地址redirect_uri不正确");
}
// 生成code 5分钟内有效
String code = UUID.randomUUID().toString().replace("-", "");
// 存入用户及clientId信息
redisTemplate.opsForValue().set("oauthCode:"+code, new Gson().toJson(new Oauth2TokenInfo(client_id, username)), 5L, TimeUnit.MINUTES);
Map<String, Object> map = new HashMap<>(16);
map.put("code", code);
map.put("redirect_uri", redirect_uri);
map.put("state", state);
return ResultUtil.data(map);
}
第三步: 获取token
这样就返回了授权码,然后夺通过 授权码获取accessToken就算完成了。
查看请求路径
http://127.0.0.1:10001/tboot/oauth2/token?code=3838482b50b1447e81cbf9e52403f629&response_type=code&grant_type=authorization_code&client_id=1287990317873762304&client_secret=f3ede985786a40c2b0d7c341c83af4f3&redirect_uri=http:%2F%2F127.0.0.1:10001%2F
查看java代码
@RequestMapping(value = "/token", method = RequestMethod.GET)
@ApiOperation(value = "获取accessToken令牌")
public Result<Object> token(@ApiParam("授权类型") @RequestParam String grant_type,
@ApiParam("客户端id") @RequestParam String client_id,
@ApiParam("客户端秘钥") @RequestParam String client_secret,
@ApiParam("认证返回的code") @RequestParam(required = false) String code,
@ApiParam("刷新token") @RequestParam(required = false) String refresh_token,
@ApiParam("成功授权后回调地址") @RequestParam(required = false) String redirect_uri){
Client client = clientService.getById(client_id);
if(client==null){
return ResultUtil.error("客户端client_id不存在");
}
// 判断clientSecret
if(!client.getClientSecret().equals(client_secret)){
return ResultUtil.error("client_secret不正确");
}
Oauth2TokenInfo tokenInfo = null;
if("authorization_code".equals(grant_type)){
// 判断回调地址
if(!client.getRedirectUri().equals(redirect_uri)){
return ResultUtil.error("回调地址redirect_uri不正确");
}
// 判断code 获取用户信息
String codeValue = redisTemplate.opsForValue().get("oauthCode:"+code);
if(StrUtil.isBlank(codeValue)){
return ResultUtil.error("code已过期");
}
tokenInfo = new Gson().fromJson(codeValue, Oauth2TokenInfo.class);
if(!tokenInfo.getClientId().equals(client_id)){
return ResultUtil.error("code不正确");
}
} else if ("refresh_token".equals(grant_type)){
// 从refreshToken中获取用户信息
String refreshTokenValue = redisTemplate.opsForValue().get("oauthTokenInfo:"+refresh_token);
if(StrUtil.isBlank(refreshTokenValue)){
return ResultUtil.error("refresh_token已过期");
}
tokenInfo = new Gson().fromJson(refreshTokenValue, Oauth2TokenInfo.class);
if(!tokenInfo.getClientId().equals(client_id)){
return ResultUtil.error("refresh_token不正确");
}
} else {
return ResultUtil.error("授权类型grant_type不正确");
}
String token = null, refreshToken = null;
Long expiresIn = null;
String tokenKey = "oauthToken:"+client_id+":"+tokenInfo.getUsername(), refreshKey = "oauthRefreshToken:"+client_id+":"+tokenInfo.getUsername();
if("authorization_code".equals(grant_type)){
// 生成token模式
String oldToken = redisTemplate.opsForValue().get(tokenKey);
String oldRefreshToken = redisTemplate.opsForValue().get(refreshKey);
if(StrUtil.isNotBlank(oldToken)&&StrUtil.isNotBlank(oldRefreshToken)){
// 旧token
token = oldToken;
refreshToken = oldRefreshToken;
expiresIn = redisTemplate.getExpire("oauthTokenInfo:"+token, TimeUnit.SECONDS);
} else {
// 新生成 30天过期
String newToken = UUID.randomUUID().toString().replace("-", "");
String newRefreshToken = UUID.randomUUID().toString().replace("-", "");
redisTemplate.opsForValue().set(tokenKey, newToken, 30L, TimeUnit.DAYS);
redisTemplate.opsForValue().set(refreshKey, newRefreshToken, 30L, TimeUnit.DAYS);
// 新token中存入用户信息
redisTemplate.opsForValue().set("oauthTokenInfo:"+newToken, new Gson().toJson(tokenInfo),30L, TimeUnit.DAYS);
redisTemplate.opsForValue().set("oauthTokenInfo:"+newRefreshToken, new Gson().toJson(tokenInfo),30L, TimeUnit.DAYS);
token = newToken;
refreshToken = newRefreshToken;
expiresIn = redisTemplate.getExpire(token, TimeUnit.SECONDS);
}
} else if("refresh_token".equals(grant_type)) {
// 刷新token模式 生成新token 30天过期
String newToken = UUID.randomUUID().toString().replace("-", "");
String newRefreshToken = UUID.randomUUID().toString().replace("-", "");
redisTemplate.opsForValue().set(tokenKey, newToken, 30L, TimeUnit.DAYS);
redisTemplate.opsForValue().set(refreshKey, newRefreshToken, 30L, TimeUnit.DAYS);
// 新token中存入用户信息
redisTemplate.opsForValue().set("oauthTokenInfo:"+newToken, new Gson().toJson(tokenInfo),30L, TimeUnit.DAYS);
redisTemplate.opsForValue().set("oauthTokenInfo:"+newRefreshToken, new Gson().toJson(tokenInfo),30L, TimeUnit.DAYS);
token = newToken;
refreshToken = newRefreshToken;
expiresIn = redisTemplate.getExpire("oauthTokenInfo:"+token, TimeUnit.SECONDS);
// 旧refreshToken过期
redisTemplate.delete("oauthTokenInfo:"+refresh_token);
}
Map<String, Object> map = new HashMap<>(16);
map.put("access_token", token);
map.put("expires_in", expiresIn);
map.put("refresh_token", refreshToken);
return ResultUtil.data(map);
}
就可以获取相当的信息
在通过 accessTonken 就可去资源服务器去获取相应的用户信息了。
重要
猿来衣舍
这是博主开的淘宝小店,主要经营舒适保暖的服饰,**有纯棉防臭袜子、主题卫衣、恒温发热保暖衣**
。欢迎大家选购。一个人能够走多远,关键在于与谁同行,我用跨越山海的一路相伴,希望得到您用金钱的称赞。
猿来衣舍
猿来衣舍
猿来衣舍
猿来衣舍
打开淘宝搜索 “猿来衣舍”这四个字就可以搜到小店,希望大家多多支持。
交个朋友吧
更多推荐
所有评论(0)