Commit ea71002ed6e77fb0e58baa3ba5e9dc7ebd889585
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 | 50 | </dependency> |
| 51 | 51 | |
| 52 | 52 | <dependency> |
| 53 | + <groupId>cn.hutool</groupId> | |
| 54 | + <artifactId>hutool-all</artifactId> | |
| 55 | + <version>5.8.5</version> | |
| 56 | + </dependency> | |
| 57 | + | |
| 58 | + <dependency> | |
| 53 | 59 | <groupId>org.projectlombok</groupId> |
| 54 | 60 | <artifactId>lombok</artifactId> |
| 55 | 61 | <optional>true</optional> | ... | ... |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/OAuth2Client.java
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 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; |
| 4 | 4 | import cn.iocoder.yudao.ssodemo.client.dto.user.UserInfoRespDTO; |
| 5 | +import cn.iocoder.yudao.ssodemo.client.dto.user.UserUpdateReqDTO; | |
| 5 | 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 | 8 | import org.springframework.core.ParameterizedTypeReference; |
| 8 | 9 | import org.springframework.http.*; |
| 9 | 10 | import org.springframework.stereotype.Component; |
| ... | ... | @@ -13,7 +14,9 @@ import org.springframework.util.MultiValueMap; |
| 13 | 14 | import org.springframework.web.client.RestTemplate; |
| 14 | 15 | |
| 15 | 16 | /** |
| 16 | - * OAuth 2.0 客户端 | |
| 17 | + * 用户 User 信息的客户端 | |
| 18 | + * | |
| 19 | + * 对应调用 OAuth2UserController 接口 | |
| 17 | 20 | */ |
| 18 | 21 | @Component |
| 19 | 22 | public class UserClient { |
| ... | ... | @@ -42,6 +45,26 @@ public class UserClient { |
| 42 | 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 | 68 | private static void addTokenHeader(HttpHeaders headers) { |
| 46 | 69 | LoginUser loginUser = SecurityUtils.getLoginUser(); |
| 47 | 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 | 3 | import cn.iocoder.yudao.ssodemo.client.UserClient; |
| 4 | 4 | import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; |
| 5 | 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 | 9 | import javax.annotation.Resource; |
| 11 | 10 | |
| ... | ... | @@ -26,4 +25,16 @@ public class UserController { |
| 26 | 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 | 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 | 5 | import org.springframework.context.annotation.Configuration; |
| 5 | 6 | import org.springframework.http.HttpMethod; |
| 6 | 7 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; |
| 7 | 8 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; |
| 9 | +import org.springframework.security.web.AuthenticationEntryPoint; | |
| 8 | 10 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; |
| 9 | 11 | |
| 10 | 12 | import javax.annotation.Resource; |
| ... | ... | @@ -12,17 +14,14 @@ import javax.annotation.Resource; |
| 12 | 14 | @Configuration |
| 13 | 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 | 17 | @Resource |
| 24 | 18 | private TokenAuthenticationFilter tokenAuthenticationFilter; |
| 25 | 19 | |
| 20 | + @Resource | |
| 21 | + private AccessDeniedHandlerImpl accessDeniedHandler; | |
| 22 | + @Resource | |
| 23 | + private AuthenticationEntryPoint authenticationEntryPoint; | |
| 24 | + | |
| 26 | 25 | @Override |
| 27 | 26 | protected void configure(HttpSecurity httpSecurity) throws Exception { |
| 28 | 27 | // 设置 URL 安全权限 |
| ... | ... | @@ -36,7 +35,12 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { |
| 36 | 35 | .and().authorizeRequests() |
| 37 | 36 | .anyRequest().authenticated(); |
| 38 | 37 | |
| 38 | + // 设置处理器 | |
| 39 | + httpSecurity.exceptionHandling().accessDeniedHandler(accessDeniedHandler) | |
| 40 | + .authenticationEntryPoint(authenticationEntryPoint); | |
| 41 | + | |
| 39 | 42 | // 添加 Token Filter |
| 40 | 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 | 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 cn.iocoder.yudao.ssodemo.framework.core.LoginUser; | |
| 7 | +import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils; | |
| 6 | 8 | import org.springframework.stereotype.Component; |
| 7 | 9 | import org.springframework.util.StringUtils; |
| 8 | 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 | 4 | import org.springframework.lang.Nullable; |
| 4 | 5 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; |
| 5 | 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 | 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 | 50 | $(function () { |
| 24 | 51 | const accessToken = localStorage.getItem('ACCESS-TOKEN'); |
| 25 | 52 | // 情况一:未登录 |
| ... | ... | @@ -58,7 +85,7 @@ |
| 58 | 85 | <!-- 情况二:已登录:1)展示用户信息;2)刷新访问令牌;3)退出登录 --> |
| 59 | 86 | <div id="yesLoginDiv" style="display: none"> |
| 60 | 87 | 您已登录!<button>退出登录</button> <br /> |
| 61 | - 昵称:<span id="nicknameSpan"> 加载中... </span> <button>修改昵称</button> <br /> | |
| 88 | + 昵称:<span id="nicknameSpan"> 加载中... </span> <button onclick="updateNickname()">修改昵称</button> <br /> | |
| 62 | 89 | 访问令牌:<span id="accessTokenSpan"> 加载中... </span> <br /> |
| 63 | 90 | </div> |
| 64 | 91 | </body> | ... | ... |