完成 yudao-sso-demo-by-code 实现修改用户的信息
parent
fc7a6c782a
commit
ea71002ed6
|
@ -49,6 +49,12 @@
|
||||||
<artifactId>spring-boot-starter-security</artifactId>
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-all</artifactId>
|
||||||
|
<version>5.8.5</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
|
|
|
@ -16,6 +16,8 @@ import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OAuth 2.0 客户端
|
* OAuth 2.0 客户端
|
||||||
|
*
|
||||||
|
* 对应调用 OAuth2OpenController 接口
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class OAuth2Client {
|
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.CommonResult;
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.user.UserInfoRespDTO;
|
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.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.core.ParameterizedTypeReference;
|
||||||
import org.springframework.http.*;
|
import org.springframework.http.*;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
@ -13,7 +14,9 @@ import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OAuth 2.0 客户端
|
* 用户 User 信息的客户端
|
||||||
|
*
|
||||||
|
* 对应调用 OAuth2UserController 接口
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class UserClient {
|
public class UserClient {
|
||||||
|
@ -42,6 +45,26 @@ public class UserClient {
|
||||||
return exchange.getBody();
|
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) {
|
private static void addTokenHeader(HttpHeaders headers) {
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||||
Assert.notNull(loginUser, "登录用户不能为空");
|
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.UserClient;
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
|
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.UserInfoRespDTO;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import cn.iocoder.yudao.ssodemo.client.dto.user.UserUpdateReqDTO;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
@ -26,4 +25,16 @@ public class UserController {
|
||||||
return userClient.getUser();
|
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;
|
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.context.annotation.Configuration;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
|
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
@ -12,17 +14,14 @@ import javax.annotation.Resource;
|
||||||
@Configuration
|
@Configuration
|
||||||
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Token 认证过滤器 Bean
|
|
||||||
// */
|
|
||||||
// @Bean
|
|
||||||
// public TokenAuthenticationFilter authenticationTokenFilter(OAuth2Client oauth2Client) {
|
|
||||||
// return new TokenAuthenticationFilter(oauth2Client);
|
|
||||||
// }
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private TokenAuthenticationFilter tokenAuthenticationFilter;
|
private TokenAuthenticationFilter tokenAuthenticationFilter;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AccessDeniedHandlerImpl accessDeniedHandler;
|
||||||
|
@Resource
|
||||||
|
private AuthenticationEntryPoint authenticationEntryPoint;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void configure(HttpSecurity httpSecurity) throws Exception {
|
protected void configure(HttpSecurity httpSecurity) throws Exception {
|
||||||
// 设置 URL 安全权限
|
// 设置 URL 安全权限
|
||||||
|
@ -36,7 +35,12 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||||
.and().authorizeRequests()
|
.and().authorizeRequests()
|
||||||
.anyRequest().authenticated();
|
.anyRequest().authenticated();
|
||||||
|
|
||||||
|
// 设置处理器
|
||||||
|
httpSecurity.exceptionHandling().accessDeniedHandler(accessDeniedHandler)
|
||||||
|
.authenticationEntryPoint(authenticationEntryPoint);
|
||||||
|
|
||||||
// 添加 Token Filter
|
// 添加 Token Filter
|
||||||
httpSecurity.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
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.OAuth2Client;
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
|
import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2CheckTokenRespDTO;
|
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.stereotype.Component;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
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.lang.Nullable;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
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;
|
+ '&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 () {
|
$(function () {
|
||||||
const accessToken = localStorage.getItem('ACCESS-TOKEN');
|
const accessToken = localStorage.getItem('ACCESS-TOKEN');
|
||||||
// 情况一:未登录
|
// 情况一:未登录
|
||||||
|
@ -58,7 +85,7 @@
|
||||||
<!-- 情况二:已登录:1)展示用户信息;2)刷新访问令牌;3)退出登录 -->
|
<!-- 情况二:已登录:1)展示用户信息;2)刷新访问令牌;3)退出登录 -->
|
||||||
<div id="yesLoginDiv" style="display: none">
|
<div id="yesLoginDiv" style="display: none">
|
||||||
您已登录!<button>退出登录</button> <br />
|
您已登录!<button>退出登录</button> <br />
|
||||||
昵称:<span id="nicknameSpan"> 加载中... </span> <button>修改昵称</button> <br />
|
昵称:<span id="nicknameSpan"> 加载中... </span> <button onclick="updateNickname()">修改昵称</button> <br />
|
||||||
访问令牌:<span id="accessTokenSpan"> 加载中... </span> <br />
|
访问令牌:<span id="accessTokenSpan"> 加载中... </span> <br />
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
Loading…
Reference in New Issue