完成 yudao-sso-demo-by-code 实现获得用户信息

This commit is contained in:
YunaiV 2022-10-01 10:52:27 +08:00
parent b7b31f03d3
commit fc7a6c782a
8 changed files with 274 additions and 55 deletions

View File

@ -20,14 +20,14 @@ import java.nio.charset.StandardCharsets;
@Component @Component
public class OAuth2Client { public class OAuth2Client {
private static final String BASE_URL = "http://127.0.0.1:48080/admin-api/system/oauth2/"; private static final String BASE_URL = "http://127.0.0.1:48080/admin-api/system/oauth2";
/** /**
* 租户编号 * 租户编号
* *
* 默认使用 1如果使用别的租户可以调整 * 默认使用 1如果使用别的租户可以调整
*/ */
private static final Long TENANT_ID = 1L; public static final Long TENANT_ID = 1L;
private static final String CLIENT_ID = "yudao-sso-demo-by-code"; private static final String CLIENT_ID = "yudao-sso-demo-by-code";
private static final String CLIENT_SECRET = "test"; private static final String CLIENT_SECRET = "test";

View File

@ -0,0 +1,50 @@
package cn.iocoder.yudao.ssodemo.client;
import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
import cn.iocoder.yudao.ssodemo.client.dto.user.UserInfoRespDTO;
import cn.iocoder.yudao.ssodemo.framework.core.LoginUser;
import cn.iocoder.yudao.ssodemo.framework.core.SecurityUtils;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
/**
* OAuth 2.0 客户端
*/
@Component
public class UserClient {
private static final String BASE_URL = "http://127.0.0.1:48080/admin-api//system/oauth2/user";
// @Resource // 可优化注册一个 RestTemplate Bean然后注入
private final RestTemplate restTemplate = new RestTemplate();
public CommonResult<UserInfoRespDTO> getUser() {
// 1.1 构建请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.set("tenant-id", OAuth2Client.TENANT_ID.toString());
addTokenHeader(headers);
// 1.2 构建请求参数
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
// 2. 执行请求
ResponseEntity<CommonResult<UserInfoRespDTO>> exchange = restTemplate.exchange(
BASE_URL + "/get",
HttpMethod.GET,
new HttpEntity<>(body, headers),
new ParameterizedTypeReference<CommonResult<UserInfoRespDTO>>() {}); // 解决 CommonResult 的泛型丢失
Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
return exchange.getBody();
}
private static void addTokenHeader(HttpHeaders headers) {
LoginUser loginUser = SecurityUtils.getLoginUser();
Assert.notNull(loginUser, "登录用户不能为空");
headers.add("Authorization", "Bearer " + loginUser.getAccessToken());
}
}

View File

@ -0,0 +1,97 @@
package cn.iocoder.yudao.ssodemo.client.dto.user;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 获得用户基本信息 Response dto
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserInfoRespDTO {
/**
* 用户编号
*/
private Long id;
/**
* 用户账号
*/
private String username;
/**
* 用户昵称
*/
private String nickname;
/**
* 用户邮箱
*/
private String email;
/**
* 手机号码
*/
private String mobile;
/**
* 用户性别
*/
private Integer sex;
/**
* 用户头像
*/
private String avatar;
/**
* 所在部门
*/
private Dept dept;
/**
* 所属岗位数组
*/
private List<Post> posts;
/**
* 部门
*/
@Data
public static class Dept {
/**
* 部门编号
*/
private Long id;
/**
* 部门名称
*/
private String name;
}
/**
* 岗位
*/
@Data
public static class Post {
/**
* 岗位编号
*/
private Long id;
/**
* 岗位名称
*/
private String name;
}
}

View File

@ -1,21 +1,29 @@
package cn.iocoder.yudao.ssodemo.controller; package cn.iocoder.yudao.ssodemo.controller;
import cn.iocoder.yudao.ssodemo.client.UserClient;
import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
import cn.iocoder.yudao.ssodemo.client.dto.user.UserInfoRespDTO;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController @RestController
@RequestMapping("/user") @RequestMapping("/user")
public class UserController { public class UserController {
@Resource
private UserClient userClient;
/** /**
* 获得当前登录用户的基本信息 * 获得当前登录用户的基本信息
* *
* @return TODO * @return 用户信息注意实际项目中最好创建对应的 ResponseVO 只返回必要的字段
*/ */
@GetMapping("/get") @GetMapping("/get")
public String getUser() { public CommonResult<UserInfoRespDTO> getUser() {
return ""; return userClient.getUser();
} }
} }

View File

@ -29,4 +29,9 @@ public class LoginUser {
*/ */
private List<String> scopes; private List<String> scopes;
/**
* 访问令牌
*/
private String accessToken;
} }

View File

@ -0,0 +1,102 @@
package cn.iocoder.yudao.ssodemo.framework.core;
import org.springframework.lang.Nullable;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.Collections;
/**
* 安全服务工具类
*
* @author 芋道源码
*/
public class SecurityUtils {
public static final String AUTHORIZATION_BEARER = "Bearer";
private SecurityUtils() {}
/**
* 从请求中获得认证 Token
*
* @param request 请求
* @param header 认证 Token 对应的 Header 名字
* @return 认证 Token
*/
public static String obtainAuthorization(HttpServletRequest request, String header) {
String authorization = request.getHeader(header);
if (!StringUtils.hasText(authorization)) {
return null;
}
int index = authorization.indexOf(AUTHORIZATION_BEARER + " ");
if (index == -1) { // 未找到
return null;
}
return authorization.substring(index + 7).trim();
}
/**
* 获得当前认证信息
*
* @return 认证信息
*/
public static Authentication getAuthentication() {
SecurityContext context = SecurityContextHolder.getContext();
if (context == null) {
return null;
}
return context.getAuthentication();
}
/**
* 获取当前用户
*
* @return 当前用户
*/
@Nullable
public static LoginUser getLoginUser() {
Authentication authentication = getAuthentication();
if (authentication == null) {
return null;
}
return authentication.getPrincipal() instanceof LoginUser ? (LoginUser) authentication.getPrincipal() : null;
}
/**
* 获得当前用户的编号从上下文中
*
* @return 用户编号
*/
@Nullable
public static Long getLoginUserId() {
LoginUser loginUser = getLoginUser();
return loginUser != null ? loginUser.getId() : null;
}
/**
* 设置当前用户
*
* @param loginUser 登录用户
* @param request 请求
*/
public 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;
}
}

View File

@ -3,10 +3,6 @@ package cn.iocoder.yudao.ssodemo.framework.core;
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.oauth2.OAuth2CheckTokenRespDTO; 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.stereotype.Component;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
@ -17,7 +13,6 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
/** /**
* Token 过滤器验证 token 的有效性 * Token 过滤器验证 token 的有效性
@ -35,13 +30,13 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException { FilterChain filterChain) throws ServletException, IOException {
// 1. 获得访问令牌 // 1. 获得访问令牌
String token = obtainAuthorization(request); String token = SecurityUtils.obtainAuthorization(request, "Authentication");
if (StringUtils.hasText(token)) { if (StringUtils.hasText(token)) {
// 2. 基于 token 构建登录用户 // 2. 基于 token 构建登录用户
LoginUser loginUser = buildLoginUserByToken(token); LoginUser loginUser = buildLoginUserByToken(token);
// 3. 设置当前用户 // 3. 设置当前用户
if (loginUser != null) { if (loginUser != null) {
setLoginUser(loginUser, request); SecurityUtils.setLoginUser(loginUser, request);
} }
} }
@ -58,50 +53,12 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
} }
// 构建登录用户 // 构建登录用户
return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType()) return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())
.setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes()); .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes())
.setAccessToken(accessToken.getAccessToken());
} catch (Exception exception) { } catch (Exception exception) {
// 校验 Token 不通过时考虑到一些接口是无需登录的所以直接返回 null 即可 // 校验 Token 不通过时考虑到一些接口是无需登录的所以直接返回 null 即可
return 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;
}
} }

View File

@ -43,7 +43,7 @@
alert('获得个人信息失败,原因:' + result.msg) alert('获得个人信息失败,原因:' + result.msg)
return; return;
} }
$('nicknameSpan').html(result.data.nickname); $('#nicknameSpan').html(result.data.nickname);
} }
}); });
}) })
@ -57,8 +57,8 @@
<!-- 情况二已登录1展示用户信息2刷新访问令牌3退出登录 --> <!-- 情况二已登录1展示用户信息2刷新访问令牌3退出登录 -->
<div id="yesLoginDiv" style="display: none"> <div id="yesLoginDiv" style="display: none">
您已登录!点击 <a href="#" onclick="ssoLogin()">退出 </a> 系统 <br /> 您已登录!<button>退出登录</button> <br />
昵称:<span id="nicknameSpan"> 加载中... </span> <br /> 昵称:<span id="nicknameSpan"> 加载中... </span> <button>修改昵称</button> <br />
访问令牌:<span id="accessTokenSpan"> 加载中... </span> <br /> 访问令牌:<span id="accessTokenSpan"> 加载中... </span> <br />
</div> </div>
</body> </body>