mirror of
https://gitee.com/dromara/sa-token.git
synced 2026-05-14 12:52:08 +08:00
OAuth2.0 beta ..
This commit is contained in:
@@ -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有效期 (单位: 秒)
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -82,6 +82,14 @@ public interface SaRequest {
|
||||
*/
|
||||
public String getRequestPath();
|
||||
|
||||
/**
|
||||
* 返回当前请求path是否为指定值
|
||||
* @return see note
|
||||
*/
|
||||
public default boolean isPath(String path) {
|
||||
return getRequestPath().equals(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回当前请求的url,例:http://xxx.com/?id=127
|
||||
* @return see note
|
||||
|
||||
@@ -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\"}";
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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() + "\""
|
||||
+ "}";
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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";
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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\
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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端启动成功");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* 以上函数为开发时必须重写实现,其余函数可以按需重写
|
||||
*/
|
||||
|
||||
|
||||
// -------------- 其它需要重写的函数
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -79,7 +79,6 @@
|
||||
<version>5.5.4</version>
|
||||
</dependency> -->
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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、测试
|
||||
|
||||
@@ -84,5 +84,4 @@ spring:
|
||||
更多框架的集成方案正在更新中... (欢迎大家提交pr)
|
||||
|
||||
|
||||
?> SerializationException序列化异常, 如果你在集成redis的时候遇到这个报错不要紧张, 下面说一下可能的原因
|
||||
?> 当你第一次使用redis的时候默认是可以的运行的, 但是切换到另一种序列化方式后就报错了, 这是因为默认SaToken会直接读取之前存储的数据, 以jackson方式读取jdk默认序列化的内容或者反过来读取都会导致读取失败. 此时只需要删除与SaToken相关的redis数据就好了
|
||||
|
||||
|
||||
@@ -51,6 +51,6 @@ StpUtil.untieDisable(10001);
|
||||
// 先踢下线
|
||||
StpUtil.logoutByLoginId(10001);
|
||||
// 再封禁账号
|
||||
StpUtil.disable(10001, 86400);
|
||||
StpUtil.disableLoginId(10001, 86400);
|
||||
```
|
||||
|
||||
|
||||
@@ -67,7 +67,4 @@ StpUtil.getTokenInfo();
|
||||
|
||||
?> 有关TokenInfo参数详解,请参考:[参考:TokenInfo参数详解](/fun/token-info)
|
||||
|
||||
?> Stpuitl依赖的是当前的线程上下文,不支持多线程异步调用
|
||||
?> 开发者传到其它的异步方法里面,需要先将必要参数取出来,否则这一块的数据会丢失的
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package cn.dev33.satoken.oauth2;
|
||||
import cn.dev33.satoken.oauth2.config.SaOAuth2Config;
|
||||
|
||||
/**
|
||||
* sa-token oauth2 模块 总控类
|
||||
* Sa-Token-OAuth2 模块 总控类
|
||||
*
|
||||
* @author kong
|
||||
*
|
||||
|
||||
@@ -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 + "]";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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\"}";
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建Model:Code授权码
|
||||
* @param ra 请求参数Model
|
||||
* @return 授权码Model
|
||||
*/
|
||||
public static CodeModel generateCode(RequestAuthModel ra) {
|
||||
return saOAuth2Template.generateCode(ra);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建Model:Access-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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建Model:Access-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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建Model:Client-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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* 授权码
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Sa-Token-OAuth2 模块自动化配置(只有引入了Sa-Token-OAuth2模块后,此包下的代码才会开始工作)
|
||||
*/
|
||||
package cn.dev33.satoken.spring.oauth2;
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user