mirror of
https://gitee.com/huangge1199_admin/vue-pro.git
synced 2024-11-29 18:51:53 +08:00
完成 yudao-sso-demo-by-code 实现 token 过滤器
This commit is contained in:
parent
0df44b51e4
commit
b7b31f03d3
@ -1,7 +1,8 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.client;
|
package cn.iocoder.yudao.ssodemo.client;
|
||||||
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
|
import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.OAuth2AccessTokenRespDTO;
|
import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2AccessTokenRespDTO;
|
||||||
|
import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2CheckTokenRespDTO;
|
||||||
import org.springframework.core.ParameterizedTypeReference;
|
import org.springframework.core.ParameterizedTypeReference;
|
||||||
import org.springframework.http.*;
|
import org.springframework.http.*;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@ -65,6 +66,26 @@ public class OAuth2Client {
|
|||||||
return exchange.getBody();
|
return exchange.getBody();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CommonResult<OAuth2CheckTokenRespDTO> checkToken(String token) {
|
||||||
|
// 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("token", token);
|
||||||
|
|
||||||
|
// 2. 执行请求
|
||||||
|
ResponseEntity<CommonResult<OAuth2CheckTokenRespDTO>> exchange = restTemplate.exchange(
|
||||||
|
BASE_URL + "/check-token",
|
||||||
|
HttpMethod.POST,
|
||||||
|
new HttpEntity<>(body, headers),
|
||||||
|
new ParameterizedTypeReference<CommonResult<OAuth2CheckTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失
|
||||||
|
Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
|
||||||
|
return exchange.getBody();
|
||||||
|
}
|
||||||
|
|
||||||
private static void addClientHeader(HttpHeaders headers) {
|
private static void addClientHeader(HttpHeaders headers) {
|
||||||
// client 拼接,需要 BASE64 编码
|
// client 拼接,需要 BASE64 编码
|
||||||
String client = CLIENT_ID + ":" + CLIENT_SECRET;
|
String client = CLIENT_ID + ":" + CLIENT_SECRET;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.client.dto;
|
package cn.iocoder.yudao.ssodemo.client.dto.oauth2;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
@ -0,0 +1,59 @@
|
|||||||
|
package cn.iocoder.yudao.ssodemo.client.dto.oauth2;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验令牌 Response DTO
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class OAuth2CheckTokenRespDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户编号
|
||||||
|
*/
|
||||||
|
@JsonProperty("user_id")
|
||||||
|
private Long userId;
|
||||||
|
/**
|
||||||
|
* 用户类型
|
||||||
|
*/
|
||||||
|
@JsonProperty("user_type")
|
||||||
|
private Integer userType;
|
||||||
|
/**
|
||||||
|
* 租户编号
|
||||||
|
*/
|
||||||
|
@JsonProperty("tenant_id")
|
||||||
|
private Long tenantId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端编号
|
||||||
|
*/
|
||||||
|
@JsonProperty("client_id")
|
||||||
|
private String clientId;
|
||||||
|
/**
|
||||||
|
* 授权范围
|
||||||
|
*/
|
||||||
|
private List<String> scopes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 访问令牌
|
||||||
|
*/
|
||||||
|
@JsonProperty("access_token")
|
||||||
|
private String accessToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 过期时间
|
||||||
|
*
|
||||||
|
* 时间戳 / 1000,即单位:秒
|
||||||
|
*/
|
||||||
|
private Long exp;
|
||||||
|
|
||||||
|
}
|
@ -2,7 +2,7 @@ package cn.iocoder.yudao.ssodemo.controller;
|
|||||||
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.OAuth2Client;
|
import cn.iocoder.yudao.ssodemo.client.OAuth2Client;
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
|
import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.OAuth2AccessTokenRespDTO;
|
import cn.iocoder.yudao.ssodemo.client.dto.oauth2.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.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
package cn.iocoder.yudao.ssodemo.controller;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/user")
|
||||||
|
public class UserController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得当前登录用户的基本信息
|
||||||
|
*
|
||||||
|
* @return TODO
|
||||||
|
*/
|
||||||
|
@GetMapping("/get")
|
||||||
|
public String getUser() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,15 +1,31 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.framework;
|
package cn.iocoder.yudao.ssodemo.framework.config;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.ssodemo.framework.core.TokenAuthenticationFilter;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Token 认证过滤器 Bean
|
||||||
|
// */
|
||||||
|
// @Bean
|
||||||
|
// public TokenAuthenticationFilter authenticationTokenFilter(OAuth2Client oauth2Client) {
|
||||||
|
// return new TokenAuthenticationFilter(oauth2Client);
|
||||||
|
// }
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private TokenAuthenticationFilter tokenAuthenticationFilter;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void configure(HttpSecurity httpSecurity) throws Exception {
|
protected void configure(HttpSecurity httpSecurity) throws Exception {
|
||||||
|
// 设置 URL 安全权限
|
||||||
httpSecurity.csrf().disable() // 禁用 CSRF 保护
|
httpSecurity.csrf().disable() // 禁用 CSRF 保护
|
||||||
.authorizeRequests()
|
.authorizeRequests()
|
||||||
// 1. 静态资源,可匿名访问
|
// 1. 静态资源,可匿名访问
|
||||||
@ -19,5 +35,8 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
|||||||
// last. 兜底规则,必须认证
|
// last. 兜底规则,必须认证
|
||||||
.and().authorizeRequests()
|
.and().authorizeRequests()
|
||||||
.anyRequest().authenticated();
|
.anyRequest().authenticated();
|
||||||
|
|
||||||
|
// 添加 Token Filter
|
||||||
|
httpSecurity.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package cn.iocoder.yudao.ssodemo.framework.core;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录用户信息
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class LoginUser {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户编号
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
/**
|
||||||
|
* 用户类型
|
||||||
|
*/
|
||||||
|
private Integer userType;
|
||||||
|
/**
|
||||||
|
* 租户编号
|
||||||
|
*/
|
||||||
|
private Long tenantId;
|
||||||
|
/**
|
||||||
|
* 授权范围
|
||||||
|
*/
|
||||||
|
private List<String> scopes;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,107 @@
|
|||||||
|
package cn.iocoder.yudao.ssodemo.framework.core;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.ssodemo.client.OAuth2Client;
|
||||||
|
import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
|
||||||
|
import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2CheckTokenRespDTO;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token 过滤器,验证 token 的有效性
|
||||||
|
* 验证通过后,获得 {@link LoginUser} 信息,并加入到 Spring Security 上下文
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class TokenAuthenticationFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private OAuth2Client oauth2Client;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
|
||||||
|
FilterChain filterChain) throws ServletException, IOException {
|
||||||
|
// 1. 获得访问令牌
|
||||||
|
String token = obtainAuthorization(request);
|
||||||
|
if (StringUtils.hasText(token)) {
|
||||||
|
// 2. 基于 token 构建登录用户
|
||||||
|
LoginUser loginUser = buildLoginUserByToken(token);
|
||||||
|
// 3. 设置当前用户
|
||||||
|
if (loginUser != null) {
|
||||||
|
setLoginUser(loginUser, request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 继续过滤链
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LoginUser buildLoginUserByToken(String token) {
|
||||||
|
try {
|
||||||
|
CommonResult<OAuth2CheckTokenRespDTO> accessTokenResult = oauth2Client.checkToken(token);
|
||||||
|
OAuth2CheckTokenRespDTO accessToken = accessTokenResult.getData();
|
||||||
|
if (accessToken == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 构建登录用户
|
||||||
|
return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())
|
||||||
|
.setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes());
|
||||||
|
} catch (Exception exception) {
|
||||||
|
// 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从请求 Header 中,获得访问令牌
|
||||||
|
*
|
||||||
|
* @param request 请求
|
||||||
|
* @return 访问令牌
|
||||||
|
*/
|
||||||
|
private static String obtainAuthorization(HttpServletRequest request) {
|
||||||
|
String authorization = request.getHeader("Authentication");
|
||||||
|
if (!StringUtils.hasText(authorization)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int index = authorization.indexOf("Bearer ");
|
||||||
|
if (index == -1) { // 未找到
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return authorization.substring(index + 7).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置当前用户
|
||||||
|
*
|
||||||
|
* @param loginUser 登录用户
|
||||||
|
* @param request 请求
|
||||||
|
*/
|
||||||
|
private static void setLoginUser(LoginUser loginUser, HttpServletRequest request) {
|
||||||
|
// 创建 Authentication,并设置到上下文
|
||||||
|
Authentication authentication = buildAuthentication(loginUser, request);
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) {
|
||||||
|
// 创建 UsernamePasswordAuthenticationToken 对象
|
||||||
|
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
|
||||||
|
loginUser, null, Collections.emptyList());
|
||||||
|
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||||
|
return authenticationToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -19,14 +19,57 @@
|
|||||||
+ '&redirect_uri=' + redirectUri
|
+ '&redirect_uri=' + redirectUri
|
||||||
+ '&response_type=' + responseType;
|
+ '&response_type=' + responseType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$(function () {
|
||||||
|
const accessToken = localStorage.getItem('ACCESS-TOKEN');
|
||||||
|
// 情况一:未登录
|
||||||
|
if (!accessToken) {
|
||||||
|
$('#noLoginDiv').css("display", "block");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 情况二:已登录
|
||||||
|
$('#yesLoginDiv').css("display", "block");
|
||||||
|
$('#accessTokenSpan').html(accessToken);
|
||||||
|
// 获得登录用户的信息
|
||||||
|
$.ajax({
|
||||||
|
url: "http://127.0.0.1:18080/user/get",
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Authentication': 'Bearer ' + accessToken
|
||||||
|
},
|
||||||
|
success: function (result) {
|
||||||
|
if (result.code !== 0) {
|
||||||
|
alert('获得个人信息失败,原因:' + result.msg)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$('nicknameSpan').html(result.data.nickname);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- 情况一:未登录:1)跳转 ruoyi-vue-pro 的 SSO 登录页 -->
|
<!-- 情况一:未登录:1)跳转 ruoyi-vue-pro 的 SSO 登录页 -->
|
||||||
<div>
|
<div id="noLoginDiv" style="display: none">
|
||||||
您未登录,点击 <a href="#" onclick="ssoLogin()">跳转 </a> SSO 单点登录
|
您未登录,点击 <a href="#" onclick="ssoLogin()">跳转 </a> SSO 单点登录
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 情况二:已登录:1)展示用户信息;2)刷新访问令牌;3)退出登录 -->
|
<!-- 情况二:已登录:1)展示用户信息;2)刷新访问令牌;3)退出登录 -->
|
||||||
|
<div id="yesLoginDiv" style="display: none">
|
||||||
|
您已登录!点击 <a href="#" onclick="ssoLogin()">退出 </a> 系统 <br />
|
||||||
|
昵称:<span id="nicknameSpan"> 加载中... </span> <br />
|
||||||
|
访问令牌:<span id="accessTokenSpan"> 加载中... </span> <br />
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
<style>
|
||||||
|
body { /** 页面居中 */
|
||||||
|
border-radius: 20px;
|
||||||
|
height: 350px;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%,-50%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</html>
|
</html>
|
||||||
|
Loading…
Reference in New Issue
Block a user