OAuth2.0 beta ..

This commit is contained in:
click33
2021-07-17 23:48:12 +08:00
parent 742b65366a
commit 971c2860f0
46 changed files with 2160 additions and 914 deletions

View File

@@ -1,5 +1,6 @@
package cn.dev33.satoken.config;
import java.io.Serializable;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -13,7 +14,9 @@ import cn.dev33.satoken.util.SaResult;
* @author kong
*
*/
public class SaSsoConfig {
public class SaSsoConfig implements Serializable {
private static final long serialVersionUID = -6541180061782004705L;
/**
* Ticket有效期 (单位: 秒)

View File

@@ -1,5 +1,7 @@
package cn.dev33.satoken.config;
import java.io.Serializable;
/**
* Sa-Token 配置类 Model
* <p>
@@ -8,7 +10,9 @@ package cn.dev33.satoken.config;
* @author kong
*
*/
public class SaTokenConfig {
public class SaTokenConfig implements Serializable {
private static final long serialVersionUID = -6541180061782004705L;
/** token名称 (同时也是cookie名称) */
private String tokenName = "satoken";

View File

@@ -82,6 +82,14 @@ public interface SaRequest {
*/
public String getRequestPath();
/**
* 返回当前请求path是否为指定值
* @return see note
*/
public default boolean isPath(String path) {
return getRequestPath().equals(path);
}
/**
* 返回当前请求的urlhttp://xxx.com/?id=127
* @return see note

View File

@@ -65,7 +65,7 @@ public class SaSsoConsts {
/** 表示OK的返回结果 */
public static final String OK = "ok";
/** 表示请求没有得到任何有效处理 */
public static final String NOT_HANDLE = "not handle";
/** 表示请求没有得到任何有效处理 {msg: "not handle"} */
public static final String NOT_HANDLE = "{\"msg\": \"not handle\"}";
}

View File

@@ -356,4 +356,43 @@ public class SaFoxUtil {
}
}
/**
* 将指定字符串按照逗号分隔符转化为字符串集合
* @param str 字符串
* @return 分割后的字符串集合
*/
public static List<String> convertStringToList(String str) {
List<String> list = new ArrayList<String>();
if(isEmpty(str)) {
return list;
}
String[] arr = str.split(",");
for (String s : arr) {
s = s.trim();
if(isEmpty(s) == false) {
list.add(s);
}
}
return list;
}
/**
* 将指定集合按照逗号连接成一个字符串
* @param list 集合
* @return 字符串
*/
public static String convertListToString(List<?> list) {
if(list == null || list.size() == 0) {
return "";
}
String str = "";
for (int i = 0; i < list.size(); i++) {
str += list.get(i);
if(i != list.size() - 1) {
str += ",";
}
}
return str;
}
}

View File

@@ -1,67 +1,107 @@
package cn.dev33.satoken.util;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 对Ajax请求返回Json格式数据的简易封装
* 对Ajax请求返回Json格式数据的简易封装 <br>
* 所有预留字段:<br>
* code=状态码 <br>
* msg=描述信息 <br>
* data=携带对象 <br>
* @author kong
*
*/
public class SaResult implements Serializable{
public class SaResult extends LinkedHashMap<String, Object> implements Serializable{
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 1L; // 序列化版本号
public static final int CODE_SUCCESS = 200;
public static final int CODE_ERROR = 500;
/**
* 状态码
*/
public int code;
public SaResult(int code, String msg, Object data) {
this.setCode(code);
this.setMsg(msg);
this.setData(data);
}
/**
* 描述信息
* 获取code
* @return code
*/
public String msg;
public Integer getCode() {
return (Integer)this.get("code");
}
/**
* 携带对象
* 获取msg
* @return msg
*/
public Object data;
public String getMsg() {
return (String)this.get("msg");
}
/**
* 获取data
* @return data
*/
public Object getData() {
return (Object)this.get("data");
}
/**
* 给code赋值连缀风格
* @param code code
* @return 对象自身
*/
public SaResult setCode(int code) {
this.code = code;
this.put("code", code);
return this;
}
/**
* 给msg赋值连缀风格
* @param msg msg
* @return 对象自身
*/
public SaResult setMsg(String msg) {
this.msg = msg;
this.put("msg", msg);
return this;
}
/**
* 给data赋值连缀风格
* @param data data
* @return 对象自身
*/
public SaResult setData(Object data) {
this.put("data", data);
return this;
}
/**
* 给data赋值连缀风格
* 写入一个值 自定义key, 连缀风格
* @param key key
* @param data data
* @return 对象自身
*/
public SaResult setData(Object data) {
this.data = data;
public SaResult set(String key, Object data) {
this.put(key, data);
return this;
}
// ============================ 构建 ==================================
public SaResult(int code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
/**
* 写入一个Map, 连缀风格
* @param map map
* @return 对象自身
*/
public SaResult setMap(Map<String, ?> map) {
for (String key : map.keySet()) {
this.put(key, map.get(key));
}
return this;
}
// ============================ 构建 ==================================
// 构建成功
public static SaResult ok() {
return new SaResult(CODE_SUCCESS, "ok", null);
@@ -93,9 +133,9 @@ public class SaResult implements Serializable{
@Override
public String toString() {
return "{"
+ "\"code\": " + this.code
+ ", \"msg\": \"" + this.msg + "\""
+ ", \"data\": \"" + this.data + "\""
+ "\"code\": " + this.getCode()
+ ", \"msg\": \"" + this.getMsg() + "\""
+ ", \"data\": \"" + this.getData() + "\""
+ "}";
}

View File

@@ -22,26 +22,39 @@
<dependencies>
<!-- springboot依赖 -->
<!-- SpringBoot依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- sa-token 权限认证, 在线文档http://sa-token.dev33.cn/ -->
<!-- Sa-Token 权限认证, 在线文档http://sa-token.dev33.cn/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- thymeleaf 视图引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- OkHttps网络请求库 http://okhttps.ejlchina.com/ -->
<dependency>
<groupId>com.ejlchina</groupId>
<artifactId>okhttps</artifactId>
<version>2.4.5</version>
<groupId>com.ejlchina</groupId>
<artifactId>okhttps</artifactId>
<version>3.1.1</version>
</dependency>
<!-- 热刷新 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!-- ConfigurationProperties -->
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@@ -3,8 +3,9 @@ package com.pj;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 启动
* 启动OAuth2-Client端
* @author kong
*/
@SpringBootApplication
@@ -12,7 +13,13 @@ public class SaOAuth2ClientApplication {
public static void main(String[] args) {
SpringApplication.run(SaOAuth2ClientApplication.class, args);
System.out.println("\n客户端启动成功,访问: http://localhost:8002/login.html");
System.out.println("\nSa-Token-OAuth Client端启动成功\n\n" + str);
}
static String str = "首先在host文件 (C:\\WINDOWS\\system32\\drivers\\etc\\hosts) 添加以下内容: \r\n" +
" 127.0.0.1 sa-oauth-server.com \r\n" +
" 127.0.0.1 sa-oauth-client.com \r\n" +
"再从浏览器访问:\r\n" +
" http://sa-oauth-client.com:8002";
}

View File

@@ -0,0 +1,188 @@
package com.pj.oauth2;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import com.ejlchina.okhttps.OkHttps;
import com.pj.utils.SoMap;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* Sa-OAuth2 Client端 控制器
* @author kong
*/
@RestController
public class SaOAuthClientController {
// 相关参数配置
private String clientId = "1001"; // 应用id
private String clientSecret = "aaaa-bbbb-cccc-dddd-eeee"; // 应用秘钥
private String serverUrl = "http://sa-oauth-server.com:8001"; // 服务端接口
// 进入首页
@RequestMapping("/")
public Object index(HttpServletRequest request) {
request.setAttribute("uid", StpUtil.getLoginIdDefaultNull());
return new ModelAndView("index.html");
}
// 根据Code码进行登录获取 Access-Token 和 openid
@RequestMapping("/codeLogin")
public SaResult codeLogin(String code) {
// 调用Server端接口获取 Access-Token 以及其他信息
String str = OkHttps.sync(serverUrl + "/oauth2/token")
.addBodyPara("grant_type", "authorization_code")
.addBodyPara("code", code)
.addBodyPara("client_id", clientId)
.addBodyPara("client_secret", clientSecret)
.post()
.getBody()
.toString();
SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so);
// code不等于200 代表请求失败
if(so.getInt("code") != 200) {
return SaResult.error(so.getString("msg"));
}
// 根据openid获取其对应的userId
SoMap data = so.getMap("data");
long uid = getUserIdByOpenid(data.getString("openid"));
data.set("uid", uid);
// 返回相关参数
StpUtil.login(uid);
return SaResult.data(data);
}
// 根据 Refresh-Token 去刷新 Access-Token
@RequestMapping("/refresh")
public SaResult refresh(String refreshToken) {
// 调用Server端接口通过 Refresh-Token 刷新出一个新的 Access-Token
String str = OkHttps.sync(serverUrl + "/oauth2/refresh")
.addBodyPara("grant_type", "refresh_token")
.addBodyPara("client_id", clientId)
.addBodyPara("client_secret", clientSecret)
.addBodyPara("refresh_token", refreshToken)
.post()
.getBody()
.toString();
SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so);
// code不等于200 代表请求失败
if(so.getInt("code") != 200) {
return SaResult.error(so.getString("msg"));
}
// 返回相关参数 (data=新的Access-Token )
SoMap data = so.getMap("data");
return SaResult.data(data);
}
// 模式三:密码式-授权登录
@RequestMapping("/passwordLogin")
public SaResult passwordLogin(String username, String password) {
// 模式三:密码式-授权登录
String str = OkHttps.sync(serverUrl + "/oauth2/token")
.addBodyPara("grant_type", "password")
.addBodyPara("client_id", clientId)
.addBodyPara("username", username)
.addBodyPara("password", password)
.post()
.getBody()
.toString();
SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so);
// code不等于200 代表请求失败
if(so.getInt("code") != 200) {
return SaResult.error(so.getString("msg"));
}
// 根据openid获取其对应的userId
SoMap data = so.getMap("data");
long uid = getUserIdByOpenid(data.getString("openid"));
data.set("uid", uid);
// 返回相关参数
StpUtil.login(uid);
return SaResult.data(data);
}
// 模式四:获取应用的 Client-Token
@RequestMapping("/clientToken")
public SaResult clientToken() {
// 调用Server端接口
String str = OkHttps.sync(serverUrl + "/oauth2/client_token")
.addBodyPara("grant_type", "client_credentials")
.addBodyPara("client_id", clientId)
.addBodyPara("client_secret", clientSecret)
.post()
.getBody()
.toString();
SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so);
// code不等于200 代表请求失败
if(so.getInt("code") != 200) {
return SaResult.error(so.getString("msg"));
}
// 返回相关参数 (data=新的Client-Token )
SoMap data = so.getMap("data");
return SaResult.data(data);
}
// 注销登录
@RequestMapping("/logout")
public SaResult logout() {
StpUtil.logout();
return SaResult.ok();
}
// 根据 Access-Token 置换相关的资源: 获取账号昵称、头像、性别等信息
@RequestMapping("/getUserinfo")
public SaResult getUserinfo(String accessToken) {
// 调用Server端接口查询开放的资源
String str = OkHttps.sync(serverUrl + "/oauth2/userinfo")
.addBodyPara("access_token", accessToken)
.post()
.getBody()
.toString();
SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so);
// code不等于200 代表请求失败
if(so.getInt("code") != 200) {
return SaResult.error(so.getString("msg"));
}
// 返回相关参数 (data=获取到的资源 )
SoMap data = so.getMap("data");
return SaResult.data(data);
}
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
// ------------ 模拟方法 ------------------
// 模拟方法根据openid获取userId
private long getUserIdByOpenid(String openid) {
// 此方法仅做模拟实际开发要根据具体业务逻辑来获取userId
return 10001;
}
}

View File

@@ -33,12 +33,10 @@ public class SoMap extends LinkedHashMap<String, Object> {
public SoMap() {
}
/** 以下元素会在isNull函数中被判定为Null */
public static final Object[] NULL_ELEMENT_ARRAY = {null, ""};
public static final List<Object> NULL_ELEMENT_LIST;
static {
NULL_ELEMENT_LIST = Arrays.asList(NULL_ELEMENT_ARRAY);
}
@@ -144,6 +142,22 @@ public class SoMap extends LinkedHashMap<String, Object> {
return getDateByFormat(key, "yyyy-MM-dd HH:mm:ss");
}
/** 转为Map并返回 */
@SuppressWarnings({ "unchecked", "rawtypes" })
public SoMap getMap(String key) {
Object value = get(key);
if(value == null) {
return SoMap.getSoMap();
}
if(value instanceof Map) {
return SoMap.getSoMap((Map)value);
}
if(value instanceof String) {
return SoMap.getSoMap().setJsonString((String)value);
}
throw new RuntimeException("值无法转化为SoMap: " + value);
}
/** 获取集合(必须原先就是个集合,否则会创建个新集合并返回) */
@SuppressWarnings("unchecked")
public List<Object> getList(String key) {

View File

@@ -5,9 +5,3 @@ server:
sa-token:
# token名称 (同时也是cookie名称)
token-name: satoken-client
spring:
# 静态文件路径映射
resources:
static-locations: classpath:/META-INF/resources/,classpath:/resources/, classpath:/static/, classpath:/public/
# static-locations: file:E:\work\project-yun\sa-token\sa-token-demo-oauth2\sa-token-demo-oauth2-client\src\main\resources\static\

View File

@@ -0,0 +1,253 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Sa-OAuth2-Client端-测试页</title>
<style type="text/css">
body{background-color: #D0D9E0;}
*{margin: 0px; padding: 0px;}
.login-box{max-width: 1000px; margin: 30px auto; padding: 1em;}
.info{line-height: 30px;}
.btn-box{margin-top: 10px; margin-bottom: 15px;}
.btn-box a{margin-right: 10px;}
.btn-box a:hover{text-decoration:underline !important;}
.login-box input{line-height: 25px; margin-bottom: 10px; padding-left: 5px;}
.login-box button{padding: 5px 15px; margin-top: 20px; cursor: pointer; }
.login-box a{text-decoration: none;}
.pst{color: #666; margin-top: 15px;}
.ps{color: #666; margin-left: 10px;}
.login-box code{display: block; background-color: #F5F2F0; border: 1px #ccc solid; color: #600; padding: 15px; margin-top: 5px; border-radius: 2px; }
.info b,.info span{color: green;}
</style>
</head>
<body>
<div class="login-box">
<h2>Sa-OAuth2-Client端-测试页</h2> <br>
<div class="info">
<div>当前账号id
<b class="uid" th:utext="${uid}"></b>
</div>
<div>当前Openid <span class="openid"></span></div>
<div>当前Access-Token <span class="access_token"></span></div>
<div>当前Refresh-Token <span class="refresh_token"></span></div>
<div>当前Client-Token <span class="client_token"></span></div>
</div>
<div class="btn-box">
<a href="javascript:logout();">注销</a>
<a href="/">回到首页</a>
</div>
<hr><br>
<h3>模式一授权码Authorization Code</h3>
<p class="pst">授权码OAuth2.0标准授权流程,先 (重定向) 获取Code授权码再 (Rest API) 获取 Access-Token 和 Openid </p>
<a href="http://sa-oauth-server.com:8001/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/">
<button>点我开始授权登录(静默授权)</button>
</a>
<span class="ps">当请求链接不包含scope权限时将无需用户手动确认做到静默授权当然此时我们也只能获取openid</span>
<code>http://sa-oauth-server.com:8001/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/</code>
<a href="http://sa-oauth-server.com:8001/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=userinfo">
<button>授权登录(显式授权)</button>
</a>
<span class="ps">当请求链接包含具体的scope权限时将需要用户手动确认此时我们除了openid以外还可以获取更多的资源</span>
<code>http://sa-oauth-server.com:8001/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=userinfo</code>
<button onclick="refreshToken()">刷新令牌</button>
<span class="ps">我们可以拿着 Refresh-Token 去刷新我们的 Access-Token每次刷新后旧Token将作废</span>
<code>http://sa-oauth-server.com:8001/oauth2/refresh?grant_type=refresh_token&client_id={value}&client_secret={value}&refresh_token={value}</code>
<button onclick="getUserinfo()">获取账号信息</button>
<span class="ps">使用 Access-Token 置换资源: 获取账号昵称、头像、性别等信息 </span>
<code>http://sa-oauth-server.com:8001/oauth2/userinfo?access_token={value}</code>
<br>
<h3>模式二隐藏式Implicit</h3>
<a href="http://sa-oauth-server.com:8001/oauth2/authorize?response_type=token&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=userinfo">
<button>隐藏式</button>
</a>
<span class="ps">越过授权码的步骤直接返回token到前端页面 格式http//:domain.com#token=xxxx-xxxx </span>
<code>http://sa-oauth-server.com:8001/oauth2/authorize?response_type=token&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=userinfo</code>
<br>
<h3>模式三密码式Password</h3>
<p class="pst">在下面输入Server端的用户名和密码使用密码式进行 OAuth2 授权登录</p>
账号:<input name="username">
密码:<input name="password">
<button onclick="passwordLogin()">登录</button>
<code>http://sa-oauth-server.com:8001/oauth2/token?grant_type=password&client_id={value}&username={value}&password={value}</code>
<br>
<h3>模式四凭证式Client Credentials</h3>
<p class="pst">以上三种模式获取的都是用户的 Access-Token代表用户对第三方应用的授权在OAuth2.0中还有一种针对 Client级别的授权
Client-Token代表应用自身的资源授权</p>
<p class="pst">Client-Token具有延迟作废特性在每次获取最新Client-Token的时候旧Client-Token不会立即过期而是作为Past-Token再次
储存起来资源请求方只要携带其中之一便可通过Token校验这种特性保证了在大量并发请求时不会出现“新旧Token交替造成的授权失效”
保证了服务的高可用</p>
<button onclick="getClientToken()">获取应用Client-Token</button>
<code>http://sa-oauth-server.com:8001/oauth2/client_token?grant_type=client_credentials&client_id={value}&client_secret={value}</code>
<br><br>
<span>更多资料请参考 Sa-Token 官方文档地址:</span>
<a href="http://sa-token.dev33.cn/">http://sa-token.dev33.cn/</a>
<div style="height: 200px;"></div>
</div>
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
<script>window.jQuery || alert('当前页面CDN服务商已宕机请将所有js包更换为本地依赖')</script>
<script type="text/javascript">
// 根据code授权码进行登录
function doLogin(code) {
$.ajax({
url: '/codeLogin?code=' + code,
dataType: 'json',
success: function(res) {
console.log('返回:', res);
if(res.code == 200) {
setInfo(res.data);
layer.msg('登录成功!');
} else {
layer.msg(res.msg);
}
},
error: function(xhr, type, errorThrown){
return layer.alert("异常:" + JSON.stringify(xhr));
}
});
}
var code = getParam('code');
if(code) {
doLogin(code);
}
// 根据 Refresh-Token 去刷新 Access-Token
function refreshToken() {
var refreshToken = $('.refresh_token').text();
if(refreshToken == '') {
return layer.alert('您还没有获取 Refresh-Token ,请先授权登录');
}
$.ajax({
url: '/refresh?refreshToken=' + refreshToken,
dataType: 'json',
success: function(res) {
console.log('返回:', res);
if(res.code == 200) {
setInfo(res.data);
layer.msg('登录成功!');
} else {
layer.msg(res.msg);
}
},
error: function(xhr, type, errorThrown){
return layer.alert("异常:" + JSON.stringify(xhr));
}
});
}
// 模式三:密码式-授权登录
function passwordLogin() {
$.ajax({
url: '/passwordLogin',
data: {
username: $('[name=username]').val(),
password: $('[name=password]').val()
},
dataType: 'json',
success: function(res) {
console.log('返回:', res);
if(res.code == 200) {
setInfo(res.data);
layer.msg('登录成功!');
} else {
layer.msg(res.msg);
}
},
error: function(xhr, type, errorThrown){
return layer.alert("异常:" + JSON.stringify(xhr));
}
});
}
// 模式四:获取应用的 Client-Token
function getClientToken () {
$.ajax({
url: '/clientToken',
dataType: 'json',
success: function(res) {
console.log('返回:', res);
if(res.code == 200) {
setInfo(res.data);
layer.msg('获取成功!');
} else {
layer.msg(res.msg);
}
},
error: function(xhr, type, errorThrown){
return layer.alert("异常:" + JSON.stringify(xhr));
}
});
}
// 使用 Access-Token 置换资源: 获取账号昵称、头像、性别等信息
function getUserinfo() {
var accessToken = $('.access_token').text();
if(accessToken == '') {
return layer.alert('您还没有获取 Access-Token ,请先授权登录');
}
$.ajax({
url: '/getUserinfo',
data: {accessToken: accessToken},
dataType: 'json',
success: function(res) {
layer.alert(JSON.stringify(res.data));
},
error: function(xhr, type, errorThrown){
return layer.alert("异常:" + JSON.stringify(xhr));
}
});
}
// 注销
function logout() {
$.ajax({
url: '/logout',
dataType: 'json',
success: function(res) {
location.href = '/';
},
error: function(xhr, type, errorThrown){
return layer.alert("异常:" + JSON.stringify(xhr));
}
});
}
// 写入数据
function setInfo(info) {
console.log('info', info);
for (var key in info) {
$('.' + key).text(info[key]);
}
if($('.uid').text() == '') {
$('.uid').html('<b style="color: #E00;">未登录</b>')
}
}
setInfo({});
// 从url中查询到指定名称的参数值
function getParam(name, defaultValue){
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if(pair[0] == name){return pair[1];}
}
return(defaultValue == undefined ? null : defaultValue);
}
</script>
</body>
</html>

View File

@@ -22,27 +22,27 @@
<dependencies>
<!-- springboot依赖 -->
<!-- SpringBoot依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- sa-token 权限认证, 在线文档http://sa-token.dev33.cn/ -->
<!-- Sa-Token 权限认证, 在线文档http://sa-token.dev33.cn/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- sa-token 实现 oauth2.0 -->
<!-- Sa-Token-OAuth2.0 模块 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-oauth2</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- sa-token整合redis (使用jackson序列化方式) -->
<!-- Sa-Token整合Redis (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
@@ -53,6 +53,19 @@
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- thymeleaf 视图引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- 热刷新 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!-- ConfigurationProperties -->
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@@ -4,7 +4,7 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 启动
* 启动OAuth2-Server端
* @author kong
*/
@SpringBootApplication
@@ -12,7 +12,7 @@ public class SaOAuth2ServerApplication {
public static void main(String[] args) {
SpringApplication.run(SaOAuth2ServerApplication.class, args);
System.out.println("\nOAuth-Server端启动成功");
System.out.println("\nSa-Token-OAuth Server端启动成功");
}
}

View File

@@ -0,0 +1,89 @@
package com.pj.oauth2;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.oauth2.config.SaOAuth2Config;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Handle;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Util;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* Sa-OAuth2 Server端 控制器
* @author kong
*
*/
@RestController
public class SaOAuth2ServerController {
// 处理所有OAuth相关请求
@RequestMapping("/oauth2/*")
public Object request() {
System.out.println("--------------进入请求 ");
return SaOAuth2Handle.serverRequest();
}
// Sa-OAuth2 定制化配置
@Autowired
public void setSaOAuth2Config(SaOAuth2Config cfg) {
cfg.
// 未登录的视图
setNotLoginView(()->{
return new ModelAndView("login.html");
}).
// 登录处理函数
setDoLoginHandle((name, pwd) -> {
if("sa".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
return SaResult.ok();
}
return SaResult.error("账号名或密码错误");
}).
// 授权确认视图
setConfirmView((clientId, scope)->{
Map<String, Object> map = new HashMap<>();
map.put("clientId", clientId);
map.put("scope", scope);
return new ModelAndView("confirm.html", map);
})
;
}
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
// ---------- 开放相关资源接口: Client端根据 Access-Token ,置换相关资源 ------------
// 获取Userinfo信息昵称、头像、性别等等
@RequestMapping("/oauth2/userinfo")
public SaResult userinfo() {
// 获取 Access-Token 对应的账号id
String accessToken = SaHolder.getRequest().getParamNotNull("access_token");
Object loginId = SaOAuth2Util.getLoginIdByAccessToken(accessToken);
System.out.println("-------- 此Access-Token对应的账号id: " + loginId);
// 模拟账号信息 (真实环境需要查询数据库获取信息)
Map<String, Object> map = new LinkedHashMap<String, Object>();
map.put("nickname", "shengzhang_");
map.put("avatar", "http://xxx.com/1.jpg");
map.put("age", "18");
map.put("sex", "");
map.put("address", "山东省 青岛市 城阳区");
return SaResult.data(map);
}
}

View File

@@ -1,72 +1,38 @@
package com.pj.oauth2;
import java.util.Arrays;
import java.util.List;
import org.springframework.stereotype.Component;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Template;
import cn.dev33.satoken.oauth2.model.SaClientModel;
/**
* 使用oauth2.0 所必须的一些自定义实现
* Sa-Token OAuth2.0 整合实现
* @author kong
*/
@Component
public class SaOAuth2TemplateImpl extends SaOAuth2Template {
/*
* ------ 注意: 以下代码均为示例,真实环境需要根据数据库查询相关信息
*/
// 返回此平台所有权限集合
// 根据 id 获取 Client 信息
@Override
public List<String> getAppScopeList() {
return Arrays.asList("userinfo");
public SaClientModel getClientModel(String clientId) {
// 此为模拟数据,真实环境需要从数据库查询
if("1001".equals(clientId)) {
return new SaClientModel()
.setClientId("10001")
.setClientSecret("aaaa-bbbb-cccc-dddd-eeee")
.setAllowUrl("*")
.setContractScope("userinfo");
}
return null;
}
// 返回指定Client签约的所有Scope集合
@Override
public List<String> getClientScopeList(String clientId) {
return Arrays.asList("userinfo");
}
// 获取指定 LoginId 对指定 Client 已经授权过的所有 Scope
@Override
public List<String> getGrantScopeList(Object loginId, String clientId) {
return Arrays.asList();
}
// 返回指定Client允许的回调域名, 多个用逗号隔开, *代表不限制
@Override
public String getClientDomain(String clientId) {
return "*";
}
// 返回指定ClientId的ClientSecret
@Override
public String getClientSecret(String clientId) {
return "aaaa-bbbb-cccc-dddd-eeee";
}
// 根据ClientId和LoginId返回openid
// 根据ClientId 和 LoginId 获取openid
@Override
public String getOpenid(String clientId, Object loginId) {
// 此为模拟数据,真实环境需要从数据库查询
return "gr_SwoIN0MC1ewxHX_vfCW3BothWDZMMtx__";
}
// 根据ClientId和openid返回LoginId
@Override
public Object getLoginId(String clientId, String openid) {
return 10001;
}
/*
* 以上函数为开发时必须重写实现,其余函数可以按需重写
*/
// -------------- 其它需要重写的函数
}

View File

@@ -5,13 +5,14 @@ server:
sa-token:
# token名称 (同时也是cookie名称)
token-name: satoken-server
# OAuth2.0 配置
oauth2:
is-code: true
is-implicit: true
is-password: true
is-client: true
spring:
# 静态文件路径映射
resources:
static-locations: classpath:/META-INF/resources/,classpath:/resources/, classpath:/static/, classpath:/public/
# static-locations: file:E:\work\project-yun\sa-token\sa-token-demo-oauth2\sa-token-demo-oauth2-server\src\main\resources\static\
# redis配置
redis:
# Redis数据库索引默认为0

View File

@@ -0,0 +1,103 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Sa-OAuth2-认证中心-确认授权页</title>
<style type="text/css">
body{background-color: #F5F5D5;}
*{margin: 0px; padding: 0px;}
.login-box{width: 400px; margin: 20vh auto; padding: 70px; border: 1px #000 solid;}
.login-box button{padding: 5px 15px; cursor: pointer; }
</style>
</head>
<body>
<div class="login-box">
<h2>Sa-OAuth2-认证中心-确认授权页</h2> <br>
<div>
<div><b>应用ID</b><span th:utext="${clientId}"></span></div>
<div><b>请求授权:</b><span th:utext="${scope}"></span></div>
<br><div>------------- 是否同意授权 -------------</div><br>
<div>
<button onclick="yes()">同意</button>
<button onclick="no()">拒绝</button>
</div>
</div>
</div>
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
<script>window.jQuery || alert('当前页面CDN服务商已宕机请将所有js包更换为本地依赖')</script>
<script type="text/javascript">
// 同意授权
function yes() {
console.log('-----------');
$.ajax({
url: '/oauth2/doConfirm',
data: {
client_id: getParam('client_id'),
scope: getParam('scope')
},
dataType: 'json',
success: function(res) {
if(res.code == 200) {
layer.msg('授权成功!');
setTimeout(function() {
location.reload(true);
}, 800);
} else {
// 重定向至授权失败URL
layer.alert('授权失败!');
}
},
error: function(e) {
console.log('error');
}
});
}
// 拒绝授权
function no() {
var url = joinParam(getParam('redirect_uri'), "handle=refuse&msg=用户拒绝了授权");
location.href = url;
}
// 从url中查询到指定名称的参数值
function getParam(name, defaultValue){
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if(pair[0] == name){return pair[1];}
}
return(defaultValue == undefined ? null : defaultValue);
}
// 在url上拼接上kv参数并返回
function joinParam(url, parameStr) {
if(parameStr == null || parameStr.length == 0) {
return url;
}
var index = url.indexOf('?');
// ? 不存在
if(index == -1) {
return url + '?' + parameStr;
}
// ? 是最后一位
if(index == url.length - 1) {
return url + parameStr;
}
// ? 是其中一位
if(index > -1 && index < url.length - 1) {
// 如果最后一位是 不是&, 且 parameStr 第一位不是 &, 就增送一个 &
if(url.lastIndexOf('&') != url.length - 1 && parameStrindexOf('&') != 0) {
return url + '&' + parameStr;
} else {
return url + parameStr;
}
}
}
</script>
</body>
</html>

View File

@@ -0,0 +1,54 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Sa-OAuth2-认证中心-登录页</title>
<style type="text/css">
body{background-color: #F5F5D5;}
*{margin: 0px; padding: 0px;}
.login-box{width: 400px; margin: 20vh auto;}
.login-box input{line-height: 25px; margin-bottom: 10px;}
.login-box button{padding: 5px 15px; cursor: pointer; }
</style>
</head>
<body>
<div class="login-box">
<h2>Sa-OAuth2-认证中心-登录页</h2> <br>
账号:<input name="name" /> <br>
密码:<input name="pwd" type="password" /> <br>
<button onclick="doLogin()">登录</button>
<span style="color: #666;">(测试账号: sa 123456</span>
</div>
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
<script>window.jQuery || alert('当前页面CDN服务商已宕机请将所有js包更换为本地依赖')</script>
<script type="text/javascript">
// 登录方法
function doLogin() {
console.log('-----------');
$.ajax({
url: '/oauth2/doLogin',
data: {
name: $('[name=name]').val(),
pwd: $('[name=pwd]').val()
},
dataType: 'json',
success: function(res) {
if(res.code == 200) {
layer.msg('登录成功!');
setTimeout(function() {
location.reload(true);
}, 800);
} else {
layer.alert(res.msg);
}
},
error: function(e) {
console.log('error');
}
});
}
</script>
</body>
</html>

View File

@@ -79,7 +79,6 @@
<version>5.5.4</version>
</dependency> -->
</dependencies>

View File

@@ -1,16 +1,5 @@
package com.pj;
//import org.springframework.boot.SpringApplication;
//import org.springframework.boot.autoconfigure.SpringBootApplication;
//import org.springframework.context.annotation.Bean;
//import org.springframework.http.MediaType;
//import org.springframework.web.reactive.function.server.RequestPredicates;
//import org.springframework.web.reactive.function.server.RouterFunction;
//import org.springframework.web.reactive.function.server.RouterFunctions;
//import org.springframework.web.reactive.function.server.ServerRequest;
//import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

View File

@@ -7,6 +7,36 @@
<table class="gzh-table" style="text-align: center;">
<tr>
<td>
<img src="https://mp.weixin.qq.com/mp/qrcode?scene=10000005&size=102&__biz=MzU2OTMyMTAxNA==&mid=2247496684&idx=2&sn=be1520743589ca43c129fde828af16ef&send_time="/>
<b>终码一生</b>
<span>分享Java开发技术JVM多线程高并发性能调优</span>
</td>
<td>
<img src="https://mp.weixin.qq.com/mp/qrcode?scene=10000005&size=102&__biz=MzU4ODI1MjA3NQ==&mid=2247502759&idx=2&sn=87ef078b86f5a0015d97807e76691b09&send_time="/>
<b>CodeSheep</b>
<span>一只爱技术的程序羊,想把分享变成一种习惯!</span>
</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>
<img src="https://mp.weixin.qq.com/mp/qrcode?scene=10000004&size=102&__biz=MzI4Njk5OTg1MA==&mid=2247489188&idx=1&sn=0098f458660d194817d4fc40bca55e4d&send_time="/>
<b>Java开发宝典</b>
<span>分享Java基础、Java框架、数据库、微服务、中间件、分布式、架构等技术干货</span>
</td>
<td>
<img src="https://mp.weixin.qq.com/mp/qrcode?scene=10000005&size=102&__biz=MzIwODkzOTc1MQ==&mid=2247489717&idx=1&sn=1c295f070123c84ee3791ac4d4898ae9&send_time="/>
<b>MarkerHub</b>
<span>专注于梳理java知识解析开源项目。 </span>
</td>
<td>
<img src="https://mp.weixin.qq.com/mp/qrcode?scene=10000004&size=102&__biz=MzI3MDE0NzYwNA==&mid=2651444525&idx=3&sn=e266f94ada851a5ba6a61c1d71e9a62f&send_time="/>
<b>架构师必备</b>
<span>分享干货文章,做一个有逼格的架构师社区! </span>
</td>
<td>
<img src="https://mp.weixin.qq.com/mp/qrcode?scene=10000005&size=102&__biz=Mzk0MjE5MTk5Mw==&mid=2247485576&idx=1&sn=d955d60ab193d895fbd15c37bedebcb8&send_time="/>
<b>Github导航站</b>
@@ -17,9 +47,6 @@
<b>Java爱好者</b>
<span>分享Java开发编程资源和Java技术文章 </span>
</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>

View File

@@ -45,19 +45,23 @@ sa-token:
password:
# 连接超时时间(毫秒)
timeout: 10ms
lettuce:
pool:
# 连接池最大连接
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
spring:
# 配置业务使用的Redis连接
redis:
# Redis数据库索引默认为0
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码默认为空
password:
# 连接超时时间(毫秒)
timeout: 10ms
```
具体可参考:[码云application.yml](https://gitee.com/dromara/sa-token/blob/dev/sa-token-demo/sa-token-demo-alone-redis/src/main/resources/application.yml)
具体可参考示例[码云application.yml](https://gitee.com/dromara/sa-token/blob/dev/sa-token-demo/sa-token-demo-alone-redis/src/main/resources/application.yml)
### 3、测试

View File

@@ -84,5 +84,4 @@ spring:
更多框架的集成方案正在更新中... (欢迎大家提交pr)
?> SerializationException序列化异常, 如果你在集成redis的时候遇到这个报错不要紧张, 下面说一下可能的原因
?> 当你第一次使用redis的时候默认是可以的运行的, 但是切换到另一种序列化方式后就报错了, 这是因为默认SaToken会直接读取之前存储的数据, 以jackson方式读取jdk默认序列化的内容或者反过来读取都会导致读取失败. 此时只需要删除与SaToken相关的redis数据就好了

View File

@@ -51,6 +51,6 @@ StpUtil.untieDisable(10001);
// 先踢下线
StpUtil.logoutByLoginId(10001);
// 再封禁账号
StpUtil.disable(10001, 86400);
StpUtil.disableLoginId(10001, 86400);
```

View File

@@ -67,7 +67,4 @@ StpUtil.getTokenInfo();
?> 有关TokenInfo参数详解请参考[参考TokenInfo参数详解](/fun/token-info)
?> Stpuitl依赖的是当前的线程上下文不支持多线程异步调用
?> 开发者传到其它的异步方法里面,需要先将必要参数取出来,否则这一块的数据会丢失的

View File

@@ -3,7 +3,7 @@ package cn.dev33.satoken.oauth2;
import cn.dev33.satoken.oauth2.config.SaOAuth2Config;
/**
* sa-token oauth2 模块 总控类
* Sa-Token-OAuth2 模块 总控类
*
* @author kong
*

View File

@@ -1,40 +1,118 @@
package cn.dev33.satoken.oauth2.config;
import java.io.Serializable;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.util.SaResult;
/**
* sa-token oauth2 配置类 Model
* Sa-Token-OAuth2 配置类 Model
* @author kong
*
*/
public class SaOAuth2Config {
public class SaOAuth2Config implements Serializable {
/**
* 授权码默认保存的时间(单位秒) 默认五分钟
*/
private long codeTimeout = 60 * 5;
private static final long serialVersionUID = -6541180061782004705L;
/**
* access_token默认保存的时间(单位秒) 默认两个小时
*/
private long accessTokenTimeout = 60 * 60 * 2;
/** 是否打开模式授权码Authorization Code */
public Boolean isCode = true;
/**
* refresh_token默认保存的时间(单位秒) 默认30 天
*/
private long refreshTokenTimeout = 60 * 60 * 24 * 30;
/** 是否打开模式隐藏式Implicit */
public Boolean isImplicit = false;
/**
* client_token默认保存的时间(单位秒) 默认两个小时
*/
private long clientTokenTimeout = 60 * 60 * 2;
/** 是否打开模式密码式Password */
public Boolean isPassword = false;
/** 是否打开模式凭证式Client Credentials */
public Boolean isClient = false;
/** 是否在每次 Refresh-Token 刷新 Access-Token 时,产生一个新的 Refresh-Token */
public Boolean isNewRefresh = false;
/** Code授权码 保存的时间(单位秒) 默认五分钟 */
public long codeTimeout = 60 * 5;
/** Access-Token 保存的时间(单位秒) 默认两个小时 */
public long accessTokenTimeout = 60 * 60 * 2;
/** Refresh-Token 保存的时间(单位秒) 默认30 天 */
public long refreshTokenTimeout = 60 * 60 * 24 * 30;
/** Client-Token 保存的时间(单位秒) 默认两个小时 */
public long clientTokenTimeout = 60 * 60 * 2;
/**
* @return isCode
*/
public Boolean getIsCode() {
return isCode;
}
/**
* @param isCode 要设置的 isCode
*/
public void setIsCode(Boolean isCode) {
this.isCode = isCode;
}
/**
* @return isImplicit
*/
public Boolean getIsImplicit() {
return isImplicit;
}
/**
* @param isImplicit 要设置的 isImplicit
*/
public void setIsImplicit(Boolean isImplicit) {
this.isImplicit = isImplicit;
}
/**
* @return isPassword
*/
public Boolean getIsPassword() {
return isPassword;
}
/**
* @param isPassword 要设置的 isPassword
*/
public void setIsPassword(Boolean isPassword) {
this.isPassword = isPassword;
}
/**
* @return isClient
*/
public Boolean getIsClient() {
return isClient;
}
/**
* @param isClient 要设置的 isClient
*/
public void setIsClient(Boolean isClient) {
this.isClient = isClient;
}
/**
* @return isNewRefresh
*/
public Boolean getIsNewRefresh() {
return isNewRefresh;
}
/**
* @param isNewRefresh 要设置的 isNewRefresh
*/
public void setIsNewRefresh(Boolean isNewRefresh) {
this.isNewRefresh = isNewRefresh;
}
/**
* @return codeTimeout
*/
@@ -100,26 +178,13 @@ public class SaOAuth2Config {
}
// -------------------- SaOAuth2Handle 所有回调函数 --------------------
/**
* OAuth-Server端未登录时返回的View
*/
public Supplier<Object> notLoginView = () -> "当前会话在OAuth-Server认证中心尚未登录";
/**
* OAuth-Server端重定向URL无效时返回的View
*/
public BiFunction<String, String, Object> invalidUrlView = (clientId, url) -> "无效重定向URL" + url;
/**
* OAuth-Server端Client请求的Scope暂未签约时返回的View
*/
public BiFunction<String, String, Object> invalidScopeView = (clientId, scope) -> "请求的Scope暂未签约";
/**
* OAuth-Server端确认授权时返回的View
*/
@@ -130,12 +195,6 @@ public class SaOAuth2Config {
*/
public BiFunction<String, String, Object> doLoginHandle = (name, pwd) -> SaResult.error();
/**
* SSO-Client端发送Http请求的处理函数
*/
public Function<String, Object> sendHttp = url -> {throw new SaTokenException("请配置Http处理器");};
/**
* @param notLoginView OAuth-Server端未登录时返回的View
* @return 对象自身
@@ -145,24 +204,6 @@ public class SaOAuth2Config {
return this;
}
/**
* @param invalidScopeView OAuth-Server端重定向URL无效时返回的View
* @return 对象自身
*/
public SaOAuth2Config setInvalidUrlView(BiFunction<String, String, Object> invalidUrlView) {
this.invalidUrlView = invalidUrlView;
return this;
}
/**
* @param invalidScopeView OAuth-Server端Client请求的Scope暂未签约时返回的View
* @return 对象自身
*/
public SaOAuth2Config setInvalidScopeView(BiFunction<String, String, Object> invalidScopeView) {
this.invalidScopeView = invalidScopeView;
return this;
}
/**
* @param confirmView OAuth-Server端确认授权时返回的View
* @return 对象自身
@@ -181,23 +222,13 @@ public class SaOAuth2Config {
return this;
}
/**
* @param sendHttp 发送Http请求的处理函数
* @return 对象自身
*/
public SaOAuth2Config setSendHttp(Function<String, Object> sendHttp) {
this.sendHttp = sendHttp;
return this;
}
@Override
public String toString() {
return "SaOAuth2Config [codeTimeout=" + codeTimeout + ", accessTokenTimeout=" + accessTokenTimeout
+ ", refreshTokenTimeout=" + refreshTokenTimeout + "]";
return "SaOAuth2Config [isCode=" + isCode + ", isImplicit=" + isImplicit + ", isPassword=" + isPassword
+ ", isClient=" + isClient + ", isNewRefresh=" + isNewRefresh + ", codeTimeout=" + codeTimeout
+ ", accessTokenTimeout=" + accessTokenTimeout + ", refreshTokenTimeout=" + refreshTokenTimeout
+ ", clientTokenTimeout=" + clientTokenTimeout + "]";
}
}

View File

@@ -12,10 +12,12 @@ public class SaOAuth2Consts {
* @author kong
*/
public static final class Api {
/** OAuth-Server端授权地址 */
public static String authorize = "/oauth2/authorize";
public static String token = "/oauth2/token";
public static String refresh = "/oauth2/refresh";
public static String client_token = "/oauth2/client_token";
public static String doLogin = "/oauth2/doLogin";
public static String doConfirm = "/oauth2/doConfirm";
}
/**
@@ -23,70 +25,44 @@ public class SaOAuth2Consts {
* @author kong
*/
public static final class Param {
/** authorize 的 返回值类型 */
public static String response_type = "response_type";
/** client_id 参数名称 */
public static String client_id = "client_id";
/** client_secret 参数名称 */
public static String client_secret = "client_secret";
/** redirect_uri 参数名称 */
public static String redirect_uri = "redirect_uri";
/** scope 参数名称 */
public static String scope = "scope";
/** state */
public static String state = "state";
/** code 参数名称 */
public static String code = "code";
/** token 参数名称 */
public static String token = "token";
/** grant_type 参数名称 */
public static String refresh_token = "refresh_token";
public static String grant_type = "grant_type";
public static String username = "username";
public static String password = "password";
public static String name = "name";
public static String pwd = "pwd";
}
/**
* 所有返回类型
*/
public static final class ResponseType {
public static String code = "code";
public static String token = "token";
}
/**
* 所有授权类型
*/
public static final class AuthType {
/** 方式一:授权码 */
public static String code = "code";
/** 方式二:隐藏式 */
public static String token = "token";
/** 方式三:密码式 */
public static String password = "password";
/** 方式四:凭证式 */
public static String client_credentials = "client_credentials";
public static final class GrantType {
public static String authorization_code = "authorization_code";
public static String refresh_token = "refresh_token";
public static String password = "password";
public static String client_credentials = "client_credentials";
}
/**
* 在保存授权码时用到的key
*/
public static final String UNLIMITED_DOMAIN = "*";
/** 表示OK的返回结果 */
public static final String OK = "ok";
/** 表示请求没有得到任何有效处理 */
public static final String NOT_HANDLE = "not handle";
/** 表示请求没有得到任何有效处理 {msg: "not handle"} */
public static final String NOT_HANDLE = "{\"msg\": \"not handle\"}";
}

View File

@@ -5,12 +5,15 @@ import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.oauth2.SaOAuth2Manager;
import cn.dev33.satoken.oauth2.config.SaOAuth2Config;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Consts.AuthType;
import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Consts.Api;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Consts.GrantType;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Consts.Param;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Consts.ResponseType;
import cn.dev33.satoken.oauth2.model.AccessTokenModel;
import cn.dev33.satoken.oauth2.model.ClientTokenModel;
import cn.dev33.satoken.oauth2.model.CodeModel;
import cn.dev33.satoken.oauth2.model.RequestAuthModel;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
@@ -20,106 +23,236 @@ import cn.dev33.satoken.util.SaResult;
*
*/
public class SaOAuth2Handle {
/**
* 处理Server端请求
* 处理Server端请求 路由分发
* @return 处理结果
*/
public static Object authorize() {
public static Object serverRequest() {
// 获取变量
SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse();
SaOAuth2Config cfg = SaOAuth2Manager.getConfig();
// StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;
// match(Api.authorize) &&
// 授权
if(req.isParam(Param.response_type, AuthType.code)) {
// 1、构建请求Model TODO 貌似这个RequestAuthModel对象也可以省略掉
RequestAuthModel ra = SaOAuth2Util.generateRequestAuth(req, StpUtil.getLoginId());
// 2、如果尚未登录, 则先去登录
if(StpUtil.isLogin() == false) {
return cfg.notLoginView.get();
}
// 3、判断重定向域名的格式是否合法
boolean isRigh = SaOAuth2Util.isRightUrl(ra.clientId, ra.redirectUri);
if(isRigh == false) {
return cfg.invalidUrlView.apply(ra.clientId, ra.redirectUri);
}
// 4、判断此次申请的Scope该Client是否已经签约
boolean isContract = SaOAuth2Util.isContract(ra.clientId, ra.scope);
if(isContract == false) {
return cfg.invalidScopeView.apply(ra.clientId, ra.scope);
}
// 5、判断此次申请的Scope该用户是否已经授权过了
boolean isGrant = SaOAuth2Util.isGrant(StpUtil.getLoginId(), ra.clientId, ra.scope);
if(isGrant == false) {
// 如果尚未授权,则转到授权页面,开始授权操作
return cfg.confirmView.apply(ra.clientId, ra.scope);
}
// ------------------ 路由分发 ------------------
// 模式一Code授权码
if(req.isPath(Api.authorize) && req.isParam(Param.response_type, ResponseType.code) && cfg.isCode) {
return authorize(req, res, cfg);
}
// 6、开始重定向授权下放code
// Code授权码 获取 Access-Token
if(req.isPath(Api.token) && req.isParam(Param.grant_type, GrantType.authorization_code)) {
return token(req, res, cfg);
}
// Refresh-Token 刷新 Access-Token
if(req.isPath(Api.refresh) && req.isParam(Param.grant_type, GrantType.refresh_token)) {
return refreshToken(req);
}
// doLogin 登录接口
if(req.isPath(Api.doLogin)) {
return doLogin(req, res, cfg);
}
// doConfirm 确认授权接口
if(req.isPath(Api.doConfirm)) {
return doConfirm(req);
}
// 模式二:隐藏式
if(req.isPath(Api.authorize) && req.isParam(Param.response_type, ResponseType.token) && cfg.isImplicit) {
return authorize(req, res, cfg);
}
// 模式三:密码式
if(req.isPath(Api.token) && req.isParam(Param.grant_type, GrantType.password) && cfg.isPassword) {
return password(req, res, cfg);
}
// 模式四:凭证式
if(req.isPath(Api.client_token) && req.isParam(Param.grant_type, GrantType.client_credentials) && cfg.isClient) {
return clientToken(req, res, cfg);
}
// 默认返回
return SaOAuth2Consts.NOT_HANDLE;
}
/**
* 模式一Code授权码 / 模式二:隐藏式
* @param req 请求对象
* @param res 响应对象
* @param cfg 配置对象
* @return 处理结果
*/
public static Object authorize(SaRequest req, SaResponse res, SaOAuth2Config cfg) {
// 1、如果尚未登录, 则先去登录
if(StpUtil.isLogin() == false) {
return cfg.notLoginView.get();
}
// 2、构建请求Model
RequestAuthModel ra = SaOAuth2Util.generateRequestAuth(req, StpUtil.getLoginId());
// 3、校验重定向域名是否合法
SaOAuth2Util.checkRightUrl(ra.clientId, ra.redirectUri);
// 4、校验此次申请的Scope该Client是否已经签约
SaOAuth2Util.checkContract(ra.clientId, ra.scope);
// 5、判断如果此次申请的Scope该用户尚未授权则转到授权页面
boolean isGrant = SaOAuth2Util.isGrant(ra.loginId, ra.clientId, ra.scope);
if(isGrant == false) {
return cfg.confirmView.apply(ra.clientId, ra.scope);
}
// 6、判断授权类型
// 如果是 授权码式开始重定向授权下放code
if(ResponseType.code.equals(ra.responseType)) {
CodeModel codeModel = SaOAuth2Util.generateCode(ra);
String redirectUri = SaOAuth2Util.buildRedirectUri(ra.redirectUri, codeModel.code, ra.state);
return res.redirect(redirectUri);
}
// 默认返回
return SaOAuth2Consts.NOT_HANDLE;
}
/**
* 获取Token
* @return
*/
public static Object token() {
// 获取变量
SaRequest req = SaHolder.getRequest();
// SaResponse res = SaHolder.getResponse();
// SaOAuth2Config cfg = SaOAuth2Manager.getConfig();
// 根据code换token
if(req.isParam(Param.grant_type, AuthType.authorization_code)) {
System.out.println("------------获取token");
// 获取参数
String code = req.getParamNotNull(Param.code); // code码
String clientId = req.getParamNotNull(Param.client_id); // 应用id
String clientSecret = req.getParamNotNull(Param.client_secret); // 应用秘钥
String redirectUri = req.getParam(Param.redirect_uri); // 应用秘钥
// 校验参数
SaOAuth2Util.checkCodeIdSecret(code, clientId, clientSecret, redirectUri);
// 构建 access_token
AccessTokenModel token = SaOAuth2Util.generateAccessToken(code);
// 返回
return SaResult.data(token.toLineMap());
// 如果是 隐藏式,则:开始重定向授权,下放 token
if(ResponseType.token.equals(ra.responseType)) {
AccessTokenModel at = SaOAuth2Util.generateAccessToken(ra, false);
String redirectUri = SaOAuth2Util.buildImplicitRedirectUri(ra.redirectUri, at.accessToken, ra.state);
return res.redirect(redirectUri);
}
// 默认返回
return SaOAuth2Consts.NOT_HANDLE;
throw new SaOAuth2Exception("无效response_type: " + ra.responseType);
}
/**
* Code授权码 获取 Access-Token
* @param req 请求对象
* @param res 响应对象
* @param cfg 配置对象
* @return 处理结果
*/
public static Object token(SaRequest req, SaResponse res, SaOAuth2Config cfg) {
// 获取参数
String code = req.getParamNotNull(Param.code);
String clientId = req.getParamNotNull(Param.client_id);
String clientSecret = req.getParamNotNull(Param.client_secret);
String redirectUri = req.getParam(Param.redirect_uri);
// 校验参数
SaOAuth2Util.checkGainTokenParam(code, clientId, clientSecret, redirectUri);
// 构建 Access-Token
AccessTokenModel token = SaOAuth2Util.generateAccessToken(code);
// 返回
return SaResult.data(token.toLineMap());
}
/**
* 路由匹配算法
* @param pattern 路由表达式
* @return 是否可以匹配
* Refresh-Token 刷新 Access-Token
* @param req 请求对象
* @return 处理结果
*/
static boolean match(String pattern) {
return SaRouter.isMatch(pattern, SaHolder.getRequest().getRequestPath());
public static Object refreshToken(SaRequest req) {
// 获取参数
String clientId = req.getParamNotNull(Param.client_id);
String clientSecret = req.getParamNotNull(Param.client_secret);
String refreshToken = req.getParamNotNull(Param.refresh_token);
// 校验参数
SaOAuth2Util.checkRefreshTokenParam(clientId, clientSecret, refreshToken);
// 获取新Token返回
Object data = SaOAuth2Util.saOAuth2Template.refreshAccessToken(refreshToken).toLineMap();
return SaResult.data(data);
}
/**
* doLogin 登录接口
* @param req 请求对象
* @param res 响应对象
* @param cfg 配置对象
* @return 处理结果
*/
public static Object doLogin(SaRequest req, SaResponse res, SaOAuth2Config cfg) {
return cfg.doLoginHandle.apply(req.getParamNotNull(Param.name), req.getParamNotNull("pwd"));
}
/**
* doConfirm 确认授权接口
* @param req 请求对象
* @return 处理结果
*/
public static Object doConfirm(SaRequest req) {
String clientId = req.getParamNotNull(Param.client_id);
String scope = req.getParamNotNull(Param.scope);
Object loginId = StpUtil.getLoginId();
SaOAuth2Util.saveGrantScope(clientId, loginId, scope);
return SaResult.ok();
}
/**
* 模式三:密码式
* @param req 请求对象
* @param res 响应对象
* @param cfg 配置对象
* @return 处理结果
*/
public static Object password(SaRequest req, SaResponse res, SaOAuth2Config cfg) {
// 1、获取请求参数
String username = req.getParamNotNull(Param.username);
String password = req.getParamNotNull(Param.password);
String clientId = req.getParamNotNull(Param.client_id);
// 2、校验client_id
SaOAuth2Util.checkClientModel(clientId);
// 3、防止因前端误传token造成逻辑干扰
SaHolder.getStorage().set(StpUtil.stpLogic.splicingKeyJustCreatedSave(), "no-token");
// 4、调用API 开始登录,如果没能成功登录,则直接退出
Object retObj = cfg.doLoginHandle.apply(username, password);
if(StpUtil.isLogin() == false) {
return retObj;
}
// 5、构建 ra对象
RequestAuthModel ra = new RequestAuthModel();
ra.clientId = clientId;
ra.loginId = StpUtil.getLoginId();
ra.scope = req.getParam(Param.scope, "");
// 6、生成 Access-Token
AccessTokenModel at = SaOAuth2Util.generateAccessToken(ra, true);
// 7、返回 Access-Token
return SaResult.data(at.toLineMap());
}
/**
* 模式四:凭证式
* @return 处理结果
*/
public static Object clientToken(SaRequest req, SaResponse res, SaOAuth2Config cfg) {
// 获取参数
String clientId = req.getParamNotNull(Param.client_id);
String clientSecret = req.getParamNotNull(Param.client_secret);
String scope = req.getParam(Param.scope);
// 校验 ClientSecret
SaOAuth2Util.checkClientSecret(clientId, clientSecret);
// 返回 Client-Token
ClientTokenModel ct = SaOAuth2Util.generateClientToken(clientId, scope);
// 返回 Client-Token
return SaResult.data(ct.toLineMap());
}
}

View File

@@ -1,199 +1,39 @@
package cn.dev33.satoken.oauth2.logic;
import java.util.List;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.oauth2.model.AccessTokenModel;
import cn.dev33.satoken.oauth2.model.ClientTokenModel;
import cn.dev33.satoken.oauth2.model.CodeModel;
import cn.dev33.satoken.oauth2.model.RefreshTokenModel;
import cn.dev33.satoken.oauth2.model.RequestAuthModel;
import cn.dev33.satoken.oauth2.model.SaClientModel;
/**
* sa-token-oauth2 模块 静态类接口转发, 方便调用
* Sa-Token-OAuth2 模块 工具类
* @author kong
*
*
*/
public class SaOAuth2Util {
/**
* 模板代码对象
*/
public static SaOAuth2Template saOAuth2Template = new SaOAuth2Template();
/**
* 根据 SaRequest 对象创建 RequestAuthModel
* @param req SaRequest对象
* @param loginId 账号id
* @return RequestAuthModel对象
*/
public static RequestAuthModel generateRequestAuth(SaRequest req, Object loginId) {
return saOAuth2Template.generateRequestAuth(req, loginId);
}
// ---------------------------------------------- 分界线 -----------------------------------------------------
// ------------------- 获取数据
// ------------------- 资源获取
/**
* 返回此平台所有权限集合
* @return 此平台所有权限名称集合
*/
public static List<String> getAppScopeList() {
return saOAuth2Template.getAppScopeList();
}
/**
* 返回指定Client签约的所有Scope名称集合
* @param clientId 应用id
* @return Scope集合
*/
public static List<String> getClientScopeList(String clientId) {
return saOAuth2Template.getClientScopeList(clientId);
}
/**
* 获取指定 LoginId 对指定 Client 已经授权过的所有 Scope
* @param clientId 应用id
* @param loginId 账号id
* @return Scope集合
*/
public static List<String> getGrantScopeList(Object loginId, String clientId) {
return saOAuth2Template.getGrantScopeList(loginId, clientId);
}
// ------------------- 数据校验
/**
* [OK] 判断该Client是否签约了指定的Scope
* 根据id获取Client信息, 如果Client为空则抛出异常
* @param clientId 应用id
* @param scope 权限
* @return ClientModel
*/
public static boolean isContract(String clientId, String scope) {
return saOAuth2Template.isContract(clientId, scope);
public static SaClientModel checkClientModel(String clientId) {
return saOAuth2Template.checkClientModel(clientId);
}
/**
* 指定 loginId 是否对一个 Client 授权给了指定 Scope
* @param clientId 应用id
* @param scope 权限
* @param loginId 账号id
* @return 是否已经授权
*/
public static boolean isGrant(Object loginId, String clientId, String scope) {
return saOAuth2Template.isGrant(loginId, clientId, scope);
}
/**
* [OK] 指定Client使用指定url作为回调地址是否合法
* @param clientId 应用id
* @param url 指定url
* @return 是否合法
*/
public static boolean isRightUrl(String clientId, String url) {
return saOAuth2Template.isRightUrl(clientId, url);
}
/**
* [OK 方法名改一下]校验code、clientId、clientSecret 三者是否正确
* @param code 授权码
* @param clientId 应用id
* @param clientSecret 秘钥
* @param redirectUri 秘钥
* @return CodeModel对象
*/
public static CodeModel checkCodeIdSecret(String code, String clientId, String clientSecret, String redirectUri) {
return saOAuth2Template.checkCodeIdSecret(code, clientId, clientSecret, redirectUri);
}
/**
* [default] 校验access_token、clientId、clientSecret 三者是否正确
* @param accessToken access_token
* @param clientId 应用id
* @param clientSecret 秘钥
* @return AccessTokenModel对象
*/
public static AccessTokenModel checkTokenIdSecret(String accessToken, String clientId, String clientSecret) {
return saOAuth2Template.checkTokenIdSecret(accessToken, clientId, clientSecret);
}
// ------------------- 逻辑相关
/**
* [OK] 根据参数生成一个授权码并返回
* @param authModel 请求授权参数Model
* @return 授权码Model
*/
public static CodeModel generateCode(RequestAuthModel authModel) {
return saOAuth2Template.generateCode(authModel);
}
/**
* 根据授权码获得授权码Model
* @param code 授权码
* @return 授权码Model
*/
public static CodeModel getCode(String code) {
return saOAuth2Template.getCode(code);
}
/**
* [default] 删除一个授权码
* @param code 授权码
*/
public static void deleteCode(String code) {
saOAuth2Template.deleteCode(code);
}
/**
* 根据授权码Model生成一个access_token
* @param codeModel 授权码Model
* @return AccessTokenModel
*/
public static AccessTokenModel generateAccessToken(String code) {
return saOAuth2Template.generateAccessToken(code);
}
/**
* [default] 根据 access_token 获得其Model详细信息
* @param accessToken access_token
* @return AccessTokenModel (授权码Model)
*/
public static AccessTokenModel getAccessToken(String accessToken) {
return saOAuth2Template.getAccessToken(accessToken);
}
/**
* 根据 refresh_token 生成一个新的 access_token
* @param refreshToken refresh_token
* @return 新的 access_token
*/
public static AccessTokenModel refreshAccessToken(String refreshToken) {
return saOAuth2Template.refreshAccessToken(refreshToken);
}
/**
* [default] 根据 refresh_token 获得其Model详细信息
* @param refreshToken refresh_token
* @return RefreshToken
*/
public static RefreshTokenModel getRefreshToken(String refreshToken) {
return saOAuth2Template.getRefreshToken(refreshToken);
}
/**
* [default] 获取 access_token 所代表的LoginId
* 获取 access_token 所代表的LoginId
* @param accessToken access_token
* @return LoginId
*/
@@ -202,27 +42,85 @@ public class SaOAuth2Util {
}
/**
* 构建:AccessToken Model (根据RequestAuthModel) 用于隐藏式
* @param ra 请求授权参数Model
* @return 授权码Model
* 获取 Access-Token如果AccessToken为空则抛出异常
* @param accessToken .
* @return .
*/
public static AccessTokenModel generateAccessToken(RequestAuthModel ra) {
return saOAuth2Template.generateAccessToken(ra);
public static AccessTokenModel checkAccessToken(String accessToken) {
return saOAuth2Template.checkAccessToken(accessToken);
}
/**
* 获取 Client-Token如果ClientToken为空则抛出异常
* @param clientToken .
* @return .
*/
public static ClientTokenModel checkClientToken(String clientToken) {
return saOAuth2Template.checkClientToken(clientToken);
}
// ------------------- generate 构建数据
/**
* 构建Model请求Model
* @param req SaRequest对象
* @param loginId 账号id
* @return RequestAuthModel对象
*/
public static RequestAuthModel generateRequestAuth(SaRequest req, Object loginId) {
return saOAuth2Template.generateRequestAuth(req, loginId);
}
/**
* 构建ModelCode授权码
* @param ra 请求参数Model
* @return 授权码Model
*/
public static CodeModel generateCode(RequestAuthModel ra) {
return saOAuth2Template.generateCode(ra);
}
/**
* 构建ModelAccess-Token
* @param code 授权码Model
* @return AccessToken Model
*/
public static AccessTokenModel generateAccessToken(String code) {
return saOAuth2Template.generateAccessToken(code);
}
/**
* 构建ClientToken Model
* @param ra 请求授权参数Model
* @return ClientToken-Model
* 刷新Model根据 Refresh-Token 生成一个新的 Access-Token
* @param refreshToken Refresh-Token值
* @return 新的 Access-Token
*/
public static AccessTokenModel refreshAccessToken(String refreshToken) {
return saOAuth2Template.refreshAccessToken(refreshToken);
}
/**
* 构建ModelAccess-Token (根据RequestAuthModel构建用于隐藏式 and 密码式)
* @param ra 请求参数Model
* @param isCreateRt 是否生成对应的Refresh-Token
* @return Access-Token Model
*/
public static AccessTokenModel generateAccessToken(RequestAuthModel ra, boolean isCreateRt) {
return saOAuth2Template.generateAccessToken(ra, isCreateRt);
}
/**
* 构建ModelClient-Token
* @param clientId 应用id
* @param scope 授权范围
* @return Client-Token Model
*/
public static ClientTokenModel generateClientToken(String clientId, String scope) {
return saOAuth2Template.generateClientToken(clientId, scope);
}
// ------------------- 自定义策略相关
/**
* [OK] 构建URL下放授权码URL
* 构建URL下放Code URL (Authorization Code 授权码)
* @param redirectUri 下放地址
* @param code code参数
* @param state state参数
@@ -231,16 +129,144 @@ public class SaOAuth2Util {
public static String buildRedirectUri(String redirectUri, String code, String state) {
return saOAuth2Template.buildRedirectUri(redirectUri, code, state);
}
/**
* [OK] 构建URL下放Token URL
* 构建URL下放Access-Token URL implicit 隐藏式)
* @param redirectUri 下放地址
* @param token token
* @param state state参数
* @return 构建完毕的URL
*/
public static String buildRedirectUri2(String redirectUri, String token, String state) {
return saOAuth2Template.buildRedirectUri2(redirectUri, token, state);
public static String buildImplicitRedirectUri(String redirectUri, String token, String state) {
return saOAuth2Template.buildImplicitRedirectUri(redirectUri, token, state);
}
// ------------------- 数据校验
/**
* 判断:指定 loginId 是否对一个 Client 授权给了指定 Scope
* @param loginId 账号id
* @param clientId 应用id
* @param scope 权限
* @return 是否已经授权
*/
public static boolean isGrant(Object loginId, String clientId, String scope) {
return saOAuth2Template.isGrant(loginId, clientId, scope);
}
/**
* 校验该Client是否签约了指定的Scope
* @param clientId 应用id
* @param scope 权限(多个用逗号隔开)
*/
public static void checkContract(String clientId, String scope) {
saOAuth2Template.checkContract(clientId, scope);
}
/**
* 校验该Client使用指定url作为回调地址是否合法
* @param clientId 应用id
* @param url 指定url
*/
public static void checkRightUrl(String clientId, String url) {
saOAuth2Template.checkRightUrl(clientId, url);
}
/**
* 校验clientId 与 clientSecret 是否正确
* @param clientId 应用id
* @param clientSecret 秘钥
* @return SaClientModel对象
*/
public static SaClientModel checkClientSecret(String clientId, String clientSecret) {
return saOAuth2Template.checkClientSecret(clientId, clientSecret);
}
/**
* 校验:使用 code 获取 token 时提供的参数校验
* @param code 授权码
* @param clientId 应用id
* @param clientSecret 秘钥
* @param redirectUri 重定向地址
* @return CodeModel对象
*/
public static CodeModel checkGainTokenParam(String code, String clientId, String clientSecret, String redirectUri) {
return saOAuth2Template.checkGainTokenParam(code, clientId, clientSecret, redirectUri);
}
/**
* 校验:使用 Refresh-Token 刷新 Access-Token 时提供的参数校验
* @param clientId 应用id
* @param clientSecret 秘钥
* @param refreshToken Refresh-Token
* @return CodeModel对象
*/
public static RefreshTokenModel checkRefreshTokenParam(String clientId, String clientSecret, String refreshToken) {
return saOAuth2Template.checkRefreshTokenParam(clientId, clientSecret, refreshToken);
}
// ------------------- save 数据
/**
* 持久化:用户授权记录
* @param clientId 应用id
* @param loginId 账号id
* @param scope 权限列表(多个逗号隔开)
*/
public static void saveGrantScope(String clientId, Object loginId, String scope) {
saOAuth2Template.saveGrantScope(clientId, loginId, scope);
}
// ------------------- get 数据
/**
* 获取Code Model
* @param code .
* @return .
*/
public static CodeModel getCode(String code) {
return saOAuth2Template.getCode(code);
}
/**
* 获取Access-Token Model
* @param accessToken .
* @return .
*/
public static AccessTokenModel getAccessToken(String accessToken) {
return saOAuth2Template.getAccessToken(accessToken);
}
/**
* 获取Refresh-Token Model
* @param refreshToken .
* @return .
*/
public static RefreshTokenModel getRefreshToken(String refreshToken) {
return saOAuth2Template.getRefreshToken(refreshToken);
}
/**
* 获取Client-Token Model
* @param clientToken .
* @return .
*/
public static ClientTokenModel getClientToken(String clientToken) {
return saOAuth2Template.getClientToken(clientToken);
}
/**
* 获取:用户授权记录
* @param clientId 应用id
* @param loginId 账号id
* @return 权限
*/
public static String getGrantScope(String clientId, Object loginId) {
return saOAuth2Template.getGrantScope(clientId, loginId);
}
}

View File

@@ -1,32 +1,35 @@
package cn.dev33.satoken.oauth2.model;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Model: access_token
* Model: Access-Token
* @author kong
*
*/
public class AccessTokenModel {
public class AccessTokenModel implements Serializable {
private static final long serialVersionUID = -6541180061782004705L;
/**
* access_token 值
* Access-Token 值
*/
public String accessToken;
/**
* refresh_token 值
* Refresh-Token 值
*/
public String refreshToken;
/**
* access_token 到期时间
* Access-Token 到期时间
*/
public long expiresTime;
/**
* refresh_token 到期时间
* Refresh-Token 到期时间
*/
public long refreshExpiresTime;
@@ -92,7 +95,6 @@ public class AccessTokenModel {
return s < 1 ? -2 : s;
}
/**
* 将所有属性转换为下划线形式的Map
* @return

View File

@@ -1,22 +1,25 @@
package cn.dev33.satoken.oauth2.model;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Model: client_token
* Model: Client-Token
* @author kong
*
*/
public class ClientTokenModel {
public class ClientTokenModel implements Serializable {
private static final long serialVersionUID = -6541180061782004705L;
/**
* client_token 值
* Client-Token 值
*/
public String clientToken;
/**
* client_token 到期时间
* Client-Token 到期时间
*/
public long expiresTime;

View File

@@ -1,11 +1,15 @@
package cn.dev33.satoken.oauth2.model;
import java.io.Serializable;
/**
* Model: [授权码 - 数据 对应关系]
* Model: 授权码
* @author kong
*
*/
public class CodeModel {
public class CodeModel implements Serializable {
private static final long serialVersionUID = -6541180061782004705L;
/**
* 授权码

View File

@@ -1,18 +1,23 @@
package cn.dev33.satoken.oauth2.model;
import java.io.Serializable;
/**
* Model: refresh_token
* Model: Refresh-Token
* @author kong
*
*/
public class RefreshTokenModel {
public class RefreshTokenModel implements Serializable {
private static final long serialVersionUID = -6541180061782004705L;
/**
* refresh_token 值
* Refresh-Token 值
*/
public String refreshToken;
/**
* refresh_token到期时间
* Refresh-Token 到期时间
*/
public long expiresTime;

View File

@@ -1,5 +1,7 @@
package cn.dev33.satoken.oauth2.model;
import java.io.Serializable;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.util.SaFoxUtil;
@@ -8,7 +10,9 @@ import cn.dev33.satoken.util.SaFoxUtil;
* @author kong
*
*/
public class RequestAuthModel {
public class RequestAuthModel implements Serializable {
private static final long serialVersionUID = -6541180061782004705L;
/**
* 应用id

View File

@@ -0,0 +1,115 @@
package cn.dev33.satoken.oauth2.model;
import java.io.Serializable;
/**
* Client应用信息 Model
* @author kong
*
*/
public class SaClientModel implements Serializable {
private static final long serialVersionUID = -6541180061782004705L;
/**
* 应用id
*/
public String clientId;
/**
* 应用秘钥
*/
public String clientSecret;
/**
* 应用签约的所有权限, 多个用逗号隔开
*/
public String contractScope;
/**
* 应用允许授权的所有URL, 多个用逗号隔开
*/
public String allowUrl;
public SaClientModel() {
}
public SaClientModel(String clientId, String clientSecret, String contractScope, String allowUrl) {
super();
this.clientId = clientId;
this.clientSecret = clientSecret;
this.contractScope = contractScope;
this.allowUrl = allowUrl;
}
/**
* @return 应用id
*/
public String getClientId() {
return clientId;
}
/**
* @param clientId 应用id
* @return 对象自身
*/
public SaClientModel setClientId(String clientId) {
this.clientId = clientId;
return this;
}
/**
* @return 应用秘钥
*/
public String getClientSecret() {
return clientSecret;
}
/**
* @param clientSecret 应用秘钥
* @return 对象自身
*/
public SaClientModel setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
return this;
}
/**
* @return 应用签约的所有权限, 多个用逗号隔开
*/
public String getContractScope() {
return contractScope;
}
/**
* @param contractScope 应用签约的所有权限, 多个用逗号隔开
* @return 对象自身
*/
public SaClientModel setContractScope(String contractScope) {
this.contractScope = contractScope;
return this;
}
/**
* @return 应用允许授权的所有URL, 多个用逗号隔开
*/
public String getAllowUrl() {
return allowUrl;
}
/**
* @param allowUrl 应用允许授权的所有URL, 多个用逗号隔开
* @return 对象自身
*/
public SaClientModel setAllowUrl(String allowUrl) {
this.allowUrl = allowUrl;
return this;
}
@Override
public String toString() {
return "SaClientModel [clientId=" + clientId + ", clientSecret=" + clientSecret + ", contractScope="
+ contractScope + ", allowUrl=" + allowUrl + "]";
}
}

View File

@@ -2,8 +2,6 @@ package cn.dev33.satoken.reactor.spring;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component;
import org.springframework.util.PathMatcher;
import cn.dev33.satoken.SaManager;
@@ -21,8 +19,6 @@ import cn.dev33.satoken.temp.SaTempInterface;
* @author kong
*
*/
@Component
@Import({SaHistoryVersionInject.class, SaBeanRegister.class})
public class SaBeanInject {
/**

View File

@@ -1 +1,4 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.dev33.satoken.reactor.spring.SaBeanInject
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.dev33.satoken.reactor.spring.SaBeanRegister,\
cn.dev33.satoken.reactor.spring.SaBeanInject,\
cn.dev33.satoken.reactor.spring.SaHistoryVersionInject

View File

@@ -26,6 +26,13 @@
<artifactId>spring-boot-starter-web</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
<!-- OAuth2.0 (optional) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-oauth2</artifactId>
<version>${sa-token-version}</version>
<optional>true</optional>
</dependency>
<!-- test -->
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@@ -2,8 +2,6 @@ package cn.dev33.satoken.spring;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component;
import org.springframework.util.PathMatcher;
import cn.dev33.satoken.SaManager;
@@ -21,8 +19,6 @@ import cn.dev33.satoken.temp.SaTempInterface;
* @author kong
*
*/
@Component
@Import({SaBeanRegister.class, SaHistoryVersionInject.class})
public class SaBeanInject {
/**

View File

@@ -0,0 +1,40 @@
package cn.dev33.satoken.spring.oauth2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import cn.dev33.satoken.oauth2.SaOAuth2Manager;
import cn.dev33.satoken.oauth2.config.SaOAuth2Config;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Template;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Util;
/**
* 注入 Sa-Token-OAuth2 所需要的Bean
*
* @author kong
*
*/
@ConditionalOnClass(SaOAuth2Manager.class)
public class SaOAuth2BeanInject {
/**
* 注入OAuth2配置Bean
*
* @param saOAuth2Config 配置对象
*/
@Autowired(required = false)
public void setSaOAuth2Config(SaOAuth2Config saOAuth2Config) {
SaOAuth2Manager.setConfig(saOAuth2Config);
}
/**
* 注入代码模板Bean
*
* @param saOAuth2Template 代码模板Bean
*/
@Autowired(required = false)
public void setSaOAuth2Interface(SaOAuth2Template saOAuth2Template) {
SaOAuth2Util.saOAuth2Template = saOAuth2Template;
}
}

View File

@@ -0,0 +1,28 @@
package cn.dev33.satoken.spring.oauth2;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import cn.dev33.satoken.oauth2.SaOAuth2Manager;
import cn.dev33.satoken.oauth2.config.SaOAuth2Config;
/**
* 注册 Sa-Token-OAuth2 所需要的Bean
* @author kong
*
*/
@ConditionalOnClass(SaOAuth2Manager.class)
public class SaOAuth2BeanRegister {
/**
* 获取OAuth2配置Bean
* @return 配置对象
*/
@Bean
@ConfigurationProperties(prefix = "sa-token.oauth2")
public SaOAuth2Config getSaOAuth2Config() {
return new SaOAuth2Config();
}
}

View File

@@ -0,0 +1,4 @@
/**
* Sa-Token-OAuth2 模块自动化配置只有引入了Sa-Token-OAuth2模块后此包下的代码才会开始工作
*/
package cn.dev33.satoken.spring.oauth2;

View File

@@ -1 +1,6 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.dev33.satoken.spring.SaBeanInject
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.dev33.satoken.spring.SaBeanRegister,\
cn.dev33.satoken.spring.SaBeanInject,\
cn.dev33.satoken.spring.SaHistoryVersionInject,\
cn.dev33.satoken.spring.oauth2.SaOAuth2BeanRegister,\
cn.dev33.satoken.spring.oauth2.SaOAuth2BeanInject