Commit ea71002ed6e77fb0e58baa3ba5e9dc7ebd889585

Authored by YunaiV
1 parent fc7a6c78

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

Showing 12 changed files with 236 additions and 17 deletions
yudao-example/yudao-sso-demo-by-code/pom.xml
@@ -50,6 +50,12 @@ @@ -50,6 +50,12 @@
50 </dependency> 50 </dependency>
51 51
52 <dependency> 52 <dependency>
  53 + <groupId>cn.hutool</groupId>
  54 + <artifactId>hutool-all</artifactId>
  55 + <version>5.8.5</version>
  56 + </dependency>
  57 +
  58 + <dependency>
53 <groupId>org.projectlombok</groupId> 59 <groupId>org.projectlombok</groupId>
54 <artifactId>lombok</artifactId> 60 <artifactId>lombok</artifactId>
55 <optional>true</optional> 61 <optional>true</optional>
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; @@ -16,6 +16,8 @@ import java.nio.charset.StandardCharsets;
16 16
17 /** 17 /**
18 * OAuth 2.0 客户端 18 * OAuth 2.0 客户端
  19 + *
  20 + * 对应调用 OAuth2OpenController 接口
19 */ 21 */
20 @Component 22 @Component
21 public class OAuth2Client { 23 public class OAuth2Client {
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; @@ -2,8 +2,9 @@ package cn.iocoder.yudao.ssodemo.client;
2 2
3 import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; 3 import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
4 import cn.iocoder.yudao.ssodemo.client.dto.user.UserInfoRespDTO; 4 import cn.iocoder.yudao.ssodemo.client.dto.user.UserInfoRespDTO;
  5 +import cn.iocoder.yudao.ssodemo.client.dto.user.UserUpdateReqDTO;
5 import cn.iocoder.yudao.ssodemo.framework.core.LoginUser; 6 import cn.iocoder.yudao.ssodemo.framework.core.LoginUser;
6 -import cn.iocoder.yudao.ssodemo.framework.core.SecurityUtils; 7 +import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
7 import org.springframework.core.ParameterizedTypeReference; 8 import org.springframework.core.ParameterizedTypeReference;
8 import org.springframework.http.*; 9 import org.springframework.http.*;
9 import org.springframework.stereotype.Component; 10 import org.springframework.stereotype.Component;
@@ -13,7 +14,9 @@ import org.springframework.util.MultiValueMap; @@ -13,7 +14,9 @@ import org.springframework.util.MultiValueMap;
13 import org.springframework.web.client.RestTemplate; 14 import org.springframework.web.client.RestTemplate;
14 15
15 /** 16 /**
16 - * OAuth 2.0 客户端 17 + * 用户 User 信息的客户端
  18 + *
  19 + * 对应调用 OAuth2UserController 接口
17 */ 20 */
18 @Component 21 @Component
19 public class UserClient { 22 public class UserClient {
@@ -42,6 +45,26 @@ public class UserClient { @@ -42,6 +45,26 @@ public class UserClient {
42 return exchange.getBody(); 45 return exchange.getBody();
43 } 46 }
44 47
  48 + public CommonResult<Boolean> updateUser(UserUpdateReqDTO updateReqDTO) {
  49 + // 1.1 构建请求头
  50 + HttpHeaders headers = new HttpHeaders();
  51 + headers.setContentType(MediaType.APPLICATION_JSON);
  52 + headers.set("tenant-id", OAuth2Client.TENANT_ID.toString());
  53 + addTokenHeader(headers);
  54 + // 1.2 构建请求参数
  55 + // 使用 updateReqDTO 即可
  56 +
  57 + // 2. 执行请求
  58 + ResponseEntity<CommonResult<Boolean>> exchange = restTemplate.exchange(
  59 + BASE_URL + "/update",
  60 + HttpMethod.PUT,
  61 + new HttpEntity<>(updateReqDTO, headers),
  62 + new ParameterizedTypeReference<CommonResult<Boolean>>() {}); // 解决 CommonResult 的泛型丢失
  63 + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
  64 + return exchange.getBody();
  65 + }
  66 +
  67 +
45 private static void addTokenHeader(HttpHeaders headers) { 68 private static void addTokenHeader(HttpHeaders headers) {
46 LoginUser loginUser = SecurityUtils.getLoginUser(); 69 LoginUser loginUser = SecurityUtils.getLoginUser();
47 Assert.notNull(loginUser, "登录用户不能为空"); 70 Assert.notNull(loginUser, "登录用户不能为空");
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/user/UserUpdateReqDTO.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 +/**
  8 + * 更新用户基本信息 Request DTO
  9 + */
  10 +@Data
  11 +@NoArgsConstructor
  12 +@AllArgsConstructor
  13 +public class UserUpdateReqDTO {
  14 +
  15 + /**
  16 + * 用户昵称
  17 + */
  18 + private String nickname;
  19 +
  20 + /**
  21 + * 用户邮箱
  22 + */
  23 + private String email;
  24 +
  25 + /**
  26 + * 手机号码
  27 + */
  28 + private String mobile;
  29 +
  30 + /**
  31 + * 用户性别
  32 + */
  33 + private Integer sex;
  34 +
  35 +}
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; @@ -3,9 +3,8 @@ package cn.iocoder.yudao.ssodemo.controller;
3 import cn.iocoder.yudao.ssodemo.client.UserClient; 3 import cn.iocoder.yudao.ssodemo.client.UserClient;
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.user.UserInfoRespDTO; 5 import cn.iocoder.yudao.ssodemo.client.dto.user.UserInfoRespDTO;
6 -import org.springframework.web.bind.annotation.GetMapping;  
7 -import org.springframework.web.bind.annotation.RequestMapping;  
8 -import org.springframework.web.bind.annotation.RestController; 6 +import cn.iocoder.yudao.ssodemo.client.dto.user.UserUpdateReqDTO;
  7 +import org.springframework.web.bind.annotation.*;
9 8
10 import javax.annotation.Resource; 9 import javax.annotation.Resource;
11 10
@@ -26,4 +25,16 @@ public class UserController { @@ -26,4 +25,16 @@ public class UserController {
26 return userClient.getUser(); 25 return userClient.getUser();
27 } 26 }
28 27
  28 + /**
  29 + * 更新当前登录用户的昵称
  30 + *
  31 + * @param nickname 昵称
  32 + * @return 成功
  33 + */
  34 + @PutMapping("/update")
  35 + public CommonResult<Boolean> updateUser(@RequestParam("nickname") String nickname) {
  36 + UserUpdateReqDTO updateReqDTO = new UserUpdateReqDTO(nickname, null, null, null);
  37 + return userClient.updateUser(updateReqDTO);
  38 + }
  39 +
29 } 40 }
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/config/SecurityConfiguration.java
1 package cn.iocoder.yudao.ssodemo.framework.config; 1 package cn.iocoder.yudao.ssodemo.framework.config;
2 2
3 -import cn.iocoder.yudao.ssodemo.framework.core.TokenAuthenticationFilter; 3 +import cn.iocoder.yudao.ssodemo.framework.core.filter.TokenAuthenticationFilter;
  4 +import cn.iocoder.yudao.ssodemo.framework.core.handler.AccessDeniedHandlerImpl;
4 import org.springframework.context.annotation.Configuration; 5 import org.springframework.context.annotation.Configuration;
5 import org.springframework.http.HttpMethod; 6 import org.springframework.http.HttpMethod;
6 import org.springframework.security.config.annotation.web.builders.HttpSecurity; 7 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
7 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 8 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  9 +import org.springframework.security.web.AuthenticationEntryPoint;
8 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 10 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
9 11
10 import javax.annotation.Resource; 12 import javax.annotation.Resource;
@@ -12,17 +14,14 @@ import javax.annotation.Resource; @@ -12,17 +14,14 @@ import javax.annotation.Resource;
12 @Configuration 14 @Configuration
13 public class SecurityConfiguration extends WebSecurityConfigurerAdapter { 15 public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
14 16
15 -// /**  
16 -// * Token 认证过滤器 Bean  
17 -// */  
18 -// @Bean  
19 -// public TokenAuthenticationFilter authenticationTokenFilter(OAuth2Client oauth2Client) {  
20 -// return new TokenAuthenticationFilter(oauth2Client);  
21 -// }  
22 -  
23 @Resource 17 @Resource
24 private TokenAuthenticationFilter tokenAuthenticationFilter; 18 private TokenAuthenticationFilter tokenAuthenticationFilter;
25 19
  20 + @Resource
  21 + private AccessDeniedHandlerImpl accessDeniedHandler;
  22 + @Resource
  23 + private AuthenticationEntryPoint authenticationEntryPoint;
  24 +
26 @Override 25 @Override
27 protected void configure(HttpSecurity httpSecurity) throws Exception { 26 protected void configure(HttpSecurity httpSecurity) throws Exception {
28 // 设置 URL 安全权限 27 // 设置 URL 安全权限
@@ -36,7 +35,12 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @@ -36,7 +35,12 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
36 .and().authorizeRequests() 35 .and().authorizeRequests()
37 .anyRequest().authenticated(); 36 .anyRequest().authenticated();
38 37
  38 + // 设置处理器
  39 + httpSecurity.exceptionHandling().accessDeniedHandler(accessDeniedHandler)
  40 + .authenticationEntryPoint(authenticationEntryPoint);
  41 +
39 // 添加 Token Filter 42 // 添加 Token Filter
40 httpSecurity.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); 43 httpSecurity.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
41 } 44 }
  45 +
42 } 46 }
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/TokenAuthenticationFilter.java renamed to yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/filter/TokenAuthenticationFilter.java
1 -package cn.iocoder.yudao.ssodemo.framework.core; 1 +package cn.iocoder.yudao.ssodemo.framework.core.filter;
2 2
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 cn.iocoder.yudao.ssodemo.framework.core.LoginUser;
  7 +import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
6 import org.springframework.stereotype.Component; 8 import org.springframework.stereotype.Component;
7 import org.springframework.util.StringUtils; 9 import org.springframework.util.StringUtils;
8 import org.springframework.web.filter.OncePerRequestFilter; 10 import org.springframework.web.filter.OncePerRequestFilter;
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/handler/AccessDeniedHandlerImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.framework.core.handler;
  2 +
  3 +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
  4 +import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
  5 +import cn.iocoder.yudao.ssodemo.framework.core.util.ServletUtils;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +import org.springframework.http.HttpStatus;
  8 +import org.springframework.security.access.AccessDeniedException;
  9 +import org.springframework.security.web.access.AccessDeniedHandler;
  10 +import org.springframework.security.web.access.ExceptionTranslationFilter;
  11 +import org.springframework.stereotype.Component;
  12 +
  13 +import javax.servlet.FilterChain;
  14 +import javax.servlet.ServletException;
  15 +import javax.servlet.http.HttpServletRequest;
  16 +import javax.servlet.http.HttpServletResponse;
  17 +import java.io.IOException;
  18 +
  19 +/**
  20 + * 访问一个需要认证的 URL 资源,已经认证(登录)但是没有权限的情况下,返回 {@link GlobalErrorCodeConstants#FORBIDDEN} 错误码。
  21 + *
  22 + * 补充:Spring Security 通过 {@link ExceptionTranslationFilter#handleAccessDeniedException(HttpServletRequest, HttpServletResponse, FilterChain, AccessDeniedException)} 方法,调用当前类
  23 + *
  24 + * @author 芋道源码
  25 + */
  26 +@Component
  27 +@SuppressWarnings("JavadocReference")
  28 +@Slf4j
  29 +public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
  30 +
  31 + @Override
  32 + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e)
  33 + throws IOException, ServletException {
  34 + // 打印 warn 的原因是,不定期合并 warn,看看有没恶意破坏
  35 + log.warn("[commence][访问 URL({}) 时,用户({}) 权限不够]", request.getRequestURI(),
  36 + SecurityUtils.getLoginUserId(), e);
  37 + // 返回 403
  38 + CommonResult<Object> result = new CommonResult<>();
  39 + result.setCode(HttpStatus.FORBIDDEN.value());
  40 + result.setMsg("没有该操作权限");
  41 + ServletUtils.writeJSON(response, result);
  42 + }
  43 +
  44 +}
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/handler/AuthenticationEntryPointImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.framework.core.handler;
  2 +
  3 +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
  4 +import cn.iocoder.yudao.ssodemo.framework.core.util.ServletUtils;
  5 +import lombok.extern.slf4j.Slf4j;
  6 +import org.springframework.http.HttpStatus;
  7 +import org.springframework.security.core.AuthenticationException;
  8 +import org.springframework.security.web.AuthenticationEntryPoint;
  9 +import org.springframework.security.web.access.ExceptionTranslationFilter;
  10 +import org.springframework.stereotype.Component;
  11 +
  12 +import javax.servlet.FilterChain;
  13 +import javax.servlet.http.HttpServletRequest;
  14 +import javax.servlet.http.HttpServletResponse;
  15 +
  16 +/**
  17 + * 访问一个需要认证的 URL 资源,但是此时自己尚未认证(登录)的情况下,返回 {@link GlobalErrorCodeConstants#UNAUTHORIZED} 错误码,从而使前端重定向到登录页
  18 + *
  19 + * 补充:Spring Security 通过 {@link ExceptionTranslationFilter#sendStartAuthentication(HttpServletRequest, HttpServletResponse, FilterChain, AuthenticationException)} 方法,调用当前类
  20 + */
  21 +@Component
  22 +@Slf4j
  23 +@SuppressWarnings("JavadocReference") // 忽略文档引用报错
  24 +public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
  25 +
  26 + @Override
  27 + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
  28 + log.debug("[commence][访问 URL({}) 时,没有登录]", request.getRequestURI(), e);
  29 + // 返回 401
  30 + CommonResult<Object> result = new CommonResult<>();
  31 + result.setCode(HttpStatus.UNAUTHORIZED.value());
  32 + result.setMsg("账号未登录");
  33 + ServletUtils.writeJSON(response, result);
  34 + }
  35 +
  36 +}
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/SecurityUtils.java renamed to yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/util/SecurityUtils.java
1 -package cn.iocoder.yudao.ssodemo.framework.core; 1 +package cn.iocoder.yudao.ssodemo.framework.core.util;
2 2
  3 +import cn.iocoder.yudao.ssodemo.framework.core.LoginUser;
3 import org.springframework.lang.Nullable; 4 import org.springframework.lang.Nullable;
4 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 5 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
5 import org.springframework.security.core.Authentication; 6 import org.springframework.security.core.Authentication;
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/util/ServletUtils.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.framework.core.util;
  2 +
  3 +import cn.hutool.extra.servlet.ServletUtil;
  4 +import cn.hutool.json.JSONUtil;
  5 +import org.springframework.http.MediaType;
  6 +
  7 +import javax.servlet.http.HttpServletResponse;
  8 +
  9 +/**
  10 + * 客户端工具类
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +public class ServletUtils {
  15 +
  16 + /**
  17 + * 返回 JSON 字符串
  18 + *
  19 + * @param response 响应
  20 + * @param object 对象,会序列化成 JSON 字符串
  21 + */
  22 + @SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码
  23 + public static void writeJSON(HttpServletResponse response, Object object) {
  24 + String content = JSONUtil.toJsonStr(object);
  25 + ServletUtil.write(response, content, MediaType.APPLICATION_JSON_UTF8_VALUE);
  26 + }
  27 +
  28 +}
yudao-example/yudao-sso-demo-by-code/src/main/resources/static/index.html
@@ -20,6 +20,33 @@ @@ -20,6 +20,33 @@
20 + '&response_type=' + responseType; 20 + '&response_type=' + responseType;
21 } 21 }
22 22
  23 + /**
  24 + * 修改昵称
  25 + */
  26 + function updateNickname() {
  27 + const nickname = prompt("请输入新的昵称", "");
  28 + if (!nickname) {
  29 + return;
  30 + }
  31 + // 更新用户的昵称
  32 + const accessToken = localStorage.getItem('ACCESS-TOKEN');
  33 + $.ajax({
  34 + url: "http://127.0.0.1:18080/user/update?nickname=" + nickname,
  35 + method: 'PUT',
  36 + headers: {
  37 + 'Authentication': 'Bearer ' + accessToken
  38 + },
  39 + success: function (result) {
  40 + if (result.code !== 0) {
  41 + alert('更新昵称失败,原因:' + result.msg)
  42 + return;
  43 + }
  44 + alert('更新昵称成功!');
  45 + $('#nicknameSpan').html(nickname);
  46 + }
  47 + });
  48 + }
  49 +
23 $(function () { 50 $(function () {
24 const accessToken = localStorage.getItem('ACCESS-TOKEN'); 51 const accessToken = localStorage.getItem('ACCESS-TOKEN');
25 // 情况一:未登录 52 // 情况一:未登录
@@ -58,7 +85,7 @@ @@ -58,7 +85,7 @@
58 <!-- 情况二:已登录:1)展示用户信息;2)刷新访问令牌;3)退出登录 --> 85 <!-- 情况二:已登录:1)展示用户信息;2)刷新访问令牌;3)退出登录 -->
59 <div id="yesLoginDiv" style="display: none"> 86 <div id="yesLoginDiv" style="display: none">
60 您已登录!<button>退出登录</button> <br /> 87 您已登录!<button>退出登录</button> <br />
61 - 昵称:<span id="nicknameSpan"> 加载中... </span> <button>修改昵称</button> <br /> 88 + 昵称:<span id="nicknameSpan"> 加载中... </span> <button onclick="updateNickname()">修改昵称</button> <br />
62 访问令牌:<span id="accessTokenSpan"> 加载中... </span> <br /> 89 访问令牌:<span id="accessTokenSpan"> 加载中... </span> <br />
63 </div> 90 </div>
64 </body> 91 </body>