فهرست منبع

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

YunaiV 2 سال پیش
والد
کامیت
fc7a6c782a

+ 2 - 2
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;
 @Component
 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;如果使用别的租户,可以调整
      */
-    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_SECRET = "test";

+ 50 - 0
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/UserClient.java

@@ -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());
+    }
+}

+ 97 - 0
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/user/UserInfoRespDTO.java

@@ -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;
+
+    }
+
+}

+ 11 - 3
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/controller/UserController.java

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

+ 5 - 0
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 {
      */
     private List<String> scopes;
 
+    /**
+     * 访问令牌
+     */
+    private String accessToken;
+
 }

+ 102 - 0
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/SecurityUtils.java

@@ -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;
+    }
+
+}

+ 4 - 47
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;
 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;
@@ -17,7 +13,6 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
-import java.util.Collections;
 
 /**
  * Token 过滤器,验证 token 的有效性
@@ -35,13 +30,13 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                     FilterChain filterChain) throws ServletException, IOException {
         // 1. 获得访问令牌
-        String token = obtainAuthorization(request);
+        String token = SecurityUtils.obtainAuthorization(request, "Authentication");
         if (StringUtils.hasText(token)) {
             // 2. 基于 token 构建登录用户
             LoginUser loginUser = buildLoginUserByToken(token);
             // 3. 设置当前用户
             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())
-                    .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes());
+                    .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes())
+                    .setAccessToken(accessToken.getAccessToken());
         } 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;
-    }
-
-
 }

+ 3 - 3
yudao-example/yudao-sso-demo-by-code/src/main/resources/static/index.html

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