diff --git a/yudao-example/yudao-sso-demo-by-code/pom.xml b/yudao-example/yudao-sso-demo-by-code/pom.xml
index 0227d37c3..3ea138ac4 100644
--- a/yudao-example/yudao-sso-demo-by-code/pom.xml
+++ b/yudao-example/yudao-sso-demo-by-code/pom.xml
@@ -48,6 +48,12 @@
org.springframework.boot
spring-boot-starter-security
+
+
+ org.projectlombok
+ lombok
+ true
+
diff --git a/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/OAuth2Client.java b/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/OAuth2Client.java
new file mode 100644
index 000000000..351224bf0
--- /dev/null
+++ b/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/OAuth2Client.java
@@ -0,0 +1,75 @@
+package cn.iocoder.yudao.ssodemo.client;
+
+import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
+import cn.iocoder.yudao.ssodemo.client.dto.OAuth2AccessTokenRespDTO;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.*;
+import org.springframework.stereotype.Component;
+import org.springframework.util.Assert;
+import org.springframework.util.Base64Utils;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestTemplate;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * OAuth 2.0 客户端
+ */
+@Component
+public class OAuth2Client {
+
+ private static final String BASE_URL = "http://127.0.0.1:48080/admin-api/system/oauth2/";
+
+ /**
+ * 租户编号
+ *
+ * 默认使用 1;如果使用别的租户,可以调整
+ */
+ private static final Long TENANT_ID = 1L;
+
+ private static final String CLIENT_ID = "yudao-sso-demo-by-code";
+ private static final String CLIENT_SECRET = "test";
+
+
+// @Resource // 可优化,注册一个 RestTemplate Bean,然后注入
+ private final RestTemplate restTemplate = new RestTemplate();
+
+ /**
+ * 使用 code 授权码,获得访问令牌
+ *
+ * @param code 授权码
+ * @param redirectUri 重定向 URI
+ * @return 访问令牌
+ */
+ public CommonResult postAccessToken(String code, String redirectUri) {
+ // 1.1 构建请求头
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+ headers.set("tenant-id", TENANT_ID.toString());
+ addClientHeader(headers);
+ // 1.2 构建请求参数
+ MultiValueMap body = new LinkedMultiValueMap<>();
+ body.add("grant_type", "authorization_code");
+ body.add("code", code);
+ body.add("redirect_uri", redirectUri);
+// body.add("state", ""); // 选填;填了会校验
+
+ // 2. 执行请求
+ ResponseEntity> exchange = restTemplate.exchange(
+ BASE_URL + "/token",
+ HttpMethod.POST,
+ new HttpEntity<>(body, headers),
+ new ParameterizedTypeReference>() {}); // 解决 CommonResult 的泛型丢失
+ Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
+ return exchange.getBody();
+ }
+
+ private static void addClientHeader(HttpHeaders headers) {
+ // client 拼接,需要 BASE64 编码
+ String client = CLIENT_ID + ":" + CLIENT_SECRET;
+ client = Base64Utils.encodeToString(client.getBytes(StandardCharsets.UTF_8));
+ headers.add("Authorization", "Basic " + client);
+ }
+
+}
diff --git a/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/CommonResult.java b/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/CommonResult.java
new file mode 100644
index 000000000..548fe51e4
--- /dev/null
+++ b/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/CommonResult.java
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.ssodemo.client.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 通用返回
+ *
+ * @param 数据泛型
+ */
+@Data
+public class CommonResult implements Serializable {
+
+ /**
+ * 错误码
+ */
+ private Integer code;
+ /**
+ * 返回数据
+ */
+ private T data;
+ /**
+ * 错误提示,用户可阅读
+ */
+ private String msg;
+
+}
diff --git a/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/OAuth2AccessTokenRespDTO.java b/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/OAuth2AccessTokenRespDTO.java
new file mode 100644
index 000000000..14c01b843
--- /dev/null
+++ b/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/OAuth2AccessTokenRespDTO.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.ssodemo.client.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 访问令牌 Response DTO
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class OAuth2AccessTokenRespDTO {
+
+ /**
+ * 访问令牌
+ */
+ @JsonProperty("access_token")
+ private String accessToken;
+
+ /**
+ * 刷新令牌
+ */
+ @JsonProperty("refresh_token")
+ private String refreshToken;
+
+ /**
+ * 令牌类型
+ */
+ @JsonProperty("token_type")
+ private String tokenType;
+
+ /**
+ * 过期时间;单位:秒
+ */
+ @JsonProperty("expires_in")
+ private Long expiresIn;
+
+ /**
+ * 授权范围;如果多个授权范围,使用空格分隔
+ */
+ private String scope;
+
+}
diff --git a/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/controller/AuthController.java b/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/controller/AuthController.java
index 8445ff288..8ed601ba5 100644
--- a/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/controller/AuthController.java
+++ b/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/controller/AuthController.java
@@ -1,14 +1,33 @@
package cn.iocoder.yudao.ssodemo.controller;
-import org.springframework.stereotype.Controller;
+import cn.iocoder.yudao.ssodemo.client.OAuth2Client;
+import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
+import cn.iocoder.yudao.ssodemo.client.dto.OAuth2AccessTokenRespDTO;
import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
-@Controller("/auth")
+import javax.annotation.Resource;
+
+@RestController
+@RequestMapping("/auth")
public class AuthController {
- @PostMapping("/login-by-code")
- public void loginByCode() {
+ @Resource
+ private OAuth2Client oauth2Client;
+ /**
+ * 使用 code 访问令牌,获得访问令牌
+ *
+ * @param code 授权码
+ * @param redirectUri 重定向 URI
+ * @return 访问令牌;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段
+ */
+ @PostMapping("/login-by-code")
+ public CommonResult loginByCode(@RequestParam("code") String code,
+ @RequestParam("redirectUri") String redirectUri) {
+ return oauth2Client.postAccessToken(code, redirectUri);
}
}
diff --git a/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/SecurityConfiguration.java b/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/SecurityConfiguration.java
index 9e925913a..608191350 100644
--- a/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/SecurityConfiguration.java
+++ b/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/SecurityConfiguration.java
@@ -10,9 +10,12 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
- httpSecurity.authorizeRequests()
+ httpSecurity.csrf().disable() // 禁用 CSRF 保护
+ .authorizeRequests()
// 1. 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
+ // 2. 登录相关的接口,可匿名访问
+ .antMatchers("/auth/login-by-code").permitAll()
// last. 兜底规则,必须认证
.and().authorizeRequests()
.anyRequest().authenticated();
diff --git a/yudao-example/yudao-sso-demo-by-code/src/main/resources/static/callback.html b/yudao-example/yudao-sso-demo-by-code/src/main/resources/static/callback.html
new file mode 100644
index 000000000..60199e29e
--- /dev/null
+++ b/yudao-example/yudao-sso-demo-by-code/src/main/resources/static/callback.html
@@ -0,0 +1,61 @@
+
+
+
+
+ SSO 授权后的回调页
+
+
+
+
+
+
+
+
+正在使用 code 授权码,进行 accessToken 访问令牌的获取
+
+
diff --git a/yudao-example/yudao-sso-demo-by-code/src/main/resources/static/login.html b/yudao-example/yudao-sso-demo-by-code/src/main/resources/static/index.html
similarity index 80%
rename from yudao-example/yudao-sso-demo-by-code/src/main/resources/static/login.html
rename to yudao-example/yudao-sso-demo-by-code/src/main/resources/static/index.html
index b468d84f1..2163a466c 100644
--- a/yudao-example/yudao-sso-demo-by-code/src/main/resources/static/login.html
+++ b/yudao-example/yudao-sso-demo-by-code/src/main/resources/static/index.html
@@ -5,8 +5,6 @@
首页
-
-