Commit 0df44b51e43c5d1501d8e64b3a28a77d26243a1d

Authored by YunaiV
1 parent eef23364

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

yudao-example/yudao-sso-demo-by-code/pom.xml
@@ -48,6 +48,12 @@ @@ -48,6 +48,12 @@
48 <groupId>org.springframework.boot</groupId> 48 <groupId>org.springframework.boot</groupId>
49 <artifactId>spring-boot-starter-security</artifactId> 49 <artifactId>spring-boot-starter-security</artifactId>
50 </dependency> 50 </dependency>
  51 +
  52 + <dependency>
  53 + <groupId>org.projectlombok</groupId>
  54 + <artifactId>lombok</artifactId>
  55 + <optional>true</optional>
  56 + </dependency>
51 </dependencies> 57 </dependencies>
52 58
53 </project> 59 </project>
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/OAuth2Client.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.client;
  2 +
  3 +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
  4 +import cn.iocoder.yudao.ssodemo.client.dto.OAuth2AccessTokenRespDTO;
  5 +import org.springframework.core.ParameterizedTypeReference;
  6 +import org.springframework.http.*;
  7 +import org.springframework.stereotype.Component;
  8 +import org.springframework.util.Assert;
  9 +import org.springframework.util.Base64Utils;
  10 +import org.springframework.util.LinkedMultiValueMap;
  11 +import org.springframework.util.MultiValueMap;
  12 +import org.springframework.web.client.RestTemplate;
  13 +
  14 +import java.nio.charset.StandardCharsets;
  15 +
  16 +/**
  17 + * OAuth 2.0 客户端
  18 + */
  19 +@Component
  20 +public class OAuth2Client {
  21 +
  22 + private static final String BASE_URL = "http://127.0.0.1:48080/admin-api/system/oauth2/";
  23 +
  24 + /**
  25 + * 租户编号
  26 + *
  27 + * 默认使用 1;如果使用别的租户,可以调整
  28 + */
  29 + private static final Long TENANT_ID = 1L;
  30 +
  31 + private static final String CLIENT_ID = "yudao-sso-demo-by-code";
  32 + private static final String CLIENT_SECRET = "test";
  33 +
  34 +
  35 +// @Resource // 可优化,注册一个 RestTemplate Bean,然后注入
  36 + private final RestTemplate restTemplate = new RestTemplate();
  37 +
  38 + /**
  39 + * 使用 code 授权码,获得访问令牌
  40 + *
  41 + * @param code 授权码
  42 + * @param redirectUri 重定向 URI
  43 + * @return 访问令牌
  44 + */
  45 + public CommonResult<OAuth2AccessTokenRespDTO> postAccessToken(String code, String redirectUri) {
  46 + // 1.1 构建请求头
  47 + HttpHeaders headers = new HttpHeaders();
  48 + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
  49 + headers.set("tenant-id", TENANT_ID.toString());
  50 + addClientHeader(headers);
  51 + // 1.2 构建请求参数
  52 + MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
  53 + body.add("grant_type", "authorization_code");
  54 + body.add("code", code);
  55 + body.add("redirect_uri", redirectUri);
  56 +// body.add("state", ""); // 选填;填了会校验
  57 +
  58 + // 2. 执行请求
  59 + ResponseEntity<CommonResult<OAuth2AccessTokenRespDTO>> exchange = restTemplate.exchange(
  60 + BASE_URL + "/token",
  61 + HttpMethod.POST,
  62 + new HttpEntity<>(body, headers),
  63 + new ParameterizedTypeReference<CommonResult<OAuth2AccessTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失
  64 + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
  65 + return exchange.getBody();
  66 + }
  67 +
  68 + private static void addClientHeader(HttpHeaders headers) {
  69 + // client 拼接,需要 BASE64 编码
  70 + String client = CLIENT_ID + ":" + CLIENT_SECRET;
  71 + client = Base64Utils.encodeToString(client.getBytes(StandardCharsets.UTF_8));
  72 + headers.add("Authorization", "Basic " + client);
  73 + }
  74 +
  75 +}
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/CommonResult.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.client.dto;
  2 +
  3 +import lombok.Data;
  4 +
  5 +import java.io.Serializable;
  6 +
  7 +/**
  8 + * 通用返回
  9 + *
  10 + * @param <T> 数据泛型
  11 + */
  12 +@Data
  13 +public class CommonResult<T> implements Serializable {
  14 +
  15 + /**
  16 + * 错误码
  17 + */
  18 + private Integer code;
  19 + /**
  20 + * 返回数据
  21 + */
  22 + private T data;
  23 + /**
  24 + * 错误提示,用户可阅读
  25 + */
  26 + private String msg;
  27 +
  28 +}
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/OAuth2AccessTokenRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.client.dto;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonProperty;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Data;
  6 +import lombok.NoArgsConstructor;
  7 +
  8 +/**
  9 + * 访问令牌 Response DTO
  10 + */
  11 +@Data
  12 +@NoArgsConstructor
  13 +@AllArgsConstructor
  14 +public class OAuth2AccessTokenRespDTO {
  15 +
  16 + /**
  17 + * 访问令牌
  18 + */
  19 + @JsonProperty("access_token")
  20 + private String accessToken;
  21 +
  22 + /**
  23 + * 刷新令牌
  24 + */
  25 + @JsonProperty("refresh_token")
  26 + private String refreshToken;
  27 +
  28 + /**
  29 + * 令牌类型
  30 + */
  31 + @JsonProperty("token_type")
  32 + private String tokenType;
  33 +
  34 + /**
  35 + * 过期时间;单位:秒
  36 + */
  37 + @JsonProperty("expires_in")
  38 + private Long expiresIn;
  39 +
  40 + /**
  41 + * 授权范围;如果多个授权范围,使用空格分隔
  42 + */
  43 + private String scope;
  44 +
  45 +}
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/controller/AuthController.java
1 package cn.iocoder.yudao.ssodemo.controller; 1 package cn.iocoder.yudao.ssodemo.controller;
2 2
3 -import org.springframework.stereotype.Controller; 3 +import cn.iocoder.yudao.ssodemo.client.OAuth2Client;
  4 +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
  5 +import cn.iocoder.yudao.ssodemo.client.dto.OAuth2AccessTokenRespDTO;
4 import org.springframework.web.bind.annotation.PostMapping; 6 import org.springframework.web.bind.annotation.PostMapping;
  7 +import org.springframework.web.bind.annotation.RequestMapping;
  8 +import org.springframework.web.bind.annotation.RequestParam;
  9 +import org.springframework.web.bind.annotation.RestController;
5 10
6 -@Controller("/auth") 11 +import javax.annotation.Resource;
  12 +
  13 +@RestController
  14 +@RequestMapping("/auth")
7 public class AuthController { 15 public class AuthController {
8 16
9 - @PostMapping("/login-by-code")  
10 - public void loginByCode() { 17 + @Resource
  18 + private OAuth2Client oauth2Client;
11 19
  20 + /**
  21 + * 使用 code 访问令牌,获得访问令牌
  22 + *
  23 + * @param code 授权码
  24 + * @param redirectUri 重定向 URI
  25 + * @return 访问令牌;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段
  26 + */
  27 + @PostMapping("/login-by-code")
  28 + public CommonResult<OAuth2AccessTokenRespDTO> loginByCode(@RequestParam("code") String code,
  29 + @RequestParam("redirectUri") String redirectUri) {
  30 + return oauth2Client.postAccessToken(code, redirectUri);
12 } 31 }
13 32
14 } 33 }
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 { @@ -10,9 +10,12 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
10 10
11 @Override 11 @Override
12 protected void configure(HttpSecurity httpSecurity) throws Exception { 12 protected void configure(HttpSecurity httpSecurity) throws Exception {
13 - httpSecurity.authorizeRequests() 13 + httpSecurity.csrf().disable() // 禁用 CSRF 保护
  14 + .authorizeRequests()
14 // 1. 静态资源,可匿名访问 15 // 1. 静态资源,可匿名访问
15 .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll() 16 .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
  17 + // 2. 登录相关的接口,可匿名访问
  18 + .antMatchers("/auth/login-by-code").permitAll()
16 // last. 兜底规则,必须认证 19 // last. 兜底规则,必须认证
17 .and().authorizeRequests() 20 .and().authorizeRequests()
18 .anyRequest().authenticated(); 21 .anyRequest().authenticated();
yudao-example/yudao-sso-demo-by-code/src/main/resources/static/callback.html 0 → 100644
  1 +<!DOCTYPE html>
  2 +<html lang="en">
  3 +<head>
  4 + <meta charset="UTF-8">
  5 + <title>SSO 授权后的回调页</title>
  6 + <!-- jQuery:操作 dom、发起请求等 -->
  7 + <script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/2.1.2/jquery.min.js" type="application/javascript"></script>
  8 + <!-- 工具类 -->
  9 + <script type="application/javascript">
  10 + (function ($) {
  11 + /**
  12 + * 获得 URL 的指定参数的值
  13 + *
  14 + * @param name 参数名
  15 + * @returns 参数值
  16 + */
  17 + $.getUrlParam = function (name) {
  18 + const reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
  19 + const r = window.location.search.substr(1).match(reg);
  20 + if (r != null) return unescape(r[2]); return null;
  21 + }
  22 + })(jQuery);
  23 + </script>
  24 +
  25 + <script type="application/javascript">
  26 + $(function () {
  27 + // 获得 code 授权码
  28 + const code = $.getUrlParam('code');
  29 + if (!code) {
  30 + alert('获取不到 code 参数,请排查!')
  31 + return;
  32 + }
  33 +
  34 + // 提交
  35 + const redirectUri = 'http://127.0.0.1:18080/callback.html'; // 需要修改成,你回调的地址,就是在 index.html 拼接的 redirectUri
  36 + $.ajax({
  37 + url: "http://127.0.0.1:18080/auth/login-by-code?code=" + code
  38 + + '&redirectUri=' + redirectUri,
  39 + method: 'POST',
  40 + success: function( result ) {
  41 + if (result.code !== 0) {
  42 + alert('获得访问令牌失败,原因:' + result.msg)
  43 + return;
  44 + }
  45 + alert('获得访问令牌成功!点击确认,跳转回首页')
  46 +
  47 + // 设置到 localStorage 中
  48 + localStorage.setItem('ACCESS-TOKEN', result.data.access_token);
  49 + localStorage.setItem('REFRESH-TOKEN', result.data.refresh_token);
  50 +
  51 + // 跳转回首页
  52 + window.location.href = '/index.html';
  53 + }
  54 + })
  55 + })
  56 + </script>
  57 +</head>
  58 +<body>
  59 +正在使用 code 授权码,进行 accessToken 访问令牌的获取
  60 +</body>
  61 +</html>
yudao-example/yudao-sso-demo-by-code/src/main/resources/static/login.html renamed to yudao-example/yudao-sso-demo-by-code/src/main/resources/static/index.html
@@ -5,8 +5,6 @@ @@ -5,8 +5,6 @@
5 <title>首页</title> 5 <title>首页</title>
6 <!-- jQuery:操作 dom、发起请求等 --> 6 <!-- jQuery:操作 dom、发起请求等 -->
7 <script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/2.1.2/jquery.min.js" type="application/javascript"></script> 7 <script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/2.1.2/jquery.min.js" type="application/javascript"></script>
8 - <!-- jQuery Cookie:操作 cookie 等 -->  
9 - <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>  
10 8
11 <script type="application/javascript"> 9 <script type="application/javascript">
12 10
@@ -15,7 +13,7 @@ @@ -15,7 +13,7 @@
15 */ 13 */
16 function ssoLogin() { 14 function ssoLogin() {
17 const clientId = 'yudao-sso-demo-by-code'; // 可以改写成,你的 clientId 15 const clientId = 'yudao-sso-demo-by-code'; // 可以改写成,你的 clientId
18 - const redirectUri = encodeURIComponent('http://127.0.0.1:18080/callback'); // 注意,需要使用 encodeURIComponent 编码地址 16 + const redirectUri = encodeURIComponent('http://127.0.0.1:18080/callback.html'); // 注意,需要使用 encodeURIComponent 编码地址
19 const responseType = 'code'; // 1)授权码模式,对应 code;2)简化模式,对应 token 17 const responseType = 'code'; // 1)授权码模式,对应 code;2)简化模式,对应 token
20 window.location.href = 'http://127.0.0.1:1024/sso?client_id=' + clientId 18 window.location.href = 'http://127.0.0.1:1024/sso?client_id=' + clientId
21 + '&redirect_uri=' + redirectUri 19 + '&redirect_uri=' + redirectUri