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

This commit is contained in:
YunaiV 2023-11-22 23:02:59 +08:00
parent 41c324acc3
commit 6a61db8508
6 changed files with 35 additions and 16 deletions

View File

@ -19,6 +19,13 @@ public class SecurityProperties {
*/ */
@NotEmpty(message = "Token Header 不能为空") @NotEmpty(message = "Token Header 不能为空")
private String tokenHeader = "Authorization"; private String tokenHeader = "Authorization";
/**
* HTTP 请求时访问令牌的请求参数
*
* 初始目的解决 WebSocket 无法通过 header 传参只能通过 token 参数拼接
*/
@NotEmpty(message = "Token Parameter 不能为空")
private String tokenParameter = "token";
/** /**
* mock 模式的开关 * mock 模式的开关

View File

@ -129,8 +129,6 @@ public class YudaoWebSecurityConfigurerAdapter {
.antMatchers(buildAppApi("/**")).permitAll() .antMatchers(buildAppApi("/**")).permitAll()
// 1.5 验证码captcha 允许匿名访问 // 1.5 验证码captcha 允许匿名访问
.antMatchers("/captcha/get", "/captcha/check").permitAll() .antMatchers("/captcha/get", "/captcha/check").permitAll()
// 1.6 webSocket 允许匿名访问
.antMatchers("/websocket/message").permitAll()
// 每个项目的自定义规则 // 每个项目的自定义规则
.and().authorizeRequests(registry -> // 下面循环设置自定义规则 .and().authorizeRequests(registry -> // 下面循环设置自定义规则
authorizeRequestsCustomizers.forEach(customizer -> customizer.customize(registry))) authorizeRequestsCustomizers.forEach(customizer -> customizer.customize(registry)))

View File

@ -41,7 +41,8 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
@SuppressWarnings("NullableProblems") @SuppressWarnings("NullableProblems")
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException { throws ServletException, IOException {
String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader()); String token = SecurityFrameworkUtils.obtainAuthorization(request,
securityProperties.getTokenHeader(), securityProperties.getTokenParameter());
if (StrUtil.isNotEmpty(token)) { if (StrUtil.isNotEmpty(token)) {
Integer userType = WebFrameworkUtils.getLoginUserType(request); Integer userType = WebFrameworkUtils.getLoginUserType(request);
try { try {
@ -74,7 +75,10 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
return null; return null;
} }
// 用户类型不匹配无权限 // 用户类型不匹配无权限
if (ObjectUtil.notEqual(accessToken.getUserType(), userType)) { // 注意只有 /admin-api/* /app-api/* userType才需要比对用户类型
// TODO 芋艿ws 要不要区分开
if (userType != null
&& ObjectUtil.notEqual(accessToken.getUserType(), userType)) {
throw new AccessDeniedException("错误的用户类型"); throw new AccessDeniedException("错误的用户类型");
} }
// 构建登录用户 // 构建登录用户

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.framework.security.core.util; package cn.iocoder.yudao.framework.security.core.util;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
@ -20,6 +21,9 @@ import java.util.Collections;
*/ */
public class SecurityFrameworkUtils { public class SecurityFrameworkUtils {
/**
* HEADER 认证头 value 的前缀
*/
public static final String AUTHORIZATION_BEARER = "Bearer"; public static final String AUTHORIZATION_BEARER = "Bearer";
private SecurityFrameworkUtils() {} private SecurityFrameworkUtils() {}
@ -28,19 +32,23 @@ public class SecurityFrameworkUtils {
* 从请求中获得认证 Token * 从请求中获得认证 Token
* *
* @param request 请求 * @param request 请求
* @param header 认证 Token 对应的 Header 名字 * @param headerName 认证 Token 对应的 Header 名字
* @param parameterName 认证 Token 对应的 Parameter 名字
* @return 认证 Token * @return 认证 Token
*/ */
public static String obtainAuthorization(HttpServletRequest request, String header) { public static String obtainAuthorization(HttpServletRequest request,
String authorization = request.getHeader(header); String headerName, String parameterName) {
if (!StringUtils.hasText(authorization)) { // 1. 获得 Token优先级Header > Parameter
String token = request.getHeader(headerName);
if (StrUtil.isEmpty(token)) {
token = request.getParameter(parameterName);
}
if (!StringUtils.hasText(token)) {
return null; return null;
} }
int index = authorization.indexOf(AUTHORIZATION_BEARER + " "); // 2. 去除 Token 中带的 Bearer
if (index == -1) { // 未找到 int index = token.indexOf(AUTHORIZATION_BEARER + " ");
return null; return index >= 0 ? token.substring(index + 7).trim() : token;
}
return authorization.substring(index + 7).trim();
} }
/** /**

View File

@ -53,7 +53,8 @@ public class AppAuthController {
@PermitAll @PermitAll
@Operation(summary = "登出系统") @Operation(summary = "登出系统")
public CommonResult<Boolean> logout(HttpServletRequest request) { public CommonResult<Boolean> logout(HttpServletRequest request) {
String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader()); String token = SecurityFrameworkUtils.obtainAuthorization(request,
securityProperties.getTokenHeader(), securityProperties.getTokenParameter());
if (StrUtil.isNotBlank(token)) { if (StrUtil.isNotBlank(token)) {
authService.logout(token); authService.logout(token);
} }

View File

@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.framework.security.config.SecurityProperties; import cn.iocoder.yudao.framework.security.config.SecurityProperties;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*;
import cn.iocoder.yudao.module.system.convert.auth.AuthConvert; import cn.iocoder.yudao.module.system.convert.auth.AuthConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
@ -38,7 +39,6 @@ import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.obtainAuthorization;
@Tag(name = "管理后台 - 认证") @Tag(name = "管理后台 - 认证")
@RestController @RestController
@ -76,7 +76,8 @@ public class AuthController {
@Operation(summary = "登出系统") @Operation(summary = "登出系统")
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志 @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
public CommonResult<Boolean> logout(HttpServletRequest request) { public CommonResult<Boolean> logout(HttpServletRequest request) {
String token = obtainAuthorization(request, securityProperties.getTokenHeader()); String token = SecurityFrameworkUtils.obtainAuthorization(request,
securityProperties.getTokenHeader(), securityProperties.getTokenParameter());
if (StrUtil.isNotBlank(token)) { if (StrUtil.isNotBlank(token)) {
authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType()); authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType());
} }