初始化提交
This commit is contained in:
		| @ -0,0 +1,13 @@ | ||||
| package cn.iocoder.yudao.ssodemo; | ||||
|  | ||||
| import org.springframework.boot.SpringApplication; | ||||
| import org.springframework.boot.autoconfigure.SpringBootApplication; | ||||
|  | ||||
| @SpringBootApplication | ||||
| public class SSODemoApplication { | ||||
|  | ||||
|     public static void main(String[] args) { | ||||
|         SpringApplication.run(SSODemoApplication.class, args); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,157 @@ | ||||
| package cn.iocoder.yudao.ssodemo.client; | ||||
|  | ||||
| import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; | ||||
| import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2AccessTokenRespDTO; | ||||
| import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2CheckTokenRespDTO; | ||||
| import org.springframework.core.ParameterizedTypeReference; | ||||
| import org.springframework.http.*; | ||||
| import org.springframework.stereotype.Component; | ||||
| import org.springframework.util.Assert; | ||||
| import org.springframework.util.Base64Utils; | ||||
| import org.springframework.util.LinkedMultiValueMap; | ||||
| import org.springframework.util.MultiValueMap; | ||||
| import org.springframework.web.client.RestTemplate; | ||||
|  | ||||
| import java.nio.charset.StandardCharsets; | ||||
|  | ||||
| /** | ||||
|  * OAuth 2.0 客户端 | ||||
|  * | ||||
|  * 对应调用 OAuth2OpenController 接口 | ||||
|  */ | ||||
| @Component | ||||
| public class OAuth2Client { | ||||
|  | ||||
|     private static final String BASE_URL = "http://127.0.0.1:48080/admin-api/system/oauth2"; | ||||
|  | ||||
|     /** | ||||
|      * 租户编号 | ||||
|      * | ||||
|      * 默认使用 1;如果使用别的租户,可以调整 | ||||
|      */ | ||||
|     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"; | ||||
|  | ||||
|  | ||||
| //    @Resource // 可优化,注册一个 RestTemplate Bean,然后注入 | ||||
|     private final RestTemplate restTemplate = new RestTemplate(); | ||||
|  | ||||
|     /** | ||||
|      * 使用 code 授权码,获得访问令牌 | ||||
|      * | ||||
|      * @param code        授权码 | ||||
|      * @param redirectUri 重定向 URI | ||||
|      * @return 访问令牌 | ||||
|      */ | ||||
|     public CommonResult<OAuth2AccessTokenRespDTO> postAccessToken(String code, String redirectUri) { | ||||
|         // 1.1 构建请求头 | ||||
|         HttpHeaders headers = new HttpHeaders(); | ||||
|         headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); | ||||
|         headers.set("tenant-id", TENANT_ID.toString()); | ||||
|         addClientHeader(headers); | ||||
|         // 1.2 构建请求参数 | ||||
|         MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); | ||||
|         body.add("grant_type", "authorization_code"); | ||||
|         body.add("code", code); | ||||
|         body.add("redirect_uri", redirectUri); | ||||
| //        body.add("state", ""); // 选填;填了会校验 | ||||
|  | ||||
|         // 2. 执行请求 | ||||
|         ResponseEntity<CommonResult<OAuth2AccessTokenRespDTO>> exchange = restTemplate.exchange( | ||||
|                 BASE_URL + "/token", | ||||
|                 HttpMethod.POST, | ||||
|                 new HttpEntity<>(body, headers), | ||||
|                 new ParameterizedTypeReference<CommonResult<OAuth2AccessTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失 | ||||
|         Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功"); | ||||
|         return exchange.getBody(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 校验访问令牌,并返回它的基本信息 | ||||
|      * | ||||
|      * @param token 访问令牌 | ||||
|      * @return 访问令牌的基本信息 | ||||
|      */ | ||||
|     public CommonResult<OAuth2CheckTokenRespDTO> checkToken(String token) { | ||||
|         // 1.1 构建请求头 | ||||
|         HttpHeaders headers = new HttpHeaders(); | ||||
|         headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); | ||||
|         headers.set("tenant-id", TENANT_ID.toString()); | ||||
|         addClientHeader(headers); | ||||
|         // 1.2 构建请求参数 | ||||
|         MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); | ||||
|         body.add("token", token); | ||||
|  | ||||
|         // 2. 执行请求 | ||||
|         ResponseEntity<CommonResult<OAuth2CheckTokenRespDTO>> exchange = restTemplate.exchange( | ||||
|                 BASE_URL + "/check-token", | ||||
|                 HttpMethod.POST, | ||||
|                 new HttpEntity<>(body, headers), | ||||
|                 new ParameterizedTypeReference<CommonResult<OAuth2CheckTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失 | ||||
|         Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功"); | ||||
|         return exchange.getBody(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 使用刷新令牌,获得(刷新)访问令牌 | ||||
|      * | ||||
|      * @param refreshToken 刷新令牌 | ||||
|      * @return 访问令牌 | ||||
|      */ | ||||
|     public CommonResult<OAuth2AccessTokenRespDTO> refreshToken(String refreshToken) { | ||||
|         // 1.1 构建请求头 | ||||
|         HttpHeaders headers = new HttpHeaders(); | ||||
|         headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); | ||||
|         headers.set("tenant-id", TENANT_ID.toString()); | ||||
|         addClientHeader(headers); | ||||
|         // 1.2 构建请求参数 | ||||
|         MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); | ||||
|         body.add("grant_type", "refresh_token"); | ||||
|         body.add("refresh_token", refreshToken); | ||||
|  | ||||
|         // 2. 执行请求 | ||||
|         ResponseEntity<CommonResult<OAuth2AccessTokenRespDTO>> exchange = restTemplate.exchange( | ||||
|                 BASE_URL + "/token", | ||||
|                 HttpMethod.POST, | ||||
|                 new HttpEntity<>(body, headers), | ||||
|                 new ParameterizedTypeReference<CommonResult<OAuth2AccessTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失 | ||||
|         Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功"); | ||||
|         return exchange.getBody(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 删除访问令牌 | ||||
|      * | ||||
|      * @param token 访问令牌 | ||||
|      * @return 成功 | ||||
|      */ | ||||
|     public CommonResult<Boolean> revokeToken(String token) { | ||||
|         // 1.1 构建请求头 | ||||
|         HttpHeaders headers = new HttpHeaders(); | ||||
|         headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); | ||||
|         headers.set("tenant-id", TENANT_ID.toString()); | ||||
|         addClientHeader(headers); | ||||
|         // 1.2 构建请求参数 | ||||
|         MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); | ||||
|         body.add("token", token); | ||||
|  | ||||
|         // 2. 执行请求 | ||||
|         ResponseEntity<CommonResult<Boolean>> exchange = restTemplate.exchange( | ||||
|                 BASE_URL + "/token", | ||||
|                 HttpMethod.DELETE, | ||||
|                 new HttpEntity<>(body, headers), | ||||
|                 new ParameterizedTypeReference<CommonResult<Boolean>>() {}); // 解决 CommonResult 的泛型丢失 | ||||
|         Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功"); | ||||
|         return exchange.getBody(); | ||||
|     } | ||||
|  | ||||
|     private static void addClientHeader(HttpHeaders headers) { | ||||
|         // client 拼接,需要 BASE64 编码 | ||||
|         String client = CLIENT_ID + ":" + CLIENT_SECRET; | ||||
|         client = Base64Utils.encodeToString(client.getBytes(StandardCharsets.UTF_8)); | ||||
|         headers.add("Authorization", "Basic " + client); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,73 @@ | ||||
| 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.util.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; | ||||
|  | ||||
| /** | ||||
|  * 用户 User 信息的客户端 | ||||
|  * | ||||
|  * 对应调用 OAuth2UserController 接口 | ||||
|  */ | ||||
| @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<UserInfoRespDTO> 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<String, Object> body = new LinkedMultiValueMap<>(); | ||||
|  | ||||
|         // 2. 执行请求 | ||||
|         ResponseEntity<CommonResult<UserInfoRespDTO>> exchange = restTemplate.exchange( | ||||
|                 BASE_URL + "/get", | ||||
|                 HttpMethod.GET, | ||||
|                 new HttpEntity<>(body, headers), | ||||
|                 new ParameterizedTypeReference<CommonResult<UserInfoRespDTO>>() {}); // 解决 CommonResult 的泛型丢失 | ||||
|         Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功"); | ||||
|         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, "登录用户不能为空"); | ||||
|         headers.add("Authorization", "Bearer " + loginUser.getAccessToken()); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,28 @@ | ||||
| package cn.iocoder.yudao.ssodemo.client.dto; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.io.Serializable; | ||||
|  | ||||
| /** | ||||
|  * 通用返回 | ||||
|  * | ||||
|  * @param <T> 数据泛型 | ||||
|  */ | ||||
| @Data | ||||
| public class CommonResult<T> implements Serializable { | ||||
|  | ||||
|     /** | ||||
|      * 错误码 | ||||
|      */ | ||||
|     private Integer code; | ||||
|     /** | ||||
|      * 返回数据 | ||||
|      */ | ||||
|     private T data; | ||||
|     /** | ||||
|      * 错误提示,用户可阅读 | ||||
|      */ | ||||
|     private String msg; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,45 @@ | ||||
| package cn.iocoder.yudao.ssodemo.client.dto.oauth2; | ||||
|  | ||||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| /** | ||||
|  * 访问令牌 Response DTO | ||||
|  */ | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| @AllArgsConstructor | ||||
| public class OAuth2AccessTokenRespDTO { | ||||
|  | ||||
|     /** | ||||
|      * 访问令牌 | ||||
|      */ | ||||
|     @JsonProperty("access_token") | ||||
|     private String accessToken; | ||||
|  | ||||
|     /** | ||||
|      * 刷新令牌 | ||||
|      */ | ||||
|     @JsonProperty("refresh_token") | ||||
|     private String refreshToken; | ||||
|  | ||||
|     /** | ||||
|      * 令牌类型 | ||||
|      */ | ||||
|     @JsonProperty("token_type") | ||||
|     private String tokenType; | ||||
|  | ||||
|     /** | ||||
|      * 过期时间;单位:秒 | ||||
|      */ | ||||
|     @JsonProperty("expires_in") | ||||
|     private Long expiresIn; | ||||
|  | ||||
|     /** | ||||
|      * 授权范围;如果多个授权范围,使用空格分隔 | ||||
|      */ | ||||
|     private String scope; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,59 @@ | ||||
| package cn.iocoder.yudao.ssodemo.client.dto.oauth2; | ||||
|  | ||||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * 校验令牌 Response DTO | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| @AllArgsConstructor | ||||
| public class OAuth2CheckTokenRespDTO { | ||||
|  | ||||
|     /** | ||||
|      * 用户编号 | ||||
|      */ | ||||
|     @JsonProperty("user_id") | ||||
|     private Long userId; | ||||
|     /** | ||||
|      * 用户类型 | ||||
|      */ | ||||
|     @JsonProperty("user_type") | ||||
|     private Integer userType; | ||||
|     /** | ||||
|      * 租户编号 | ||||
|      */ | ||||
|     @JsonProperty("tenant_id") | ||||
|     private Long tenantId; | ||||
|  | ||||
|     /** | ||||
|      * 客户端编号 | ||||
|      */ | ||||
|     @JsonProperty("client_id") | ||||
|     private String clientId; | ||||
|     /** | ||||
|      * 授权范围 | ||||
|      */ | ||||
|     private List<String> scopes; | ||||
|  | ||||
|     /** | ||||
|      * 访问令牌 | ||||
|      */ | ||||
|     @JsonProperty("access_token") | ||||
|     private String accessToken; | ||||
|  | ||||
|     /** | ||||
|      * 过期时间 | ||||
|      * | ||||
|      * 时间戳 / 1000,即单位:秒 | ||||
|      */ | ||||
|     private Long exp; | ||||
|  | ||||
| } | ||||
| @ -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<Post> posts; | ||||
|  | ||||
|     /** | ||||
|      * 部门 | ||||
|      */ | ||||
|     @Data | ||||
|     public static class Dept { | ||||
|  | ||||
|         /** | ||||
|          * 部门编号 | ||||
|          */ | ||||
|         private Long id; | ||||
|  | ||||
|         /** | ||||
|          * 部门名称 | ||||
|          */ | ||||
|         private String name; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 岗位 | ||||
|      */ | ||||
|     @Data | ||||
|     public static class Post { | ||||
|  | ||||
|         /** | ||||
|          * 岗位编号 | ||||
|          */ | ||||
|         private Long id; | ||||
|  | ||||
|         /** | ||||
|          * 岗位名称 | ||||
|          */ | ||||
|         private String name; | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -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; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,63 @@ | ||||
| package cn.iocoder.yudao.ssodemo.controller; | ||||
|  | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import cn.iocoder.yudao.ssodemo.client.OAuth2Client; | ||||
| import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; | ||||
| import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2AccessTokenRespDTO; | ||||
| import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils; | ||||
| import org.springframework.web.bind.annotation.PostMapping; | ||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||
| import org.springframework.web.bind.annotation.RequestParam; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
|  | ||||
| import javax.annotation.Resource; | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
|  | ||||
| @RestController | ||||
| @RequestMapping("/auth") | ||||
| public class AuthController { | ||||
|  | ||||
|     @Resource | ||||
|     private OAuth2Client oauth2Client; | ||||
|  | ||||
|     /** | ||||
|      * 使用 code 访问令牌,获得访问令牌 | ||||
|      * | ||||
|      * @param code 授权码 | ||||
|      * @param redirectUri 重定向 URI | ||||
|      * @return 访问令牌;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段 | ||||
|      */ | ||||
|     @PostMapping("/login-by-code") | ||||
|     public CommonResult<OAuth2AccessTokenRespDTO> loginByCode(@RequestParam("code") String code, | ||||
|                                                               @RequestParam("redirectUri") String redirectUri) { | ||||
|         return oauth2Client.postAccessToken(code, redirectUri); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 使用刷新令牌,获得(刷新)访问令牌 | ||||
|      * | ||||
|      * @param refreshToken 刷新令牌 | ||||
|      * @return 访问令牌;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段 | ||||
|      */ | ||||
|     @PostMapping("/refresh-token") | ||||
|     public CommonResult<OAuth2AccessTokenRespDTO> refreshToken(@RequestParam("refreshToken") String refreshToken) { | ||||
|         return oauth2Client.refreshToken(refreshToken); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 退出登录 | ||||
|      * | ||||
|      * @param request 请求 | ||||
|      * @return 成功 | ||||
|      */ | ||||
|     @PostMapping("/logout") | ||||
|     public CommonResult<Boolean> logout(HttpServletRequest request) { | ||||
|         String token = SecurityUtils.obtainAuthorization(request, "Authorization"); | ||||
|         if (StrUtil.isNotBlank(token)) { | ||||
|             return oauth2Client.revokeToken(token); | ||||
|         } | ||||
|         // 返回成功 | ||||
|         return new CommonResult<>(); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,40 @@ | ||||
| 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 cn.iocoder.yudao.ssodemo.client.dto.user.UserUpdateReqDTO; | ||||
| import org.springframework.web.bind.annotation.*; | ||||
|  | ||||
| import javax.annotation.Resource; | ||||
|  | ||||
| @RestController | ||||
| @RequestMapping("/user") | ||||
| public class UserController { | ||||
|  | ||||
|     @Resource | ||||
|     private UserClient userClient; | ||||
|  | ||||
|     /** | ||||
|      * 获得当前登录用户的基本信息 | ||||
|      * | ||||
|      * @return 用户信息;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段 | ||||
|      */ | ||||
|     @GetMapping("/get") | ||||
|     public CommonResult<UserInfoRespDTO> 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); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,52 @@ | ||||
| package cn.iocoder.yudao.ssodemo.framework.config; | ||||
|  | ||||
| import cn.iocoder.yudao.ssodemo.framework.core.filter.TokenAuthenticationFilter; | ||||
| import cn.iocoder.yudao.ssodemo.framework.core.handler.AccessDeniedHandlerImpl; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| 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.EnableWebSecurity; | ||||
| import org.springframework.security.web.AuthenticationEntryPoint; | ||||
| import org.springframework.security.web.SecurityFilterChain; | ||||
| import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; | ||||
|  | ||||
| import javax.annotation.Resource; | ||||
|  | ||||
| @Configuration(proxyBeanMethods = false) | ||||
| @EnableWebSecurity | ||||
| public class SecurityConfiguration{ | ||||
|  | ||||
|     @Resource | ||||
|     private TokenAuthenticationFilter tokenAuthenticationFilter; | ||||
|  | ||||
|     @Resource | ||||
|     private AccessDeniedHandlerImpl accessDeniedHandler; | ||||
|     @Resource | ||||
|     private AuthenticationEntryPoint authenticationEntryPoint; | ||||
|  | ||||
|     @Bean | ||||
|     protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { | ||||
|         // 设置 URL 安全权限 | ||||
|         httpSecurity.csrf().disable() // 禁用 CSRF 保护 | ||||
|                 .authorizeRequests() | ||||
|                 // 1. 静态资源,可匿名访问 | ||||
|                 .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll() | ||||
|                 // 2. 登录相关的接口,可匿名访问 | ||||
|                 .antMatchers("/auth/login-by-code").permitAll() | ||||
|                 .antMatchers("/auth/refresh-token").permitAll() | ||||
|                 .antMatchers("/auth/logout").permitAll() | ||||
|                 // last. 兜底规则,必须认证 | ||||
|                 .and().authorizeRequests() | ||||
|                 .anyRequest().authenticated(); | ||||
|  | ||||
|         // 设置处理器 | ||||
|         httpSecurity.exceptionHandling().accessDeniedHandler(accessDeniedHandler) | ||||
|                 .authenticationEntryPoint(authenticationEntryPoint); | ||||
|  | ||||
|         // 添加 Token Filter | ||||
|         httpSecurity.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); | ||||
|         return httpSecurity.build(); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,37 @@ | ||||
| package cn.iocoder.yudao.ssodemo.framework.core; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * 登录用户信息 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Data | ||||
| public class LoginUser { | ||||
|  | ||||
|     /** | ||||
|      * 用户编号 | ||||
|      */ | ||||
|     private Long id; | ||||
|     /** | ||||
|      * 用户类型 | ||||
|      */ | ||||
|     private Integer userType; | ||||
|     /** | ||||
|      * 租户编号 | ||||
|      */ | ||||
|     private Long tenantId; | ||||
|     /** | ||||
|      * 授权范围 | ||||
|      */ | ||||
|     private List<String> scopes; | ||||
|  | ||||
|     /** | ||||
|      * 访问令牌 | ||||
|      */ | ||||
|     private String accessToken; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,66 @@ | ||||
| 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; | ||||
|  | ||||
| import javax.annotation.Resource; | ||||
| import javax.servlet.FilterChain; | ||||
| import javax.servlet.ServletException; | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| import java.io.IOException; | ||||
|  | ||||
| /** | ||||
|  * Token 过滤器,验证 token 的有效性 | ||||
|  * 验证通过后,获得 {@link LoginUser} 信息,并加入到 Spring Security 上下文 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Component | ||||
| public class TokenAuthenticationFilter extends OncePerRequestFilter { | ||||
|  | ||||
|     @Resource | ||||
|     private OAuth2Client oauth2Client; | ||||
|  | ||||
|     @Override | ||||
|     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, | ||||
|                                     FilterChain filterChain) throws ServletException, IOException { | ||||
|         // 1. 获得访问令牌 | ||||
|         String token = SecurityUtils.obtainAuthorization(request, "Authorization"); | ||||
|         if (StringUtils.hasText(token)) { | ||||
|             // 2. 基于 token 构建登录用户 | ||||
|             LoginUser loginUser = buildLoginUserByToken(token); | ||||
|             // 3. 设置当前用户 | ||||
|             if (loginUser != null) { | ||||
|                 SecurityUtils.setLoginUser(loginUser, request); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // 继续过滤链 | ||||
|         filterChain.doFilter(request, response); | ||||
|     } | ||||
|  | ||||
|     private LoginUser buildLoginUserByToken(String token) { | ||||
|         try { | ||||
|             CommonResult<OAuth2CheckTokenRespDTO> accessTokenResult = oauth2Client.checkToken(token); | ||||
|             OAuth2CheckTokenRespDTO accessToken = accessTokenResult.getData(); | ||||
|             if (accessToken == null) { | ||||
|                 return null; | ||||
|             } | ||||
|             // 构建登录用户 | ||||
|             return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType()) | ||||
|                     .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes()) | ||||
|                     .setAccessToken(accessToken.getAccessToken()); | ||||
|         } catch (Exception exception) { | ||||
|             // 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可 | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -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); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,103 @@ | ||||
| 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; | ||||
| 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; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,32 @@ | ||||
| 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); | ||||
|     } | ||||
|  | ||||
|     public static void write(HttpServletResponse response, String text, String contentType) { | ||||
|         ServletUtil.write(response, text, contentType); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,2 @@ | ||||
| server: | ||||
|   port: 18080 | ||||
| @ -0,0 +1,61 @@ | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
| <head> | ||||
| 	<meta charset="UTF-8"> | ||||
| 	<title>SSO 授权后的回调页</title> | ||||
| 	<!-- jQuery:操作 dom、发起请求等 --> | ||||
| 	<script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/2.1.2/jquery.min.js" type="application/javascript"></script> | ||||
| 	<!-- 工具类 --> | ||||
| 	<script type="application/javascript"> | ||||
|     (function ($) { | ||||
|       /** | ||||
|        * 获得 URL 的指定参数的值 | ||||
|        * | ||||
|        * @param name 参数名 | ||||
|        * @returns 参数值 | ||||
|        */ | ||||
|       $.getUrlParam = function (name) { | ||||
|         const reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); | ||||
|         const r = window.location.search.substr(1).match(reg); | ||||
|         if (r != null) return unescape(r[2]); return null; | ||||
|       } | ||||
|     })(jQuery); | ||||
| 	</script> | ||||
|  | ||||
| 	<script type="application/javascript"> | ||||
|     $(function () { | ||||
|       // 获得 code 授权码 | ||||
|       const code = $.getUrlParam('code'); | ||||
|       if (!code) { | ||||
|         alert('获取不到 code 参数,请排查!') | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       // 提交 | ||||
|       const redirectUri = 'http://127.0.0.1:18080/callback.html'; // 需要修改成,你回调的地址,就是在 index.html 拼接的 redirectUri | ||||
|       $.ajax({ | ||||
|         url:  "http://127.0.0.1:18080/auth/login-by-code?code=" + code | ||||
|           + '&redirectUri=' + redirectUri, | ||||
|         method: 'POST', | ||||
|         success: function( result ) { | ||||
|           if (result.code !== 0) { | ||||
|             alert('获得访问令牌失败,原因:' + result.msg) | ||||
|             return; | ||||
|           } | ||||
|           alert('获得访问令牌成功!点击确认,跳转回首页') | ||||
|  | ||||
|           // 设置到 localStorage 中 | ||||
|           localStorage.setItem('ACCESS-TOKEN', result.data.access_token); | ||||
|           localStorage.setItem('REFRESH-TOKEN', result.data.refresh_token); | ||||
|  | ||||
|           // 跳转回首页 | ||||
|           window.location.href = '/index.html'; | ||||
|         } | ||||
|       }) | ||||
|     }) | ||||
| 	</script> | ||||
| </head> | ||||
| <body> | ||||
| 正在使用 code 授权码,进行 accessToken 访问令牌的获取 | ||||
| </body> | ||||
| </html> | ||||
| @ -0,0 +1,159 @@ | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
| <head> | ||||
| 	<meta charset="UTF-8"> | ||||
| 	<title>首页</title> | ||||
| 	<!-- jQuery:操作 dom、发起请求等 --> | ||||
| 	<script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/2.1.2/jquery.min.js" type="application/javascript"></script> | ||||
|  | ||||
| 	<script type="application/javascript"> | ||||
|  | ||||
|     /** | ||||
|      * 跳转单点登录 | ||||
|      */ | ||||
|     function ssoLogin() { | ||||
|       const clientId = 'yudao-sso-demo-by-code'; // 可以改写成,你的 clientId | ||||
|       const redirectUri = encodeURIComponent('http://127.0.0.1:18080/callback.html'); // 注意,需要使用 encodeURIComponent 编码地址 | ||||
|       const responseType = 'code'; // 1)授权码模式,对应 code;2)简化模式,对应 token | ||||
|       window.location.href = 'http://127.0.0.1:1024/sso?client_id=' + clientId | ||||
|         + '&redirect_uri=' + redirectUri | ||||
|         + '&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: { | ||||
|           'Authorization': 'Bearer ' + accessToken | ||||
|         }, | ||||
|         success: function (result) { | ||||
|           if (result.code !== 0) { | ||||
|             alert('更新昵称失败,原因:' + result.msg) | ||||
|             return; | ||||
|           } | ||||
|           alert('更新昵称成功!'); | ||||
|           $('#nicknameSpan').html(nickname); | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 刷新令牌 | ||||
|      */ | ||||
|     function refreshToken() { | ||||
|       const refreshToken = localStorage.getItem('REFRESH-TOKEN'); | ||||
|       if (!refreshToken) { | ||||
|         alert("获取不到刷新令牌"); | ||||
|         return; | ||||
|       } | ||||
|       $.ajax({ | ||||
|         url: "http://127.0.0.1:18080/auth/refresh-token?refreshToken=" + refreshToken, | ||||
|         method: 'POST', | ||||
|         success: function (result) { | ||||
|           if (result.code !== 0) { | ||||
|             alert('刷新访问令牌失败,原因:' + result.msg) | ||||
|             return; | ||||
|           } | ||||
|           alert('更新访问令牌成功!'); | ||||
|           $('#accessTokenSpan').html(result.data.access_token); | ||||
|  | ||||
|           // 设置到 localStorage 中 | ||||
|           localStorage.setItem('ACCESS-TOKEN', result.data.access_token); | ||||
|           localStorage.setItem('REFRESH-TOKEN', result.data.refresh_token); | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 登出,删除访问令牌 | ||||
|      */ | ||||
|     function logout() { | ||||
|       const accessToken = localStorage.getItem('ACCESS-TOKEN'); | ||||
|       if (!accessToken) { | ||||
|         location.reload(); | ||||
|         return; | ||||
|       } | ||||
|       $.ajax({ | ||||
|         url: "http://127.0.0.1:18080/auth/logout", | ||||
|         method: 'POST', | ||||
|         headers: { | ||||
|           'Authorization': 'Bearer ' + accessToken | ||||
|         }, | ||||
|         success: function (result) { | ||||
|           if (result.code !== 0) { | ||||
|             alert('退出登录失败,原因:' + result.msg) | ||||
|             return; | ||||
|           } | ||||
|           alert('退出登录成功!'); | ||||
|           // 删除 localStorage 中 | ||||
|           localStorage.removeItem('ACCESS-TOKEN'); | ||||
|           localStorage.removeItem('REFRESH-TOKEN'); | ||||
|  | ||||
|           location.reload(); | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     $(function () { | ||||
|       const accessToken = localStorage.getItem('ACCESS-TOKEN'); | ||||
|       // 情况一:未登录 | ||||
|       if (!accessToken) { | ||||
|         $('#noLoginDiv').css("display", "block"); | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       // 情况二:已登录 | ||||
|       $('#yesLoginDiv').css("display", "block"); | ||||
|       $('#accessTokenSpan').html(accessToken); | ||||
|       // 获得登录用户的信息 | ||||
|       $.ajax({ | ||||
|         url: "http://127.0.0.1:18080/user/get", | ||||
|         method: 'GET', | ||||
|         headers: { | ||||
|           'Authorization': 'Bearer ' + accessToken | ||||
|         }, | ||||
|         success: function (result) { | ||||
|           if (result.code !== 0) { | ||||
|             alert('获得个人信息失败,原因:' + result.msg) | ||||
|             return; | ||||
|           } | ||||
|           $('#nicknameSpan').html(result.data.nickname); | ||||
|         } | ||||
|       }); | ||||
|     }) | ||||
| 	</script> | ||||
| </head> | ||||
| <body> | ||||
| <!-- 情况一:未登录:1)跳转 ruoyi-vue-pro 的 SSO 登录页 --> | ||||
| <div id="noLoginDiv" style="display: none"> | ||||
| 	您未登录,点击 <a href="#" onclick="ssoLogin()">跳转 </a> SSO 单点登录 | ||||
| </div> | ||||
|  | ||||
| <!-- 情况二:已登录:1)展示用户信息;2)刷新访问令牌;3)退出登录 --> | ||||
| <div id="yesLoginDiv" style="display: none"> | ||||
| 	您已登录!<button onclick="logout()">退出登录</button> <br /> | ||||
| 	昵称:<span id="nicknameSpan"> 加载中... </span> <button onclick="updateNickname()">修改昵称</button> <br /> | ||||
| 	访问令牌:<span id="accessTokenSpan"> 加载中... </span> <button onclick="refreshToken()">刷新令牌</button> <br /> | ||||
| </div> | ||||
| </body> | ||||
| <style> | ||||
|     body { /** 页面居中 */ | ||||
|         border-radius: 20px; | ||||
|         height: 350px; | ||||
|         position: absolute; | ||||
|         left: 50%; | ||||
|         top: 50%; | ||||
|         transform: translate(-50%,-50%); | ||||
|     } | ||||
| </style> | ||||
| </html> | ||||
		Reference in New Issue
	
	Block a user
	 YangJ
					YangJ