Commit 6a61db85082d36ffd5d3a26cc0937de11a5b41c1

Authored by YunaiV
1 parent 41c324ac

security: query string 支持传递 token 参数,解决 ws token 认证

yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/SecurityProperties.java
@@ -19,6 +19,13 @@ public class SecurityProperties { @@ -19,6 +19,13 @@ public class SecurityProperties {
19 */ 19 */
20 @NotEmpty(message = "Token Header 不能为空") 20 @NotEmpty(message = "Token Header 不能为空")
21 private String tokenHeader = "Authorization"; 21 private String tokenHeader = "Authorization";
  22 + /**
  23 + * HTTP 请求时,访问令牌的请求参数
  24 + *
  25 + * 初始目的:解决 WebSocket 无法通过 header 传参,只能通过 token 参数拼接
  26 + */
  27 + @NotEmpty(message = "Token Parameter 不能为空")
  28 + private String tokenParameter = "token";
22 29
23 /** 30 /**
24 * mock 模式的开关 31 * mock 模式的开关
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java
@@ -129,8 +129,6 @@ public class YudaoWebSecurityConfigurerAdapter { @@ -129,8 +129,6 @@ public class YudaoWebSecurityConfigurerAdapter {
129 .antMatchers(buildAppApi("/**")).permitAll() 129 .antMatchers(buildAppApi("/**")).permitAll()
130 // 1.5 验证码captcha 允许匿名访问 130 // 1.5 验证码captcha 允许匿名访问
131 .antMatchers("/captcha/get", "/captcha/check").permitAll() 131 .antMatchers("/captcha/get", "/captcha/check").permitAll()
132 - // 1.6 webSocket 允许匿名访问  
133 - .antMatchers("/websocket/message").permitAll()  
134 // ②:每个项目的自定义规则 132 // ②:每个项目的自定义规则
135 .and().authorizeRequests(registry -> // 下面,循环设置自定义规则 133 .and().authorizeRequests(registry -> // 下面,循环设置自定义规则
136 authorizeRequestsCustomizers.forEach(customizer -> customizer.customize(registry))) 134 authorizeRequestsCustomizers.forEach(customizer -> customizer.customize(registry)))
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java
@@ -41,7 +41,8 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { @@ -41,7 +41,8 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
41 @SuppressWarnings("NullableProblems") 41 @SuppressWarnings("NullableProblems")
42 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) 42 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
43 throws ServletException, IOException { 43 throws ServletException, IOException {
44 - String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader()); 44 + String token = SecurityFrameworkUtils.obtainAuthorization(request,
  45 + securityProperties.getTokenHeader(), securityProperties.getTokenParameter());
45 if (StrUtil.isNotEmpty(token)) { 46 if (StrUtil.isNotEmpty(token)) {
46 Integer userType = WebFrameworkUtils.getLoginUserType(request); 47 Integer userType = WebFrameworkUtils.getLoginUserType(request);
47 try { 48 try {
@@ -74,7 +75,10 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { @@ -74,7 +75,10 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
74 return null; 75 return null;
75 } 76 }
76 // 用户类型不匹配,无权限 77 // 用户类型不匹配,无权限
77 - if (ObjectUtil.notEqual(accessToken.getUserType(), userType)) { 78 + // 注意:只有 /admin-api/* 和 /app-api/* 有 userType,才需要比对用户类型
  79 + // TODO 芋艿:ws 要不要区分开?
  80 + if (userType != null
  81 + && ObjectUtil.notEqual(accessToken.getUserType(), userType)) {
78 throw new AccessDeniedException("错误的用户类型"); 82 throw new AccessDeniedException("错误的用户类型");
79 } 83 }
80 // 构建登录用户 84 // 构建登录用户
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java
1 package cn.iocoder.yudao.framework.security.core.util; 1 package cn.iocoder.yudao.framework.security.core.util;
2 2
  3 +import cn.hutool.core.util.StrUtil;
3 import cn.iocoder.yudao.framework.security.core.LoginUser; 4 import cn.iocoder.yudao.framework.security.core.LoginUser;
4 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; 5 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
5 import org.springframework.lang.Nullable; 6 import org.springframework.lang.Nullable;
@@ -20,6 +21,9 @@ import java.util.Collections; @@ -20,6 +21,9 @@ import java.util.Collections;
20 */ 21 */
21 public class SecurityFrameworkUtils { 22 public class SecurityFrameworkUtils {
22 23
  24 + /**
  25 + * HEADER 认证头 value 的前缀
  26 + */
23 public static final String AUTHORIZATION_BEARER = "Bearer"; 27 public static final String AUTHORIZATION_BEARER = "Bearer";
24 28
25 private SecurityFrameworkUtils() {} 29 private SecurityFrameworkUtils() {}
@@ -28,19 +32,23 @@ public class SecurityFrameworkUtils { @@ -28,19 +32,23 @@ public class SecurityFrameworkUtils {
28 * 从请求中,获得认证 Token 32 * 从请求中,获得认证 Token
29 * 33 *
30 * @param request 请求 34 * @param request 请求
31 - * @param header 认证 Token 对应的 Header 名字 35 + * @param headerName 认证 Token 对应的 Header 名字
  36 + * @param parameterName 认证 Token 对应的 Parameter 名字
32 * @return 认证 Token 37 * @return 认证 Token
33 */ 38 */
34 - public static String obtainAuthorization(HttpServletRequest request, String header) {  
35 - String authorization = request.getHeader(header);  
36 - if (!StringUtils.hasText(authorization)) {  
37 - return null; 39 + public static String obtainAuthorization(HttpServletRequest request,
  40 + String headerName, String parameterName) {
  41 + // 1. 获得 Token。优先级:Header > Parameter
  42 + String token = request.getHeader(headerName);
  43 + if (StrUtil.isEmpty(token)) {
  44 + token = request.getParameter(parameterName);
38 } 45 }
39 - int index = authorization.indexOf(AUTHORIZATION_BEARER + " ");  
40 - if (index == -1) { // 未找到 46 + if (!StringUtils.hasText(token)) {
41 return null; 47 return null;
42 } 48 }
43 - return authorization.substring(index + 7).trim(); 49 + // 2. 去除 Token 中带的 Bearer
  50 + int index = token.indexOf(AUTHORIZATION_BEARER + " ");
  51 + return index >= 0 ? token.substring(index + 7).trim() : token;
44 } 52 }
45 53
46 /** 54 /**
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java
@@ -53,7 +53,8 @@ public class AppAuthController { @@ -53,7 +53,8 @@ public class AppAuthController {
53 @PermitAll 53 @PermitAll
54 @Operation(summary = "登出系统") 54 @Operation(summary = "登出系统")
55 public CommonResult<Boolean> logout(HttpServletRequest request) { 55 public CommonResult<Boolean> logout(HttpServletRequest request) {
56 - String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader()); 56 + String token = SecurityFrameworkUtils.obtainAuthorization(request,
  57 + securityProperties.getTokenHeader(), securityProperties.getTokenParameter());
57 if (StrUtil.isNotBlank(token)) { 58 if (StrUtil.isNotBlank(token)) {
58 authService.logout(token); 59 authService.logout(token);
59 } 60 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java
@@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; @@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
7 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 7 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
8 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; 8 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
9 import cn.iocoder.yudao.framework.security.config.SecurityProperties; 9 import cn.iocoder.yudao.framework.security.config.SecurityProperties;
  10 +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
10 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*; 11 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*;
11 import cn.iocoder.yudao.module.system.convert.auth.AuthConvert; 12 import cn.iocoder.yudao.module.system.convert.auth.AuthConvert;
12 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; 13 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
@@ -38,7 +39,6 @@ import java.util.Set; @@ -38,7 +39,6 @@ import java.util.Set;
38 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 39 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
39 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; 40 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
40 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; 41 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
41 -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.obtainAuthorization;  
42 42
43 @Tag(name = "管理后台 - 认证") 43 @Tag(name = "管理后台 - 认证")
44 @RestController 44 @RestController
@@ -76,7 +76,8 @@ public class AuthController { @@ -76,7 +76,8 @@ public class AuthController {
76 @Operation(summary = "登出系统") 76 @Operation(summary = "登出系统")
77 @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 77 @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
78 public CommonResult<Boolean> logout(HttpServletRequest request) { 78 public CommonResult<Boolean> logout(HttpServletRequest request) {
79 - String token = obtainAuthorization(request, securityProperties.getTokenHeader()); 79 + String token = SecurityFrameworkUtils.obtainAuthorization(request,
  80 + securityProperties.getTokenHeader(), securityProperties.getTokenParameter());
80 if (StrUtil.isNotBlank(token)) { 81 if (StrUtil.isNotBlank(token)) {
81 authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType()); 82 authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType());
82 } 83 }