From 0df44b51e43c5d1501d8e64b3a28a77d26243a1d Mon Sep 17 00:00:00 2001 From: YunaiV Date: Thu, 29 Sep 2022 23:59:42 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=20yudao-sso-demo-by-code=20?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=20code=20=E6=8E=88=E6=9D=83=E7=A0=81?= =?UTF-8?q?=EF=BC=8C=E8=8E=B7=E5=BE=97=E8=AE=BF=E9=97=AE=E4=BB=A4=E7=89=8C?= =?UTF-8?q?=E7=9A=84=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-example/yudao-sso-demo-by-code/pom.xml | 6 ++ .../yudao/ssodemo/client/OAuth2Client.java | 75 +++++++++++++++++++ .../ssodemo/client/dto/CommonResult.java | 28 +++++++ .../client/dto/OAuth2AccessTokenRespDTO.java | 45 +++++++++++ .../ssodemo/controller/AuthController.java | 27 ++++++- .../framework/SecurityConfiguration.java | 5 +- .../src/main/resources/static/callback.html | 61 +++++++++++++++ .../static/{login.html => index.html} | 4 +- 8 files changed, 243 insertions(+), 8 deletions(-) create mode 100644 yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/OAuth2Client.java create mode 100644 yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/CommonResult.java create mode 100644 yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/OAuth2AccessTokenRespDTO.java create mode 100644 yudao-example/yudao-sso-demo-by-code/src/main/resources/static/callback.html rename yudao-example/yudao-sso-demo-by-code/src/main/resources/static/{login.html => index.html} (80%) 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 @@ 首页 - -