mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-11-04 12:18:42 +08:00 
			
		
		
		
	完成 yudao-sso-demo-by-code 使用 code 授权码,获得访问令牌的逻辑
This commit is contained in:
		@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
@@ -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
 | 
			
		||||
		Reference in New Issue
	
	Block a user