Commit fc7a6c782a18074d41d80377e4ac993d65c35fe6
1 parent
b7b31f03
完成 yudao-sso-demo-by-code 实现获得用户信息
Showing
8 changed files
with
274 additions
and
55 deletions
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; |
| 20 | 20 | @Component |
| 21 | 21 | public class OAuth2Client { |
| 22 | 22 | |
| 23 | - private static final String BASE_URL = "http://127.0.0.1:48080/admin-api/system/oauth2/"; | |
| 23 | + private static final String BASE_URL = "http://127.0.0.1:48080/admin-api/system/oauth2"; | |
| 24 | 24 | |
| 25 | 25 | /** |
| 26 | 26 | * 租户编号 |
| 27 | 27 | * |
| 28 | 28 | * 默认使用 1;如果使用别的租户,可以调整 |
| 29 | 29 | */ |
| 30 | - private static final Long TENANT_ID = 1L; | |
| 30 | + public static final Long TENANT_ID = 1L; | |
| 31 | 31 | |
| 32 | 32 | private static final String CLIENT_ID = "yudao-sso-demo-by-code"; |
| 33 | 33 | private static final String CLIENT_SECRET = "test"; | ... | ... |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/UserClient.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.client; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; | |
| 4 | +import cn.iocoder.yudao.ssodemo.client.dto.user.UserInfoRespDTO; | |
| 5 | +import cn.iocoder.yudao.ssodemo.framework.core.LoginUser; | |
| 6 | +import cn.iocoder.yudao.ssodemo.framework.core.SecurityUtils; | |
| 7 | +import org.springframework.core.ParameterizedTypeReference; | |
| 8 | +import org.springframework.http.*; | |
| 9 | +import org.springframework.stereotype.Component; | |
| 10 | +import org.springframework.util.Assert; | |
| 11 | +import org.springframework.util.LinkedMultiValueMap; | |
| 12 | +import org.springframework.util.MultiValueMap; | |
| 13 | +import org.springframework.web.client.RestTemplate; | |
| 14 | + | |
| 15 | +/** | |
| 16 | + * OAuth 2.0 客户端 | |
| 17 | + */ | |
| 18 | +@Component | |
| 19 | +public class UserClient { | |
| 20 | + | |
| 21 | + private static final String BASE_URL = "http://127.0.0.1:48080/admin-api//system/oauth2/user"; | |
| 22 | + | |
| 23 | + // @Resource // 可优化,注册一个 RestTemplate Bean,然后注入 | |
| 24 | + private final RestTemplate restTemplate = new RestTemplate(); | |
| 25 | + | |
| 26 | + public CommonResult<UserInfoRespDTO> getUser() { | |
| 27 | + // 1.1 构建请求头 | |
| 28 | + HttpHeaders headers = new HttpHeaders(); | |
| 29 | + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); | |
| 30 | + headers.set("tenant-id", OAuth2Client.TENANT_ID.toString()); | |
| 31 | + addTokenHeader(headers); | |
| 32 | + // 1.2 构建请求参数 | |
| 33 | + MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); | |
| 34 | + | |
| 35 | + // 2. 执行请求 | |
| 36 | + ResponseEntity<CommonResult<UserInfoRespDTO>> exchange = restTemplate.exchange( | |
| 37 | + BASE_URL + "/get", | |
| 38 | + HttpMethod.GET, | |
| 39 | + new HttpEntity<>(body, headers), | |
| 40 | + new ParameterizedTypeReference<CommonResult<UserInfoRespDTO>>() {}); // 解决 CommonResult 的泛型丢失 | |
| 41 | + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功"); | |
| 42 | + return exchange.getBody(); | |
| 43 | + } | |
| 44 | + | |
| 45 | + private static void addTokenHeader(HttpHeaders headers) { | |
| 46 | + LoginUser loginUser = SecurityUtils.getLoginUser(); | |
| 47 | + Assert.notNull(loginUser, "登录用户不能为空"); | |
| 48 | + headers.add("Authorization", "Bearer " + loginUser.getAccessToken()); | |
| 49 | + } | |
| 50 | +} | ... | ... |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/user/UserInfoRespDTO.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 | +import java.util.List; | |
| 8 | + | |
| 9 | +/** | |
| 10 | + * 获得用户基本信息 Response dto | |
| 11 | + */ | |
| 12 | +@Data | |
| 13 | +@NoArgsConstructor | |
| 14 | +@AllArgsConstructor | |
| 15 | +public class UserInfoRespDTO { | |
| 16 | + | |
| 17 | + /** | |
| 18 | + * 用户编号 | |
| 19 | + */ | |
| 20 | + private Long id; | |
| 21 | + | |
| 22 | + /** | |
| 23 | + * 用户账号 | |
| 24 | + */ | |
| 25 | + private String username; | |
| 26 | + | |
| 27 | + /** | |
| 28 | + * 用户昵称 | |
| 29 | + */ | |
| 30 | + private String nickname; | |
| 31 | + | |
| 32 | + /** | |
| 33 | + * 用户邮箱 | |
| 34 | + */ | |
| 35 | + private String email; | |
| 36 | + /** | |
| 37 | + * 手机号码 | |
| 38 | + */ | |
| 39 | + private String mobile; | |
| 40 | + | |
| 41 | + /** | |
| 42 | + * 用户性别 | |
| 43 | + */ | |
| 44 | + private Integer sex; | |
| 45 | + | |
| 46 | + /** | |
| 47 | + * 用户头像 | |
| 48 | + */ | |
| 49 | + private String avatar; | |
| 50 | + | |
| 51 | + /** | |
| 52 | + * 所在部门 | |
| 53 | + */ | |
| 54 | + private Dept dept; | |
| 55 | + | |
| 56 | + /** | |
| 57 | + * 所属岗位数组 | |
| 58 | + */ | |
| 59 | + private List<Post> posts; | |
| 60 | + | |
| 61 | + /** | |
| 62 | + * 部门 | |
| 63 | + */ | |
| 64 | + @Data | |
| 65 | + public static class Dept { | |
| 66 | + | |
| 67 | + /** | |
| 68 | + * 部门编号 | |
| 69 | + */ | |
| 70 | + private Long id; | |
| 71 | + | |
| 72 | + /** | |
| 73 | + * 部门名称 | |
| 74 | + */ | |
| 75 | + private String name; | |
| 76 | + | |
| 77 | + } | |
| 78 | + | |
| 79 | + /** | |
| 80 | + * 岗位 | |
| 81 | + */ | |
| 82 | + @Data | |
| 83 | + public static class Post { | |
| 84 | + | |
| 85 | + /** | |
| 86 | + * 岗位编号 | |
| 87 | + */ | |
| 88 | + private Long id; | |
| 89 | + | |
| 90 | + /** | |
| 91 | + * 岗位名称 | |
| 92 | + */ | |
| 93 | + private String name; | |
| 94 | + | |
| 95 | + } | |
| 96 | + | |
| 97 | +} | ... | ... |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/controller/UserController.java
| 1 | 1 | package cn.iocoder.yudao.ssodemo.controller; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.ssodemo.client.UserClient; | |
| 4 | +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; | |
| 5 | +import cn.iocoder.yudao.ssodemo.client.dto.user.UserInfoRespDTO; | |
| 3 | 6 | import org.springframework.web.bind.annotation.GetMapping; |
| 4 | 7 | import org.springframework.web.bind.annotation.RequestMapping; |
| 5 | 8 | import org.springframework.web.bind.annotation.RestController; |
| 6 | 9 | |
| 10 | +import javax.annotation.Resource; | |
| 11 | + | |
| 7 | 12 | @RestController |
| 8 | 13 | @RequestMapping("/user") |
| 9 | 14 | public class UserController { |
| 10 | 15 | |
| 16 | + @Resource | |
| 17 | + private UserClient userClient; | |
| 18 | + | |
| 11 | 19 | /** |
| 12 | 20 | * 获得当前登录用户的基本信息 |
| 13 | 21 | * |
| 14 | - * @return TODO | |
| 22 | + * @return 用户信息;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段 | |
| 15 | 23 | */ |
| 16 | 24 | @GetMapping("/get") |
| 17 | - public String getUser() { | |
| 18 | - return ""; | |
| 25 | + public CommonResult<UserInfoRespDTO> getUser() { | |
| 26 | + return userClient.getUser(); | |
| 19 | 27 | } |
| 20 | 28 | |
| 21 | 29 | } | ... | ... |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/LoginUser.java
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/SecurityUtils.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.framework.core; | |
| 2 | + | |
| 3 | +import org.springframework.lang.Nullable; | |
| 4 | +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | |
| 5 | +import org.springframework.security.core.Authentication; | |
| 6 | +import org.springframework.security.core.context.SecurityContext; | |
| 7 | +import org.springframework.security.core.context.SecurityContextHolder; | |
| 8 | +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; | |
| 9 | +import org.springframework.util.StringUtils; | |
| 10 | + | |
| 11 | +import javax.servlet.http.HttpServletRequest; | |
| 12 | +import java.util.Collections; | |
| 13 | + | |
| 14 | +/** | |
| 15 | + * 安全服务工具类 | |
| 16 | + * | |
| 17 | + * @author 芋道源码 | |
| 18 | + */ | |
| 19 | +public class SecurityUtils { | |
| 20 | + | |
| 21 | + public static final String AUTHORIZATION_BEARER = "Bearer"; | |
| 22 | + | |
| 23 | + private SecurityUtils() {} | |
| 24 | + | |
| 25 | + /** | |
| 26 | + * 从请求中,获得认证 Token | |
| 27 | + * | |
| 28 | + * @param request 请求 | |
| 29 | + * @param header 认证 Token 对应的 Header 名字 | |
| 30 | + * @return 认证 Token | |
| 31 | + */ | |
| 32 | + public static String obtainAuthorization(HttpServletRequest request, String header) { | |
| 33 | + String authorization = request.getHeader(header); | |
| 34 | + if (!StringUtils.hasText(authorization)) { | |
| 35 | + return null; | |
| 36 | + } | |
| 37 | + int index = authorization.indexOf(AUTHORIZATION_BEARER + " "); | |
| 38 | + if (index == -1) { // 未找到 | |
| 39 | + return null; | |
| 40 | + } | |
| 41 | + return authorization.substring(index + 7).trim(); | |
| 42 | + } | |
| 43 | + | |
| 44 | + /** | |
| 45 | + * 获得当前认证信息 | |
| 46 | + * | |
| 47 | + * @return 认证信息 | |
| 48 | + */ | |
| 49 | + public static Authentication getAuthentication() { | |
| 50 | + SecurityContext context = SecurityContextHolder.getContext(); | |
| 51 | + if (context == null) { | |
| 52 | + return null; | |
| 53 | + } | |
| 54 | + return context.getAuthentication(); | |
| 55 | + } | |
| 56 | + | |
| 57 | + /** | |
| 58 | + * 获取当前用户 | |
| 59 | + * | |
| 60 | + * @return 当前用户 | |
| 61 | + */ | |
| 62 | + @Nullable | |
| 63 | + public static LoginUser getLoginUser() { | |
| 64 | + Authentication authentication = getAuthentication(); | |
| 65 | + if (authentication == null) { | |
| 66 | + return null; | |
| 67 | + } | |
| 68 | + return authentication.getPrincipal() instanceof LoginUser ? (LoginUser) authentication.getPrincipal() : null; | |
| 69 | + } | |
| 70 | + | |
| 71 | + /** | |
| 72 | + * 获得当前用户的编号,从上下文中 | |
| 73 | + * | |
| 74 | + * @return 用户编号 | |
| 75 | + */ | |
| 76 | + @Nullable | |
| 77 | + public static Long getLoginUserId() { | |
| 78 | + LoginUser loginUser = getLoginUser(); | |
| 79 | + return loginUser != null ? loginUser.getId() : null; | |
| 80 | + } | |
| 81 | + | |
| 82 | + /** | |
| 83 | + * 设置当前用户 | |
| 84 | + * | |
| 85 | + * @param loginUser 登录用户 | |
| 86 | + * @param request 请求 | |
| 87 | + */ | |
| 88 | + public static void setLoginUser(LoginUser loginUser, HttpServletRequest request) { | |
| 89 | + // 创建 Authentication,并设置到上下文 | |
| 90 | + Authentication authentication = buildAuthentication(loginUser, request); | |
| 91 | + SecurityContextHolder.getContext().setAuthentication(authentication); | |
| 92 | + } | |
| 93 | + | |
| 94 | + private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) { | |
| 95 | + // 创建 UsernamePasswordAuthenticationToken 对象 | |
| 96 | + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( | |
| 97 | + loginUser, null, Collections.emptyList()); | |
| 98 | + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); | |
| 99 | + return authenticationToken; | |
| 100 | + } | |
| 101 | + | |
| 102 | +} | ... | ... |
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; |
| 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 org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | |
| 7 | -import org.springframework.security.core.Authentication; | |
| 8 | -import org.springframework.security.core.context.SecurityContextHolder; | |
| 9 | -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; | |
| 10 | 6 | import org.springframework.stereotype.Component; |
| 11 | 7 | import org.springframework.util.StringUtils; |
| 12 | 8 | import org.springframework.web.filter.OncePerRequestFilter; |
| ... | ... | @@ -17,7 +13,6 @@ import javax.servlet.ServletException; |
| 17 | 13 | import javax.servlet.http.HttpServletRequest; |
| 18 | 14 | import javax.servlet.http.HttpServletResponse; |
| 19 | 15 | import java.io.IOException; |
| 20 | -import java.util.Collections; | |
| 21 | 16 | |
| 22 | 17 | /** |
| 23 | 18 | * Token 过滤器,验证 token 的有效性 |
| ... | ... | @@ -35,13 +30,13 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { |
| 35 | 30 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, |
| 36 | 31 | FilterChain filterChain) throws ServletException, IOException { |
| 37 | 32 | // 1. 获得访问令牌 |
| 38 | - String token = obtainAuthorization(request); | |
| 33 | + String token = SecurityUtils.obtainAuthorization(request, "Authentication"); | |
| 39 | 34 | if (StringUtils.hasText(token)) { |
| 40 | 35 | // 2. 基于 token 构建登录用户 |
| 41 | 36 | LoginUser loginUser = buildLoginUserByToken(token); |
| 42 | 37 | // 3. 设置当前用户 |
| 43 | 38 | if (loginUser != null) { |
| 44 | - setLoginUser(loginUser, request); | |
| 39 | + SecurityUtils.setLoginUser(loginUser, request); | |
| 45 | 40 | } |
| 46 | 41 | } |
| 47 | 42 | |
| ... | ... | @@ -58,50 +53,12 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { |
| 58 | 53 | } |
| 59 | 54 | // 构建登录用户 |
| 60 | 55 | return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType()) |
| 61 | - .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes()); | |
| 56 | + .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes()) | |
| 57 | + .setAccessToken(accessToken.getAccessToken()); | |
| 62 | 58 | } catch (Exception exception) { |
| 63 | 59 | // 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可 |
| 64 | 60 | return null; |
| 65 | 61 | } |
| 66 | 62 | } |
| 67 | 63 | |
| 68 | - /** | |
| 69 | - * 从请求 Header 中,获得访问令牌 | |
| 70 | - * | |
| 71 | - * @param request 请求 | |
| 72 | - * @return 访问令牌 | |
| 73 | - */ | |
| 74 | - private static String obtainAuthorization(HttpServletRequest request) { | |
| 75 | - String authorization = request.getHeader("Authentication"); | |
| 76 | - if (!StringUtils.hasText(authorization)) { | |
| 77 | - return null; | |
| 78 | - } | |
| 79 | - int index = authorization.indexOf("Bearer "); | |
| 80 | - if (index == -1) { // 未找到 | |
| 81 | - return null; | |
| 82 | - } | |
| 83 | - return authorization.substring(index + 7).trim(); | |
| 84 | - } | |
| 85 | - | |
| 86 | - /** | |
| 87 | - * 设置当前用户 | |
| 88 | - * | |
| 89 | - * @param loginUser 登录用户 | |
| 90 | - * @param request 请求 | |
| 91 | - */ | |
| 92 | - private static void setLoginUser(LoginUser loginUser, HttpServletRequest request) { | |
| 93 | - // 创建 Authentication,并设置到上下文 | |
| 94 | - Authentication authentication = buildAuthentication(loginUser, request); | |
| 95 | - SecurityContextHolder.getContext().setAuthentication(authentication); | |
| 96 | - } | |
| 97 | - | |
| 98 | - private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) { | |
| 99 | - // 创建 UsernamePasswordAuthenticationToken 对象 | |
| 100 | - UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( | |
| 101 | - loginUser, null, Collections.emptyList()); | |
| 102 | - authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); | |
| 103 | - return authenticationToken; | |
| 104 | - } | |
| 105 | - | |
| 106 | - | |
| 107 | 64 | } | ... | ... |
yudao-example/yudao-sso-demo-by-code/src/main/resources/static/index.html
| ... | ... | @@ -43,7 +43,7 @@ |
| 43 | 43 | alert('获得个人信息失败,原因:' + result.msg) |
| 44 | 44 | return; |
| 45 | 45 | } |
| 46 | - $('nicknameSpan').html(result.data.nickname); | |
| 46 | + $('#nicknameSpan').html(result.data.nickname); | |
| 47 | 47 | } |
| 48 | 48 | }); |
| 49 | 49 | }) |
| ... | ... | @@ -57,8 +57,8 @@ |
| 57 | 57 | |
| 58 | 58 | <!-- 情况二:已登录:1)展示用户信息;2)刷新访问令牌;3)退出登录 --> |
| 59 | 59 | <div id="yesLoginDiv" style="display: none"> |
| 60 | - 您已登录!点击 <a href="#" onclick="ssoLogin()">退出 </a> 系统 <br /> | |
| 61 | - 昵称:<span id="nicknameSpan"> 加载中... </span> <br /> | |
| 60 | + 您已登录!<button>退出登录</button> <br /> | |
| 61 | + 昵称:<span id="nicknameSpan"> 加载中... </span> <button>修改昵称</button> <br /> | |
| 62 | 62 | 访问令牌:<span id="accessTokenSpan"> 加载中... </span> <br /> |
| 63 | 63 | </div> |
| 64 | 64 | </body> | ... | ... |