瀏覽代碼

完成 yudao-sso-demo-by-code 使用 code 授权码,获得访问令牌的逻辑

YunaiV 2 年之前
父節點
當前提交
0df44b51e4

+ 6 - 0
yudao-example/yudao-sso-demo-by-code/pom.xml

@@ -48,6 +48,12 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-security</artifactId>
         </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
     </dependencies>
 
 </project>

+ 75 - 0
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<OAuth2AccessTokenRespDTO> 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<String, Object> body = new LinkedMultiValueMap<>();
+        body.add("grant_type", "authorization_code");
+        body.add("code", code);
+        body.add("redirect_uri", redirectUri);
+//        body.add("state", ""); // 选填;填了会校验
+
+        // 2. 执行请求
+        ResponseEntity<CommonResult<OAuth2AccessTokenRespDTO>> exchange = restTemplate.exchange(
+                BASE_URL + "/token",
+                HttpMethod.POST,
+                new HttpEntity<>(body, headers),
+                new ParameterizedTypeReference<CommonResult<OAuth2AccessTokenRespDTO>>() {}); // 解决 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);
+    }
+
+}

+ 28 - 0
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 <T> 数据泛型
+ */
+@Data
+public class CommonResult<T> implements Serializable {
+
+    /**
+     * 错误码
+     */
+    private Integer code;
+    /**
+     * 返回数据
+     */
+    private T data;
+    /**
+     * 错误提示,用户可阅读
+     */
+    private String msg;
+
+}

+ 45 - 0
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;
+
+}

+ 23 - 4
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<OAuth2AccessTokenRespDTO> loginByCode(@RequestParam("code") String code,
+                                                              @RequestParam("redirectUri") String redirectUri) {
+        return oauth2Client.postAccessToken(code, redirectUri);
     }
 
 }

+ 4 - 1
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();

+ 61 - 0
yudao-example/yudao-sso-demo-by-code/src/main/resources/static/callback.html

@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+	<meta charset="UTF-8">
+	<title>SSO 授权后的回调页</title>
+	<!-- jQuery:操作 dom、发起请求等 -->
+	<script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/2.1.2/jquery.min.js" type="application/javascript"></script>
+	<!-- 工具类 -->
+	<script type="application/javascript">
+    (function ($) {
+      /**
+			 * 获得 URL 的指定参数的值
+			 *
+       * @param name 参数名
+       * @returns 参数值
+       */
+      $.getUrlParam = function (name) {
+        const reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
+        const r = window.location.search.substr(1).match(reg);
+        if (r != null) return unescape(r[2]); return null;
+      }
+    })(jQuery);
+	</script>
+
+	<script type="application/javascript">
+    $(function () {
+      // 获得 code 授权码
+			const code = $.getUrlParam('code');
+      if (!code) {
+        alert('获取不到 code 参数,请排查!')
+        return;
+			}
+
+      // 提交
+			const redirectUri = 'http://127.0.0.1:18080/callback.html'; // 需要修改成,你回调的地址,就是在 index.html 拼接的 redirectUri
+      $.ajax({
+        url:  "http://127.0.0.1:18080/auth/login-by-code?code=" + code
+					+ '&redirectUri=' + redirectUri,
+        method: 'POST',
+        success: function( result ) {
+          if (result.code !== 0) {
+            alert('获得访问令牌失败,原因:' + result.msg)
+            return;
+          }
+          alert('获得访问令牌成功!点击确认,跳转回首页')
+
+          // 设置到 localStorage 中
+          localStorage.setItem('ACCESS-TOKEN', result.data.access_token);
+          localStorage.setItem('REFRESH-TOKEN', result.data.refresh_token);
+
+          // 跳转回首页
+          window.location.href = '/index.html';
+        }
+      })
+		})
+	</script>
+</head>
+<body>
+正在使用 code 授权码,进行 accessToken 访问令牌的获取
+</body>
+</html>

+ 1 - 3
yudao-example/yudao-sso-demo-by-code/src/main/resources/static/login.html → yudao-example/yudao-sso-demo-by-code/src/main/resources/static/index.html

@@ -5,8 +5,6 @@
 	<title>首页</title>
 	<!-- jQuery:操作 dom、发起请求等 -->
 	<script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/2.1.2/jquery.min.js" type="application/javascript"></script>
-	<!-- jQuery Cookie:操作 cookie 等 -->
-	<script src="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery-cookie/1.4.1/jquery.cookie.min.js" type="application/javascript"/></script>
 
 	<script type="application/javascript">
 
@@ -15,7 +13,7 @@
      */
 		function ssoLogin() {
 			const clientId = 'yudao-sso-demo-by-code'; // 可以改写成,你的 clientId
-      const redirectUri = encodeURIComponent('http://127.0.0.1:18080/callback'); // 注意,需要使用 encodeURIComponent 编码地址
+      const redirectUri = encodeURIComponent('http://127.0.0.1:18080/callback.html'); // 注意,需要使用 encodeURIComponent 编码地址
       const responseType = 'code'; // 1)授权码模式,对应 code;2)简化模式,对应 token
       window.location.href = 'http://127.0.0.1:1024/sso?client_id=' + clientId
 				+ '&redirect_uri=' + redirectUri