Commit fc7a6c782a18074d41d80377e4ac993d65c35fe6

Authored by YunaiV
1 parent b7b31f03

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

yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/OAuth2Client.java
@@ -20,14 +20,14 @@ import java.nio.charset.StandardCharsets; @@ -20,14 +20,14 @@ import java.nio.charset.StandardCharsets;
20 @Component 20 @Component
21 public class OAuth2Client { 21 public class OAuth2Client {
22 22
23 - private static final String BASE_URL = "http://127.0.0.1:48080/admin-api/system/oauth2/"; 23 + private static final String BASE_URL = "http://127.0.0.1:48080/admin-api/system/oauth2";
24 24
25 /** 25 /**
26 * 租户编号 26 * 租户编号
27 * 27 *
28 * 默认使用 1;如果使用别的租户,可以调整 28 * 默认使用 1;如果使用别的租户,可以调整
29 */ 29 */
30 - private static final Long TENANT_ID = 1L; 30 + public static final Long TENANT_ID = 1L;
31 31
32 private static final String CLIENT_ID = "yudao-sso-demo-by-code"; 32 private static final String CLIENT_ID = "yudao-sso-demo-by-code";
33 private static final String CLIENT_SECRET = "test"; 33 private static final String CLIENT_SECRET = "test";
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/UserClient.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.user.UserInfoRespDTO;
  5 +import cn.iocoder.yudao.ssodemo.framework.core.LoginUser;
  6 +import cn.iocoder.yudao.ssodemo.framework.core.SecurityUtils;
  7 +import org.springframework.core.ParameterizedTypeReference;
  8 +import org.springframework.http.*;
  9 +import org.springframework.stereotype.Component;
  10 +import org.springframework.util.Assert;
  11 +import org.springframework.util.LinkedMultiValueMap;
  12 +import org.springframework.util.MultiValueMap;
  13 +import org.springframework.web.client.RestTemplate;
  14 +
  15 +/**
  16 + * OAuth 2.0 客户端
  17 + */
  18 +@Component
  19 +public class UserClient {
  20 +
  21 + private static final String BASE_URL = "http://127.0.0.1:48080/admin-api//system/oauth2/user";
  22 +
  23 + // @Resource // 可优化,注册一个 RestTemplate Bean,然后注入
  24 + private final RestTemplate restTemplate = new RestTemplate();
  25 +
  26 + public CommonResult<UserInfoRespDTO> getUser() {
  27 + // 1.1 构建请求头
  28 + HttpHeaders headers = new HttpHeaders();
  29 + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
  30 + headers.set("tenant-id", OAuth2Client.TENANT_ID.toString());
  31 + addTokenHeader(headers);
  32 + // 1.2 构建请求参数
  33 + MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
  34 +
  35 + // 2. 执行请求
  36 + ResponseEntity<CommonResult<UserInfoRespDTO>> exchange = restTemplate.exchange(
  37 + BASE_URL + "/get",
  38 + HttpMethod.GET,
  39 + new HttpEntity<>(body, headers),
  40 + new ParameterizedTypeReference<CommonResult<UserInfoRespDTO>>() {}); // 解决 CommonResult 的泛型丢失
  41 + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
  42 + return exchange.getBody();
  43 + }
  44 +
  45 + private static void addTokenHeader(HttpHeaders headers) {
  46 + LoginUser loginUser = SecurityUtils.getLoginUser();
  47 + Assert.notNull(loginUser, "登录用户不能为空");
  48 + headers.add("Authorization", "Bearer " + loginUser.getAccessToken());
  49 + }
  50 +}
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/user/UserInfoRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.client.dto.user;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Data;
  5 +import lombok.NoArgsConstructor;
  6 +
  7 +import java.util.List;
  8 +
  9 +/**
  10 + * 获得用户基本信息 Response dto
  11 + */
  12 +@Data
  13 +@NoArgsConstructor
  14 +@AllArgsConstructor
  15 +public class UserInfoRespDTO {
  16 +
  17 + /**
  18 + * 用户编号
  19 + */
  20 + private Long id;
  21 +
  22 + /**
  23 + * 用户账号
  24 + */
  25 + private String username;
  26 +
  27 + /**
  28 + * 用户昵称
  29 + */
  30 + private String nickname;
  31 +
  32 + /**
  33 + * 用户邮箱
  34 + */
  35 + private String email;
  36 + /**
  37 + * 手机号码
  38 + */
  39 + private String mobile;
  40 +
  41 + /**
  42 + * 用户性别
  43 + */
  44 + private Integer sex;
  45 +
  46 + /**
  47 + * 用户头像
  48 + */
  49 + private String avatar;
  50 +
  51 + /**
  52 + * 所在部门
  53 + */
  54 + private Dept dept;
  55 +
  56 + /**
  57 + * 所属岗位数组
  58 + */
  59 + private List<Post> posts;
  60 +
  61 + /**
  62 + * 部门
  63 + */
  64 + @Data
  65 + public static class Dept {
  66 +
  67 + /**
  68 + * 部门编号
  69 + */
  70 + private Long id;
  71 +
  72 + /**
  73 + * 部门名称
  74 + */
  75 + private String name;
  76 +
  77 + }
  78 +
  79 + /**
  80 + * 岗位
  81 + */
  82 + @Data
  83 + public static class Post {
  84 +
  85 + /**
  86 + * 岗位编号
  87 + */
  88 + private Long id;
  89 +
  90 + /**
  91 + * 岗位名称
  92 + */
  93 + private String name;
  94 +
  95 + }
  96 +
  97 +}
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/controller/UserController.java
1 package cn.iocoder.yudao.ssodemo.controller; 1 package cn.iocoder.yudao.ssodemo.controller;
2 2
  3 +import cn.iocoder.yudao.ssodemo.client.UserClient;
  4 +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
  5 +import cn.iocoder.yudao.ssodemo.client.dto.user.UserInfoRespDTO;
3 import org.springframework.web.bind.annotation.GetMapping; 6 import org.springframework.web.bind.annotation.GetMapping;
4 import org.springframework.web.bind.annotation.RequestMapping; 7 import org.springframework.web.bind.annotation.RequestMapping;
5 import org.springframework.web.bind.annotation.RestController; 8 import org.springframework.web.bind.annotation.RestController;
6 9
  10 +import javax.annotation.Resource;
  11 +
7 @RestController 12 @RestController
8 @RequestMapping("/user") 13 @RequestMapping("/user")
9 public class UserController { 14 public class UserController {
10 15
  16 + @Resource
  17 + private UserClient userClient;
  18 +
11 /** 19 /**
12 * 获得当前登录用户的基本信息 20 * 获得当前登录用户的基本信息
13 * 21 *
14 - * @return TODO 22 + * @return 用户信息;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段
15 */ 23 */
16 @GetMapping("/get") 24 @GetMapping("/get")
17 - public String getUser() {  
18 - return ""; 25 + public CommonResult<UserInfoRespDTO> getUser() {
  26 + return userClient.getUser();
19 } 27 }
20 28
21 } 29 }
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/LoginUser.java
@@ -29,4 +29,9 @@ public class LoginUser { @@ -29,4 +29,9 @@ public class LoginUser {
29 */ 29 */
30 private List<String> scopes; 30 private List<String> scopes;
31 31
  32 + /**
  33 + * 访问令牌
  34 + */
  35 + private String accessToken;
  36 +
32 } 37 }
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/SecurityUtils.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.framework.core;
  2 +
  3 +import org.springframework.lang.Nullable;
  4 +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  5 +import org.springframework.security.core.Authentication;
  6 +import org.springframework.security.core.context.SecurityContext;
  7 +import org.springframework.security.core.context.SecurityContextHolder;
  8 +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
  9 +import org.springframework.util.StringUtils;
  10 +
  11 +import javax.servlet.http.HttpServletRequest;
  12 +import java.util.Collections;
  13 +
  14 +/**
  15 + * 安全服务工具类
  16 + *
  17 + * @author 芋道源码
  18 + */
  19 +public class SecurityUtils {
  20 +
  21 + public static final String AUTHORIZATION_BEARER = "Bearer";
  22 +
  23 + private SecurityUtils() {}
  24 +
  25 + /**
  26 + * 从请求中,获得认证 Token
  27 + *
  28 + * @param request 请求
  29 + * @param header 认证 Token 对应的 Header 名字
  30 + * @return 认证 Token
  31 + */
  32 + public static String obtainAuthorization(HttpServletRequest request, String header) {
  33 + String authorization = request.getHeader(header);
  34 + if (!StringUtils.hasText(authorization)) {
  35 + return null;
  36 + }
  37 + int index = authorization.indexOf(AUTHORIZATION_BEARER + " ");
  38 + if (index == -1) { // 未找到
  39 + return null;
  40 + }
  41 + return authorization.substring(index + 7).trim();
  42 + }
  43 +
  44 + /**
  45 + * 获得当前认证信息
  46 + *
  47 + * @return 认证信息
  48 + */
  49 + public static Authentication getAuthentication() {
  50 + SecurityContext context = SecurityContextHolder.getContext();
  51 + if (context == null) {
  52 + return null;
  53 + }
  54 + return context.getAuthentication();
  55 + }
  56 +
  57 + /**
  58 + * 获取当前用户
  59 + *
  60 + * @return 当前用户
  61 + */
  62 + @Nullable
  63 + public static LoginUser getLoginUser() {
  64 + Authentication authentication = getAuthentication();
  65 + if (authentication == null) {
  66 + return null;
  67 + }
  68 + return authentication.getPrincipal() instanceof LoginUser ? (LoginUser) authentication.getPrincipal() : null;
  69 + }
  70 +
  71 + /**
  72 + * 获得当前用户的编号,从上下文中
  73 + *
  74 + * @return 用户编号
  75 + */
  76 + @Nullable
  77 + public static Long getLoginUserId() {
  78 + LoginUser loginUser = getLoginUser();
  79 + return loginUser != null ? loginUser.getId() : null;
  80 + }
  81 +
  82 + /**
  83 + * 设置当前用户
  84 + *
  85 + * @param loginUser 登录用户
  86 + * @param request 请求
  87 + */
  88 + public static void setLoginUser(LoginUser loginUser, HttpServletRequest request) {
  89 + // 创建 Authentication,并设置到上下文
  90 + Authentication authentication = buildAuthentication(loginUser, request);
  91 + SecurityContextHolder.getContext().setAuthentication(authentication);
  92 + }
  93 +
  94 + private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) {
  95 + // 创建 UsernamePasswordAuthenticationToken 对象
  96 + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
  97 + loginUser, null, Collections.emptyList());
  98 + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
  99 + return authenticationToken;
  100 + }
  101 +
  102 +}
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/TokenAuthenticationFilter.java
@@ -3,10 +3,6 @@ package cn.iocoder.yudao.ssodemo.framework.core; @@ -3,10 +3,6 @@ package cn.iocoder.yudao.ssodemo.framework.core;
3 import cn.iocoder.yudao.ssodemo.client.OAuth2Client; 3 import cn.iocoder.yudao.ssodemo.client.OAuth2Client;
4 import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; 4 import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
5 import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2CheckTokenRespDTO; 5 import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2CheckTokenRespDTO;
6 -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;  
7 -import org.springframework.security.core.Authentication;  
8 -import org.springframework.security.core.context.SecurityContextHolder;  
9 -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;  
10 import org.springframework.stereotype.Component; 6 import org.springframework.stereotype.Component;
11 import org.springframework.util.StringUtils; 7 import org.springframework.util.StringUtils;
12 import org.springframework.web.filter.OncePerRequestFilter; 8 import org.springframework.web.filter.OncePerRequestFilter;
@@ -17,7 +13,6 @@ import javax.servlet.ServletException; @@ -17,7 +13,6 @@ import javax.servlet.ServletException;
17 import javax.servlet.http.HttpServletRequest; 13 import javax.servlet.http.HttpServletRequest;
18 import javax.servlet.http.HttpServletResponse; 14 import javax.servlet.http.HttpServletResponse;
19 import java.io.IOException; 15 import java.io.IOException;
20 -import java.util.Collections;  
21 16
22 /** 17 /**
23 * Token 过滤器,验证 token 的有效性 18 * Token 过滤器,验证 token 的有效性
@@ -35,13 +30,13 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { @@ -35,13 +30,13 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
35 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, 30 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
36 FilterChain filterChain) throws ServletException, IOException { 31 FilterChain filterChain) throws ServletException, IOException {
37 // 1. 获得访问令牌 32 // 1. 获得访问令牌
38 - String token = obtainAuthorization(request); 33 + String token = SecurityUtils.obtainAuthorization(request, "Authentication");
39 if (StringUtils.hasText(token)) { 34 if (StringUtils.hasText(token)) {
40 // 2. 基于 token 构建登录用户 35 // 2. 基于 token 构建登录用户
41 LoginUser loginUser = buildLoginUserByToken(token); 36 LoginUser loginUser = buildLoginUserByToken(token);
42 // 3. 设置当前用户 37 // 3. 设置当前用户
43 if (loginUser != null) { 38 if (loginUser != null) {
44 - setLoginUser(loginUser, request); 39 + SecurityUtils.setLoginUser(loginUser, request);
45 } 40 }
46 } 41 }
47 42
@@ -58,50 +53,12 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { @@ -58,50 +53,12 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
58 } 53 }
59 // 构建登录用户 54 // 构建登录用户
60 return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType()) 55 return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())
61 - .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes()); 56 + .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes())
  57 + .setAccessToken(accessToken.getAccessToken());
62 } catch (Exception exception) { 58 } catch (Exception exception) {
63 // 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可 59 // 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可
64 return null; 60 return null;
65 } 61 }
66 } 62 }
67 63
68 - /**  
69 - * 从请求 Header 中,获得访问令牌  
70 - *  
71 - * @param request 请求  
72 - * @return 访问令牌  
73 - */  
74 - private static String obtainAuthorization(HttpServletRequest request) {  
75 - String authorization = request.getHeader("Authentication");  
76 - if (!StringUtils.hasText(authorization)) {  
77 - return null;  
78 - }  
79 - int index = authorization.indexOf("Bearer ");  
80 - if (index == -1) { // 未找到  
81 - return null;  
82 - }  
83 - return authorization.substring(index + 7).trim();  
84 - }  
85 -  
86 - /**  
87 - * 设置当前用户  
88 - *  
89 - * @param loginUser 登录用户  
90 - * @param request 请求  
91 - */  
92 - private static void setLoginUser(LoginUser loginUser, HttpServletRequest request) {  
93 - // 创建 Authentication,并设置到上下文  
94 - Authentication authentication = buildAuthentication(loginUser, request);  
95 - SecurityContextHolder.getContext().setAuthentication(authentication);  
96 - }  
97 -  
98 - private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) {  
99 - // 创建 UsernamePasswordAuthenticationToken 对象  
100 - UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(  
101 - loginUser, null, Collections.emptyList());  
102 - authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));  
103 - return authenticationToken;  
104 - }  
105 -  
106 -  
107 } 64 }
yudao-example/yudao-sso-demo-by-code/src/main/resources/static/index.html
@@ -43,7 +43,7 @@ @@ -43,7 +43,7 @@
43 alert('获得个人信息失败,原因:' + result.msg) 43 alert('获得个人信息失败,原因:' + result.msg)
44 return; 44 return;
45 } 45 }
46 - $('nicknameSpan').html(result.data.nickname); 46 + $('#nicknameSpan').html(result.data.nickname);
47 } 47 }
48 }); 48 });
49 }) 49 })
@@ -57,8 +57,8 @@ @@ -57,8 +57,8 @@
57 57
58 <!-- 情况二:已登录:1)展示用户信息;2)刷新访问令牌;3)退出登录 --> 58 <!-- 情况二:已登录:1)展示用户信息;2)刷新访问令牌;3)退出登录 -->
59 <div id="yesLoginDiv" style="display: none"> 59 <div id="yesLoginDiv" style="display: none">
60 - 您已登录!点击 <a href="#" onclick="ssoLogin()">退出 </a> 系统 <br />  
61 - 昵称:<span id="nicknameSpan"> 加载中... </span> <br /> 60 + 您已登录!<button>退出登录</button> <br />
  61 + 昵称:<span id="nicknameSpan"> 加载中... </span> <button>修改昵称</button> <br />
62 访问令牌:<span id="accessTokenSpan"> 加载中... </span> <br /> 62 访问令牌:<span id="accessTokenSpan"> 加载中... </span> <br />
63 </div> 63 </div>
64 </body> 64 </body>