xinwei #3

Merged
root merged 30 commits from xinwei into master 2024-08-15 09:53:30 +08:00
7 changed files with 2 additions and 222 deletions
Showing only changes of commit 70cf1947c8 - Show all commits

View File

@ -1,8 +1,6 @@
package com.xxl.job.admin.framework.security.config;
import com.xxl.job.admin.enums.ApiConstants;
import com.xxl.job.admin.framework.security.core.filter.TokenAuthenticationFilter;
import com.xxl.job.admin.framework.security.core.handler.AccessDeniedHandlerImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
@ -16,42 +14,18 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
import javax.annotation.Resource;
/**
* Demo 模块的 Security 配置
* Security 配置 由于案例单点登录模块引入了spring-boot-starter-security要配Security配置不然会使用默认全部拦截
*/
@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("/**").permitAll()
.antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
// 2. 登录相关的接口可匿名访问
.antMatchers("/toLogin").permitAll()
.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);
.anyRequest().permitAll(); // 放行所有使用xxl-job的拦截器拦截
return httpSecurity.build();
}

View File

@ -1,67 +0,0 @@
package com.xxl.job.admin.framework.security.core.filter;
import com.xxl.job.admin.client.OAuth2Client;
import com.xxl.job.admin.client.dto.CommonResult;
import com.xxl.job.admin.client.dto.oauth2.OAuth2CheckTokenRespDTO;
import com.xxl.job.admin.framework.security.core.LoginUser;
import com.xxl.job.admin.framework.security.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;
}
}
}

View File

@ -1,45 +0,0 @@
package com.xxl.job.admin.framework.security.core.handler;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.xxl.job.admin.client.dto.CommonResult;
import com.xxl.job.admin.framework.security.core.util.SecurityUtils;
import com.xxl.job.admin.framework.security.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);
}
}

View File

@ -1,37 +0,0 @@
package com.xxl.job.admin.framework.security.core.handler;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.xxl.job.admin.client.dto.CommonResult;
import com.xxl.job.admin.framework.security.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);
}
}

View File

@ -69,17 +69,6 @@ public class SecurityUtils {
return authentication.getPrincipal() instanceof LoginUser ? (LoginUser) authentication.getPrincipal() : null;
}
/**
* 获得当前用户的编号从上下文中
*
* @return 用户编号
*/
@Nullable
public static Long getLoginUserId() {
LoginUser loginUser = getLoginUser();
return loginUser != null ? loginUser.getId() : null;
}
/**
* 设置当前用户
*

View File

@ -1,32 +0,0 @@
package com.xxl.job.admin.framework.security.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);
}
}

View File

@ -10,8 +10,6 @@ import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.core.util.JacksonUtil;
import com.xxl.job.admin.dao.XxlJobUserDao;
import com.xxl.job.admin.framework.security.core.LoginUser;
import com.xxl.job.admin.framework.security.core.filter.TokenAuthenticationFilter;
import com.xxl.job.admin.framework.security.core.handler.AccessDeniedHandlerImpl;
import com.xxl.job.admin.framework.security.core.util.SecurityUtils;
import com.xxl.job.core.biz.model.ReturnT;
import org.springframework.context.annotation.Configuration;