完成 yudao-sso-demo-by-code 实现修改用户的信息
This commit is contained in:
parent
fc7a6c782a
commit
ea71002ed6
|
@ -49,6 +49,12 @@
|
|||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.5</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
|
|
|
@ -16,6 +16,8 @@ import java.nio.charset.StandardCharsets;
|
|||
|
||||
/**
|
||||
* OAuth 2.0 客户端
|
||||
*
|
||||
* 对应调用 OAuth2OpenController 接口
|
||||
*/
|
||||
@Component
|
||||
public class OAuth2Client {
|
||||
|
|
|
@ -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, "登录用户不能为空");
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue