完成 yudao-sso-demo-by-code 使用 code 授权码,获得访问令牌的逻辑
parent
eef233644c
commit
0df44b51e4
|
@ -48,6 +48,12 @@
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-security</artifactId>
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -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;
|
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.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 {
|
public class AuthController {
|
||||||
|
|
||||||
@PostMapping("/login-by-code")
|
@Resource
|
||||||
public void loginByCode() {
|
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
|
@Override
|
||||||
protected void configure(HttpSecurity httpSecurity) throws Exception {
|
protected void configure(HttpSecurity httpSecurity) throws Exception {
|
||||||
httpSecurity.authorizeRequests()
|
httpSecurity.csrf().disable() // 禁用 CSRF 保护
|
||||||
|
.authorizeRequests()
|
||||||
// 1. 静态资源,可匿名访问
|
// 1. 静态资源,可匿名访问
|
||||||
.antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
|
.antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
|
||||||
|
// 2. 登录相关的接口,可匿名访问
|
||||||
|
.antMatchers("/auth/login-by-code").permitAll()
|
||||||
// last. 兜底规则,必须认证
|
// last. 兜底规则,必须认证
|
||||||
.and().authorizeRequests()
|
.and().authorizeRequests()
|
||||||
.anyRequest().authenticated();
|
.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>
|
<title>首页</title>
|
||||||
<!-- jQuery:操作 dom、发起请求等 -->
|
<!-- 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 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">
|
<script type="application/javascript">
|
||||||
|
|
||||||
|
@ -15,7 +13,7 @@
|
||||||
*/
|
*/
|
||||||
function ssoLogin() {
|
function ssoLogin() {
|
||||||
const clientId = 'yudao-sso-demo-by-code'; // 可以改写成,你的 clientId
|
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
|
const responseType = 'code'; // 1)授权码模式,对应 code;2)简化模式,对应 token
|
||||||
window.location.href = 'http://127.0.0.1:1024/sso?client_id=' + clientId
|
window.location.href = 'http://127.0.0.1:1024/sso?client_id=' + clientId
|
||||||
+ '&redirect_uri=' + redirectUri
|
+ '&redirect_uri=' + redirectUri
|
Loading…
Reference in New Issue