From fc7a6c782a18074d41d80377e4ac993d65c35fe6 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 1 Oct 2022 10:52:27 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=20yudao-sso-demo-by-code=20?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E8=8E=B7=E5=BE=97=E7=94=A8=E6=88=B7=E4=BF=A1?= =?UTF-8?q?=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/ssodemo/client/OAuth2Client.java | 4 +- .../yudao/ssodemo/client/UserClient.java | 50 +++++++++ .../client/dto/user/UserInfoRespDTO.java | 97 +++++++++++++++++ .../ssodemo/controller/UserController.java | 14 ++- .../ssodemo/framework/core/LoginUser.java | 5 + .../ssodemo/framework/core/SecurityUtils.java | 102 ++++++++++++++++++ .../core/TokenAuthenticationFilter.java | 51 +-------- .../src/main/resources/static/index.html | 6 +- 8 files changed, 274 insertions(+), 55 deletions(-) create mode 100644 yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/UserClient.java create mode 100644 yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/user/UserInfoRespDTO.java create mode 100644 yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/SecurityUtils.java diff --git a/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/OAuth2Client.java b/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/OAuth2Client.java index 8449dd39b..f29da71a4 100644 --- a/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/OAuth2Client.java +++ b/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/OAuth2Client.java @@ -20,14 +20,14 @@ import java.nio.charset.StandardCharsets; @Component public class OAuth2Client { - private static final String BASE_URL = "http://127.0.0.1:48080/admin-api/system/oauth2/"; + private static final String BASE_URL = "http://127.0.0.1:48080/admin-api/system/oauth2"; /** * 租户编号 * * 默认使用 1;如果使用别的租户,可以调整 */ - private static final Long TENANT_ID = 1L; + public static final Long TENANT_ID = 1L; private static final String CLIENT_ID = "yudao-sso-demo-by-code"; private static final String CLIENT_SECRET = "test"; diff --git a/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/UserClient.java b/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/UserClient.java new file mode 100644 index 000000000..8b2fa2a2e --- /dev/null +++ b/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/UserClient.java @@ -0,0 +1,50 @@ +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.framework.core.LoginUser; +import cn.iocoder.yudao.ssodemo.framework.core.SecurityUtils; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.*; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +/** + * OAuth 2.0 客户端 + */ +@Component +public class UserClient { + + private static final String BASE_URL = "http://127.0.0.1:48080/admin-api//system/oauth2/user"; + + // @Resource // 可优化,注册一个 RestTemplate Bean,然后注入 + private final RestTemplate restTemplate = new RestTemplate(); + + public CommonResult getUser() { + // 1.1 构建请求头 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + headers.set("tenant-id", OAuth2Client.TENANT_ID.toString()); + addTokenHeader(headers); + // 1.2 构建请求参数 + MultiValueMap body = new LinkedMultiValueMap<>(); + + // 2. 执行请求 + ResponseEntity> exchange = restTemplate.exchange( + BASE_URL + "/get", + HttpMethod.GET, + new HttpEntity<>(body, headers), + new ParameterizedTypeReference>() {}); // 解决 CommonResult 的泛型丢失 + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功"); + return exchange.getBody(); + } + + private static void addTokenHeader(HttpHeaders headers) { + LoginUser loginUser = SecurityUtils.getLoginUser(); + Assert.notNull(loginUser, "登录用户不能为空"); + headers.add("Authorization", "Bearer " + loginUser.getAccessToken()); + } +} diff --git a/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/user/UserInfoRespDTO.java b/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/user/UserInfoRespDTO.java new file mode 100644 index 000000000..e81bea9eb --- /dev/null +++ b/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/user/UserInfoRespDTO.java @@ -0,0 +1,97 @@ +package cn.iocoder.yudao.ssodemo.client.dto.user; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 获得用户基本信息 Response dto + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class UserInfoRespDTO { + + /** + * 用户编号 + */ + private Long id; + + /** + * 用户账号 + */ + private String username; + + /** + * 用户昵称 + */ + private String nickname; + + /** + * 用户邮箱 + */ + private String email; + /** + * 手机号码 + */ + private String mobile; + + /** + * 用户性别 + */ + private Integer sex; + + /** + * 用户头像 + */ + private String avatar; + + /** + * 所在部门 + */ + private Dept dept; + + /** + * 所属岗位数组 + */ + private List posts; + + /** + * 部门 + */ + @Data + public static class Dept { + + /** + * 部门编号 + */ + private Long id; + + /** + * 部门名称 + */ + private String name; + + } + + /** + * 岗位 + */ + @Data + public static class Post { + + /** + * 岗位编号 + */ + private Long id; + + /** + * 岗位名称 + */ + private String name; + + } + +} diff --git a/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/controller/UserController.java b/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/controller/UserController.java index 77e4a9113..5626b7140 100644 --- a/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/controller/UserController.java +++ b/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/controller/UserController.java @@ -1,21 +1,29 @@ 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 javax.annotation.Resource; + @RestController @RequestMapping("/user") public class UserController { + @Resource + private UserClient userClient; + /** * 获得当前登录用户的基本信息 * - * @return TODO + * @return 用户信息;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段 */ @GetMapping("/get") - public String getUser() { - return ""; + public CommonResult getUser() { + return userClient.getUser(); } } diff --git a/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/LoginUser.java b/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/LoginUser.java index e785e1544..44f3edf54 100644 --- a/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/LoginUser.java +++ b/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/LoginUser.java @@ -29,4 +29,9 @@ public class LoginUser { */ private List scopes; + /** + * 访问令牌 + */ + private String accessToken; + } diff --git a/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/SecurityUtils.java b/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/SecurityUtils.java new file mode 100644 index 000000000..adfd32b60 --- /dev/null +++ b/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/SecurityUtils.java @@ -0,0 +1,102 @@ +package cn.iocoder.yudao.ssodemo.framework.core; + +import org.springframework.lang.Nullable; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.util.StringUtils; + +import javax.servlet.http.HttpServletRequest; +import java.util.Collections; + +/** + * 安全服务工具类 + * + * @author 芋道源码 + */ +public class SecurityUtils { + + public static final String AUTHORIZATION_BEARER = "Bearer"; + + private SecurityUtils() {} + + /** + * 从请求中,获得认证 Token + * + * @param request 请求 + * @param header 认证 Token 对应的 Header 名字 + * @return 认证 Token + */ + public static String obtainAuthorization(HttpServletRequest request, String header) { + String authorization = request.getHeader(header); + if (!StringUtils.hasText(authorization)) { + return null; + } + int index = authorization.indexOf(AUTHORIZATION_BEARER + " "); + if (index == -1) { // 未找到 + return null; + } + return authorization.substring(index + 7).trim(); + } + + /** + * 获得当前认证信息 + * + * @return 认证信息 + */ + public static Authentication getAuthentication() { + SecurityContext context = SecurityContextHolder.getContext(); + if (context == null) { + return null; + } + return context.getAuthentication(); + } + + /** + * 获取当前用户 + * + * @return 当前用户 + */ + @Nullable + public static LoginUser getLoginUser() { + Authentication authentication = getAuthentication(); + if (authentication == null) { + return null; + } + return authentication.getPrincipal() instanceof LoginUser ? (LoginUser) authentication.getPrincipal() : null; + } + + /** + * 获得当前用户的编号,从上下文中 + * + * @return 用户编号 + */ + @Nullable + public static Long getLoginUserId() { + LoginUser loginUser = getLoginUser(); + return loginUser != null ? loginUser.getId() : null; + } + + /** + * 设置当前用户 + * + * @param loginUser 登录用户 + * @param request 请求 + */ + public static void setLoginUser(LoginUser loginUser, HttpServletRequest request) { + // 创建 Authentication,并设置到上下文 + Authentication authentication = buildAuthentication(loginUser, request); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + + private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) { + // 创建 UsernamePasswordAuthenticationToken 对象 + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( + loginUser, null, Collections.emptyList()); + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + return authenticationToken; + } + +} diff --git a/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/TokenAuthenticationFilter.java b/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/TokenAuthenticationFilter.java index a895fc2c5..cb90b4840 100644 --- a/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/TokenAuthenticationFilter.java +++ b/yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/TokenAuthenticationFilter.java @@ -3,10 +3,6 @@ package cn.iocoder.yudao.ssodemo.framework.core; 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 org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; @@ -17,7 +13,6 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.util.Collections; /** * Token 过滤器,验证 token 的有效性 @@ -35,13 +30,13 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 1. 获得访问令牌 - String token = obtainAuthorization(request); + String token = SecurityUtils.obtainAuthorization(request, "Authentication"); if (StringUtils.hasText(token)) { // 2. 基于 token 构建登录用户 LoginUser loginUser = buildLoginUserByToken(token); // 3. 设置当前用户 if (loginUser != null) { - setLoginUser(loginUser, request); + SecurityUtils.setLoginUser(loginUser, request); } } @@ -58,50 +53,12 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { } // 构建登录用户 return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType()) - .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes()); + .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes()) + .setAccessToken(accessToken.getAccessToken()); } catch (Exception exception) { // 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可 return null; } } - /** - * 从请求 Header 中,获得访问令牌 - * - * @param request 请求 - * @return 访问令牌 - */ - private static String obtainAuthorization(HttpServletRequest request) { - String authorization = request.getHeader("Authentication"); - if (!StringUtils.hasText(authorization)) { - return null; - } - int index = authorization.indexOf("Bearer "); - if (index == -1) { // 未找到 - return null; - } - return authorization.substring(index + 7).trim(); - } - - /** - * 设置当前用户 - * - * @param loginUser 登录用户 - * @param request 请求 - */ - private static void setLoginUser(LoginUser loginUser, HttpServletRequest request) { - // 创建 Authentication,并设置到上下文 - Authentication authentication = buildAuthentication(loginUser, request); - SecurityContextHolder.getContext().setAuthentication(authentication); - } - - private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) { - // 创建 UsernamePasswordAuthenticationToken 对象 - UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( - loginUser, null, Collections.emptyList()); - authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - return authenticationToken; - } - - } diff --git a/yudao-example/yudao-sso-demo-by-code/src/main/resources/static/index.html b/yudao-example/yudao-sso-demo-by-code/src/main/resources/static/index.html index df037f4c5..491982bf9 100644 --- a/yudao-example/yudao-sso-demo-by-code/src/main/resources/static/index.html +++ b/yudao-example/yudao-sso-demo-by-code/src/main/resources/static/index.html @@ -43,7 +43,7 @@ alert('获得个人信息失败,原因:' + result.msg) return; } - $('nicknameSpan').html(result.data.nickname); + $('#nicknameSpan').html(result.data.nickname); } }); }) @@ -57,8 +57,8 @@