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 20 @Component
21 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 28 * 默认使用 1;如果使用别的租户,可以调整
29 29 */
30   - private static final Long TENANT_ID = 1L;
  30 + public static final Long TENANT_ID = 1L;
31 31  
32 32 private static final String CLIENT_ID = "yudao-sso-demo-by-code";
33 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 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 6 import org.springframework.web.bind.annotation.GetMapping;
4 7 import org.springframework.web.bind.annotation.RequestMapping;
5 8 import org.springframework.web.bind.annotation.RestController;
6 9  
  10 +import javax.annotation.Resource;
  11 +
7 12 @RestController
8 13 @RequestMapping("/user")
9 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 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 29 */
30 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 3 import cn.iocoder.yudao.ssodemo.client.OAuth2Client;
4 4 import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
5 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 6 import org.springframework.stereotype.Component;
11 7 import org.springframework.util.StringUtils;
12 8 import org.springframework.web.filter.OncePerRequestFilter;
... ... @@ -17,7 +13,6 @@ import javax.servlet.ServletException;
17 13 import javax.servlet.http.HttpServletRequest;
18 14 import javax.servlet.http.HttpServletResponse;
19 15 import java.io.IOException;
20   -import java.util.Collections;
21 16  
22 17 /**
23 18 * Token 过滤器,验证 token 的有效性
... ... @@ -35,13 +30,13 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
35 30 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
36 31 FilterChain filterChain) throws ServletException, IOException {
37 32 // 1. 获得访问令牌
38   - String token = obtainAuthorization(request);
  33 + String token = SecurityUtils.obtainAuthorization(request, "Authentication");
39 34 if (StringUtils.hasText(token)) {
40 35 // 2. 基于 token 构建登录用户
41 36 LoginUser loginUser = buildLoginUserByToken(token);
42 37 // 3. 设置当前用户
43 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 53 }
59 54 // 构建登录用户
60 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 58 } catch (Exception exception) {
63 59 // 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可
64 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 43 alert('获得个人信息失败,原因:' + result.msg)
44 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 57  
58 58 <!-- 情况二:已登录:1)展示用户信息;2)刷新访问令牌;3)退出登录 -->
59 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 62 访问令牌:<span id="accessTokenSpan"> 加载中... </span> <br />
63 63 </div>
64 64 </body>
... ...