Commit ab40254bc1591644394e07f34762eef2a598cc53
1 parent
ff54f169
完成 yudao-sso-demo-by-code 退出登录
Showing
4 changed files
with
78 additions
and
1 deletions
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/OAuth2Client.java
| @@ -121,6 +121,32 @@ public class OAuth2Client { | @@ -121,6 +121,32 @@ public class OAuth2Client { | ||
| 121 | return exchange.getBody(); | 121 | return exchange.getBody(); |
| 122 | } | 122 | } |
| 123 | 123 | ||
| 124 | + /** | ||
| 125 | + * 删除访问令牌 | ||
| 126 | + * | ||
| 127 | + * @param token 访问令牌 | ||
| 128 | + * @return 成功 | ||
| 129 | + */ | ||
| 130 | + public CommonResult<Boolean> revokeToken(String token) { | ||
| 131 | + // 1.1 构建请求头 | ||
| 132 | + HttpHeaders headers = new HttpHeaders(); | ||
| 133 | + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); | ||
| 134 | + headers.set("tenant-id", TENANT_ID.toString()); | ||
| 135 | + addClientHeader(headers); | ||
| 136 | + // 1.2 构建请求参数 | ||
| 137 | + MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); | ||
| 138 | + body.add("token", token); | ||
| 139 | + | ||
| 140 | + // 2. 执行请求 | ||
| 141 | + ResponseEntity<CommonResult<Boolean>> exchange = restTemplate.exchange( | ||
| 142 | + BASE_URL + "/token", | ||
| 143 | + HttpMethod.DELETE, | ||
| 144 | + new HttpEntity<>(body, headers), | ||
| 145 | + new ParameterizedTypeReference<CommonResult<Boolean>>() {}); // 解决 CommonResult 的泛型丢失 | ||
| 146 | + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功"); | ||
| 147 | + return exchange.getBody(); | ||
| 148 | + } | ||
| 149 | + | ||
| 124 | private static void addClientHeader(HttpHeaders headers) { | 150 | private static void addClientHeader(HttpHeaders headers) { |
| 125 | // client 拼接,需要 BASE64 编码 | 151 | // client 拼接,需要 BASE64 编码 |
| 126 | String client = CLIENT_ID + ":" + CLIENT_SECRET; | 152 | String client = CLIENT_ID + ":" + CLIENT_SECRET; |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/controller/AuthController.java
| 1 | package cn.iocoder.yudao.ssodemo.controller; | 1 | package cn.iocoder.yudao.ssodemo.controller; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.util.StrUtil; | ||
| 3 | import cn.iocoder.yudao.ssodemo.client.OAuth2Client; | 4 | import cn.iocoder.yudao.ssodemo.client.OAuth2Client; |
| 4 | import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; | 5 | import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; |
| 5 | import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2AccessTokenRespDTO; | 6 | import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2AccessTokenRespDTO; |
| 7 | +import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils; | ||
| 6 | import org.springframework.web.bind.annotation.PostMapping; | 8 | import org.springframework.web.bind.annotation.PostMapping; |
| 7 | import org.springframework.web.bind.annotation.RequestMapping; | 9 | import org.springframework.web.bind.annotation.RequestMapping; |
| 8 | import org.springframework.web.bind.annotation.RequestParam; | 10 | import org.springframework.web.bind.annotation.RequestParam; |
| 9 | import org.springframework.web.bind.annotation.RestController; | 11 | import org.springframework.web.bind.annotation.RestController; |
| 10 | 12 | ||
| 11 | import javax.annotation.Resource; | 13 | import javax.annotation.Resource; |
| 14 | +import javax.servlet.http.HttpServletRequest; | ||
| 12 | 15 | ||
| 13 | @RestController | 16 | @RestController |
| 14 | @RequestMapping("/auth") | 17 | @RequestMapping("/auth") |
| @@ -41,4 +44,20 @@ public class AuthController { | @@ -41,4 +44,20 @@ public class AuthController { | ||
| 41 | return oauth2Client.refreshToken(refreshToken); | 44 | return oauth2Client.refreshToken(refreshToken); |
| 42 | } | 45 | } |
| 43 | 46 | ||
| 47 | + /** | ||
| 48 | + * 退出登录 | ||
| 49 | + * | ||
| 50 | + * @param request 请求 | ||
| 51 | + * @return 成功 | ||
| 52 | + */ | ||
| 53 | + @PostMapping("/logout") | ||
| 54 | + public CommonResult<Boolean> logout(HttpServletRequest request) { | ||
| 55 | + String token = SecurityUtils.obtainAuthorization(request, "Authentication"); | ||
| 56 | + if (StrUtil.isNotBlank(token)) { | ||
| 57 | + return oauth2Client.revokeToken(token); | ||
| 58 | + } | ||
| 59 | + // 返回成功 | ||
| 60 | + return new CommonResult<>(); | ||
| 61 | + } | ||
| 62 | + | ||
| 44 | } | 63 | } |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/config/SecurityConfiguration.java
| @@ -31,6 +31,8 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { | @@ -31,6 +31,8 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { | ||
| 31 | .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll() | 31 | .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll() |
| 32 | // 2. 登录相关的接口,可匿名访问 | 32 | // 2. 登录相关的接口,可匿名访问 |
| 33 | .antMatchers("/auth/login-by-code").permitAll() | 33 | .antMatchers("/auth/login-by-code").permitAll() |
| 34 | + .antMatchers("/auth/refresh-token").permitAll() | ||
| 35 | + .antMatchers("/auth/logout").permitAll() | ||
| 34 | // last. 兜底规则,必须认证 | 36 | // last. 兜底规则,必须认证 |
| 35 | .and().authorizeRequests() | 37 | .and().authorizeRequests() |
| 36 | .anyRequest().authenticated(); | 38 | .anyRequest().authenticated(); |
yudao-example/yudao-sso-demo-by-code/src/main/resources/static/index.html
| @@ -74,6 +74,36 @@ | @@ -74,6 +74,36 @@ | ||
| 74 | }); | 74 | }); |
| 75 | } | 75 | } |
| 76 | 76 | ||
| 77 | + /** | ||
| 78 | + * 刷新令牌 | ||
| 79 | + */ | ||
| 80 | + function logout() { | ||
| 81 | + const accessToken = localStorage.getItem('ACCESS-TOKEN'); | ||
| 82 | + if (!accessToken) { | ||
| 83 | + location.reload(); | ||
| 84 | + return; | ||
| 85 | + } | ||
| 86 | + $.ajax({ | ||
| 87 | + url: "http://127.0.0.1:18080/auth/logout", | ||
| 88 | + method: 'POST', | ||
| 89 | + headers: { | ||
| 90 | + 'Authentication': 'Bearer ' + accessToken | ||
| 91 | + }, | ||
| 92 | + success: function (result) { | ||
| 93 | + if (result.code !== 0) { | ||
| 94 | + alert('退出登录失败,原因:' + result.msg) | ||
| 95 | + return; | ||
| 96 | + } | ||
| 97 | + alert('退出登录成功!'); | ||
| 98 | + // 删除 localStorage 中 | ||
| 99 | + localStorage.removeItem('ACCESS-TOKEN'); | ||
| 100 | + localStorage.removeItem('REFRESH-TOKEN'); | ||
| 101 | + | ||
| 102 | + location.reload(); | ||
| 103 | + } | ||
| 104 | + }); | ||
| 105 | + } | ||
| 106 | + | ||
| 77 | $(function () { | 107 | $(function () { |
| 78 | const accessToken = localStorage.getItem('ACCESS-TOKEN'); | 108 | const accessToken = localStorage.getItem('ACCESS-TOKEN'); |
| 79 | // 情况一:未登录 | 109 | // 情况一:未登录 |
| @@ -111,7 +141,7 @@ | @@ -111,7 +141,7 @@ | ||
| 111 | 141 | ||
| 112 | <!-- 情况二:已登录:1)展示用户信息;2)刷新访问令牌;3)退出登录 --> | 142 | <!-- 情况二:已登录:1)展示用户信息;2)刷新访问令牌;3)退出登录 --> |
| 113 | <div id="yesLoginDiv" style="display: none"> | 143 | <div id="yesLoginDiv" style="display: none"> |
| 114 | - 您已登录!<button>退出登录</button> <br /> | 144 | + 您已登录!<button onclick="logout()">退出登录</button> <br /> |
| 115 | 昵称:<span id="nicknameSpan"> 加载中... </span> <button onclick="updateNickname()">修改昵称</button> <br /> | 145 | 昵称:<span id="nicknameSpan"> 加载中... </span> <button onclick="updateNickname()">修改昵称</button> <br /> |
| 116 | 访问令牌:<span id="accessTokenSpan"> 加载中... </span> <button onclick="refreshToken()">刷新令牌</button> <br /> | 146 | 访问令牌:<span id="accessTokenSpan"> 加载中... </span> <button onclick="refreshToken()">刷新令牌</button> <br /> |
| 117 | </div> | 147 | </div> |