Просмотр исходного кода

完成 yudao-sso-demo-by-code 实现修改用户的信息

YunaiV 2 лет назад
Родитель
Сommit
ea71002ed6
12 измененных файлов с 236 добавлено и 17 удалено
  1. 6 0
      yudao-example/yudao-sso-demo-by-code/pom.xml
  2. 2 0
      yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/OAuth2Client.java
  3. 25 2
      yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/UserClient.java
  4. 35 0
      yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/user/UserUpdateReqDTO.java
  5. 14 3
      yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/controller/UserController.java
  6. 13 9
      yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/config/SecurityConfiguration.java
  7. 3 1
      yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/TokenAuthenticationFilter.java
  8. 44 0
      yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/handler/AccessDeniedHandlerImpl.java
  9. 36 0
      yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/handler/AuthenticationEntryPointImpl.java
  10. 2 1
      yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/SecurityUtils.java
  11. 28 0
      yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/util/ServletUtils.java
  12. 28 1
      yudao-example/yudao-sso-demo-by-code/src/main/resources/static/index.html

+ 6 - 0
yudao-example/yudao-sso-demo-by-code/pom.xml

@@ -50,6 +50,12 @@
         </dependency>
 
         <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.8.5</version>
+        </dependency>
+
+        <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
             <optional>true</optional>

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

@@ -16,6 +16,8 @@ import java.nio.charset.StandardCharsets;
 
 /**
  * OAuth 2.0 客户端
+ *
+ * 对应调用 OAuth2OpenController 接口
  */
 @Component
 public class OAuth2Client {

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

@@ -2,8 +2,9 @@ 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.client.dto.user.UserUpdateReqDTO;
 import cn.iocoder.yudao.ssodemo.framework.core.LoginUser;
-import cn.iocoder.yudao.ssodemo.framework.core.SecurityUtils;
+import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
 import org.springframework.core.ParameterizedTypeReference;
 import org.springframework.http.*;
 import org.springframework.stereotype.Component;
@@ -13,7 +14,9 @@ import org.springframework.util.MultiValueMap;
 import org.springframework.web.client.RestTemplate;
 
 /**
- * OAuth 2.0 客户端
+ * 用户 User 信息的客户端
+ *
+ * 对应调用 OAuth2UserController 接口
  */
 @Component
 public class UserClient {
@@ -42,6 +45,26 @@ public class UserClient {
         return exchange.getBody();
     }
 
+    public CommonResult<Boolean> updateUser(UserUpdateReqDTO updateReqDTO) {
+        // 1.1 构建请求头
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_JSON);
+        headers.set("tenant-id", OAuth2Client.TENANT_ID.toString());
+        addTokenHeader(headers);
+        // 1.2 构建请求参数
+        // 使用 updateReqDTO 即可
+
+        // 2. 执行请求
+        ResponseEntity<CommonResult<Boolean>> exchange = restTemplate.exchange(
+                BASE_URL + "/update",
+                HttpMethod.PUT,
+                new HttpEntity<>(updateReqDTO, headers),
+                new ParameterizedTypeReference<CommonResult<Boolean>>() {}); // 解决 CommonResult 的泛型丢失
+        Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
+        return exchange.getBody();
+    }
+
+
     private static void addTokenHeader(HttpHeaders headers) {
         LoginUser loginUser = SecurityUtils.getLoginUser();
         Assert.notNull(loginUser, "登录用户不能为空");

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

@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.ssodemo.client.dto.user;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 更新用户基本信息 Request DTO
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class UserUpdateReqDTO {
+
+    /**
+     * 用户昵称
+     */
+    private String nickname;
+
+    /**
+     * 用户邮箱
+     */
+    private String email;
+
+    /**
+     * 手机号码
+     */
+    private String mobile;
+
+    /**
+     * 用户性别
+     */
+    private Integer sex;
+
+}

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

@@ -3,9 +3,8 @@ 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 cn.iocoder.yudao.ssodemo.client.dto.user.UserUpdateReqDTO;
+import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
 
@@ -26,4 +25,16 @@ public class UserController {
         return userClient.getUser();
     }
 
+    /**
+     * 更新当前登录用户的昵称
+     *
+     * @param nickname 昵称
+     * @return 成功
+     */
+    @PutMapping("/update")
+    public CommonResult<Boolean> updateUser(@RequestParam("nickname") String nickname) {
+        UserUpdateReqDTO updateReqDTO = new UserUpdateReqDTO(nickname, null, null, null);
+        return userClient.updateUser(updateReqDTO);
+    }
+
 }

+ 13 - 9
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/config/SecurityConfiguration.java

@@ -1,10 +1,12 @@
 package cn.iocoder.yudao.ssodemo.framework.config;
 
-import cn.iocoder.yudao.ssodemo.framework.core.TokenAuthenticationFilter;
+import cn.iocoder.yudao.ssodemo.framework.core.filter.TokenAuthenticationFilter;
+import cn.iocoder.yudao.ssodemo.framework.core.handler.AccessDeniedHandlerImpl;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.http.HttpMethod;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.web.AuthenticationEntryPoint;
 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
 
 import javax.annotation.Resource;
@@ -12,17 +14,14 @@ import javax.annotation.Resource;
 @Configuration
 public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
 
-//    /**
-//     * Token 认证过滤器 Bean
-//     */
-//    @Bean
-//    public TokenAuthenticationFilter authenticationTokenFilter(OAuth2Client oauth2Client) {
-//        return new TokenAuthenticationFilter(oauth2Client);
-//    }
-
     @Resource
     private TokenAuthenticationFilter tokenAuthenticationFilter;
 
+    @Resource
+    private AccessDeniedHandlerImpl accessDeniedHandler;
+    @Resource
+    private AuthenticationEntryPoint authenticationEntryPoint;
+
     @Override
     protected void configure(HttpSecurity httpSecurity) throws Exception {
         // 设置 URL 安全权限
@@ -36,7 +35,12 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
                 .and().authorizeRequests()
                 .anyRequest().authenticated();
 
+        // 设置处理器
+        httpSecurity.exceptionHandling().accessDeniedHandler(accessDeniedHandler)
+                .authenticationEntryPoint(authenticationEntryPoint);
+
         // 添加 Token Filter
         httpSecurity.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
     }
+
 }

+ 3 - 1
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/TokenAuthenticationFilter.java

@@ -1,8 +1,10 @@
-package cn.iocoder.yudao.ssodemo.framework.core;
+package cn.iocoder.yudao.ssodemo.framework.core.filter;
 
 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 cn.iocoder.yudao.ssodemo.framework.core.LoginUser;
+import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
 import org.springframework.stereotype.Component;
 import org.springframework.util.StringUtils;
 import org.springframework.web.filter.OncePerRequestFilter;

+ 44 - 0
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/handler/AccessDeniedHandlerImpl.java

@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.ssodemo.framework.core.handler;
+
+import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
+import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
+import cn.iocoder.yudao.ssodemo.framework.core.util.ServletUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.web.access.AccessDeniedHandler;
+import org.springframework.security.web.access.ExceptionTranslationFilter;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * 访问一个需要认证的 URL 资源,已经认证(登录)但是没有权限的情况下,返回 {@link GlobalErrorCodeConstants#FORBIDDEN} 错误码。
+ *
+ * 补充:Spring Security 通过 {@link ExceptionTranslationFilter#handleAccessDeniedException(HttpServletRequest, HttpServletResponse, FilterChain, AccessDeniedException)} 方法,调用当前类
+ *
+ * @author 芋道源码
+ */
+@Component
+@SuppressWarnings("JavadocReference")
+@Slf4j
+public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
+
+    @Override
+    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e)
+            throws IOException, ServletException {
+        // 打印 warn 的原因是,不定期合并 warn,看看有没恶意破坏
+        log.warn("[commence][访问 URL({}) 时,用户({}) 权限不够]", request.getRequestURI(),
+                SecurityUtils.getLoginUserId(), e);
+        // 返回 403
+        CommonResult<Object> result = new CommonResult<>();
+        result.setCode(HttpStatus.FORBIDDEN.value());
+        result.setMsg("没有该操作权限");
+        ServletUtils.writeJSON(response, result);
+    }
+
+}

+ 36 - 0
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/handler/AuthenticationEntryPointImpl.java

@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.ssodemo.framework.core.handler;
+
+import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
+import cn.iocoder.yudao.ssodemo.framework.core.util.ServletUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.security.web.access.ExceptionTranslationFilter;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.FilterChain;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * 访问一个需要认证的 URL 资源,但是此时自己尚未认证(登录)的情况下,返回 {@link GlobalErrorCodeConstants#UNAUTHORIZED} 错误码,从而使前端重定向到登录页
+ *
+ * 补充:Spring Security 通过 {@link ExceptionTranslationFilter#sendStartAuthentication(HttpServletRequest, HttpServletResponse, FilterChain, AuthenticationException)} 方法,调用当前类
+ */
+@Component
+@Slf4j
+@SuppressWarnings("JavadocReference") // 忽略文档引用报错
+public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
+
+    @Override
+    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
+        log.debug("[commence][访问 URL({}) 时,没有登录]", request.getRequestURI(), e);
+        // 返回 401
+        CommonResult<Object> result = new CommonResult<>();
+        result.setCode(HttpStatus.UNAUTHORIZED.value());
+        result.setMsg("账号未登录");
+        ServletUtils.writeJSON(response, result);
+    }
+
+}

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

@@ -1,5 +1,6 @@
-package cn.iocoder.yudao.ssodemo.framework.core;
+package cn.iocoder.yudao.ssodemo.framework.core.util;
 
+import cn.iocoder.yudao.ssodemo.framework.core.LoginUser;
 import org.springframework.lang.Nullable;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.Authentication;

+ 28 - 0
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/util/ServletUtils.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.ssodemo.framework.core.util;
+
+import cn.hutool.extra.servlet.ServletUtil;
+import cn.hutool.json.JSONUtil;
+import org.springframework.http.MediaType;
+
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * 客户端工具类
+ *
+ * @author 芋道源码
+ */
+public class ServletUtils {
+
+    /**
+     * 返回 JSON 字符串
+     *
+     * @param response 响应
+     * @param object 对象,会序列化成 JSON 字符串
+     */
+    @SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码
+    public static void writeJSON(HttpServletResponse response, Object object) {
+        String content = JSONUtil.toJsonStr(object);
+        ServletUtil.write(response, content, MediaType.APPLICATION_JSON_UTF8_VALUE);
+    }
+
+}

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

@@ -20,6 +20,33 @@
 				+ '&response_type=' + responseType;
     }
 
+    /**
+		 * 修改昵称
+     */
+    function updateNickname() {
+      const nickname = prompt("请输入新的昵称", "");
+      if (!nickname) {
+        return;
+      }
+      // 更新用户的昵称
+      const accessToken = localStorage.getItem('ACCESS-TOKEN');
+      $.ajax({
+        url: "http://127.0.0.1:18080/user/update?nickname=" + nickname,
+        method: 'PUT',
+        headers: {
+          'Authentication': 'Bearer ' + accessToken
+        },
+        success: function (result) {
+          if (result.code !== 0) {
+            alert('更新昵称失败,原因:' + result.msg)
+            return;
+          }
+          alert('更新昵称成功!');
+          $('#nicknameSpan').html(nickname);
+        }
+      });
+		}
+
     $(function () {
       const accessToken = localStorage.getItem('ACCESS-TOKEN');
       // 情况一:未登录
@@ -58,7 +85,7 @@
 	<!-- 情况二:已登录:1)展示用户信息;2)刷新访问令牌;3)退出登录 -->
 	<div id="yesLoginDiv" style="display: none">
 		您已登录!<button>退出登录</button> <br />
-		昵称:<span id="nicknameSpan"> 加载中... </span> <button>修改昵称</button> <br />
+		昵称:<span id="nicknameSpan"> 加载中... </span> <button onclick="updateNickname()">修改昵称</button> <br />
 		访问令牌:<span id="accessTokenSpan"> 加载中... </span> <br />
 	</div>
 </body>