From f5cc0345a75714d00d57dbd0c79f8b9b61e8a916 Mon Sep 17 00:00:00 2001 From: xiongfeng Date: Wed, 27 Aug 2025 22:01:34 +0800 Subject: [PATCH] =?UTF-8?q?=E7=99=BB=E5=BD=95=E6=97=B6=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E6=9D=83=E9=99=90=E5=92=8C=E8=A7=92=E8=89=B2?= =?UTF-8?q?=E5=B9=B6=E5=8A=A0=E8=BD=BD=E5=88=B0Spring=20Security=E7=9A=84?= =?UTF-8?q?=E4=B8=8A=E4=B8=8B=E6=96=87=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 4 +- .../common/model/CustomUserDetails.java | 54 +++++++++++++++++ .../xf/basedemo/config/GlobalCorsConfig.java | 60 ++++++++++++++----- .../xf/basedemo/config/SaTokenConfigure.java | 23 ------- .../controller/business/UserController.java | 15 +++-- .../interceptor/InterceptorConfig.java | 1 - .../interceptor/StpInterfaceImpl.java | 36 ----------- .../interceptor/TokenInterceptor.java | 51 ++++++++++++++-- .../basedemo/mappers/SysPermissionMapper.java | 2 +- .../cn/xf/basedemo/mappers/SysRoleMapper.java | 2 +- .../service/impl/UserServiceImpl.java | 3 - 11 files changed, 159 insertions(+), 92 deletions(-) create mode 100644 src/main/java/cn/xf/basedemo/common/model/CustomUserDetails.java delete mode 100644 src/main/java/cn/xf/basedemo/config/SaTokenConfigure.java delete mode 100644 src/main/java/cn/xf/basedemo/interceptor/StpInterfaceImpl.java diff --git a/pom.xml b/pom.xml index 5279238..56925b9 100644 --- a/pom.xml +++ b/pom.xml @@ -196,8 +196,8 @@ org.apache.maven.plugins maven-compiler-plugin - 14 - 14 + 16 + 16 diff --git a/src/main/java/cn/xf/basedemo/common/model/CustomUserDetails.java b/src/main/java/cn/xf/basedemo/common/model/CustomUserDetails.java new file mode 100644 index 0000000..f380858 --- /dev/null +++ b/src/main/java/cn/xf/basedemo/common/model/CustomUserDetails.java @@ -0,0 +1,54 @@ +package cn.xf.basedemo.common.model; + +import lombok.Data; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; + +/** + * @Description: 自定义Spring Security用户对象类 + * @ClassName: CustomUserDetails + * @Author: xiongfeng + * @Date: 2025/8/27 20:58 + * @Version: 1.0 + */ +@Data +public class CustomUserDetails implements UserDetails { + + /** + * 用户ID + */ + private Integer userId; + + /** + * 手机号 + */ + private String phone; + + /** + * 权限列表 + */ + private Collection authorities; + + public CustomUserDetails(Integer userId, String phone, Collection authorities) { + this.userId = userId; + this.phone = phone; + this.authorities = authorities; + } + + @Override + public Collection getAuthorities() { + return null; + } + + @Override + public String getPassword() { + return null; + } + + @Override + public String getUsername() { + return null; + } +} diff --git a/src/main/java/cn/xf/basedemo/config/GlobalCorsConfig.java b/src/main/java/cn/xf/basedemo/config/GlobalCorsConfig.java index e401c65..cf9999d 100644 --- a/src/main/java/cn/xf/basedemo/config/GlobalCorsConfig.java +++ b/src/main/java/cn/xf/basedemo/config/GlobalCorsConfig.java @@ -5,7 +5,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.SecurityFilterChain; import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; @@ -17,28 +21,54 @@ import org.springframework.web.filter.CorsFilter; @Configuration public class GlobalCorsConfig { - @ConditionalOnMissingBean +// @ConditionalOnMissingBean +// @Bean +// public CorsFilter corsFilter() { +// CorsConfiguration config = new CorsConfiguration(); +// // 放行哪些原始域 +// //config.addAllowedOrigin("*"); +// // 放行哪些原始域,SpringBoot2.4.4下低版本使用.allowedOrigins("*") +// config.addAllowedOriginPattern("*"); +// // 放行哪些原始请求头部信息 +// config.addAllowedHeader("*"); +// // 放行全部请求 +// config.addAllowedMethod("*"); +// // 是否发送Cookie +// config.setAllowCredentials(true); +// +// UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource(); +// configSource.registerCorsConfiguration("/**", config); +// +//// FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(configSource)); +// // 这个顺序很重要哦,为避免麻烦请设置在最前 +//// bean.setOrder(0); +// return new CorsFilter(configSource); +// } + @Bean - public FilterRegistrationBean corsFilter() { + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .cors(Customizer.withDefaults()) // 开启 CORS + .csrf(csrf -> csrf.disable()) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/user/login", "/web/**").permitAll() // 放行登录、注册接口 + .anyRequest().authenticated() + ); + + return http.build(); + } + + @Bean + public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); - // 放行哪些原始域 - //config.addAllowedOrigin("*"); - // 放行哪些原始域,SpringBoot2.4.4下低版本使用.allowedOrigins("*") config.addAllowedOriginPattern("*"); - // 放行哪些原始请求头部信息 config.addAllowedHeader("*"); - // 放行全部请求 config.addAllowedMethod("*"); - // 是否发送Cookie config.setAllowCredentials(true); - UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource(); - configSource.registerCorsConfiguration("/**", config); - - FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(configSource)); - // 这个顺序很重要哦,为避免麻烦请设置在最前 - bean.setOrder(0); - return bean; + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + return source; } } diff --git a/src/main/java/cn/xf/basedemo/config/SaTokenConfigure.java b/src/main/java/cn/xf/basedemo/config/SaTokenConfigure.java deleted file mode 100644 index fa44f18..0000000 --- a/src/main/java/cn/xf/basedemo/config/SaTokenConfigure.java +++ /dev/null @@ -1,23 +0,0 @@ -package cn.xf.basedemo.config; - -import cn.dev33.satoken.interceptor.SaInterceptor; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -/** - * @Description: sa token拦截器注册类 - * @ClassName: SaTokenConfigure - * @Author: xiongfeng - * @Date: 2025/8/24 20:30 - * @Version: 1.0 - */ -@Configuration -public class SaTokenConfigure implements WebMvcConfigurer { - // 注册 Sa-Token 拦截器,打开注解式鉴权功能 - @Override - public void addInterceptors(InterceptorRegistry registry) { - // 注册 Sa-Token 拦截器,打开注解式鉴权功能 - registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**"); - } -} diff --git a/src/main/java/cn/xf/basedemo/controller/business/UserController.java b/src/main/java/cn/xf/basedemo/controller/business/UserController.java index 2c727c9..00df484 100644 --- a/src/main/java/cn/xf/basedemo/controller/business/UserController.java +++ b/src/main/java/cn/xf/basedemo/controller/business/UserController.java @@ -1,8 +1,6 @@ package cn.xf.basedemo.controller.business; -import cn.dev33.satoken.annotation.SaCheckPermission; -import cn.dev33.satoken.annotation.SaCheckRole; -import cn.dev33.satoken.stp.StpUtil; +import cn.xf.basedemo.common.model.CustomUserDetails; import cn.xf.basedemo.common.model.LoginUser; import cn.xf.basedemo.common.model.RetObj; import cn.xf.basedemo.interceptor.SessionContext; @@ -11,6 +9,9 @@ import cn.xf.basedemo.service.UserService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; /** @@ -37,15 +38,15 @@ public class UserController { @Operation(summary = "用户信息", description = "用户信息") @PostMapping("/info") - @SaCheckPermission("user:info") //权限校验 + @PreAuthorize("hasAuthority('user:add')") // 权限控制 public RetObj info(){ LoginUser loginUser = SessionContext.getInstance().get(); return RetObj.success(loginUser); } @Operation(summary = "es同步用户信息", description = "用户信息") - @SaCheckRole("admin") //角色校验 @GetMapping("/syncEs") + @PreAuthorize("hasRole('admin')") // 角色控制 public RetObj syncEs(Long userId){ return userService.syncEs(userId); } @@ -59,7 +60,9 @@ public class UserController { @Operation(summary = "获取用户权限数据", description = "用户信息") @GetMapping("/getPermission") public RetObj getPermission(){ - return RetObj.success(StpUtil.getPermissionList()); + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + CustomUserDetails user = (CustomUserDetails) auth.getPrincipal(); + return RetObj.success(user.getAuthorities()); } } diff --git a/src/main/java/cn/xf/basedemo/interceptor/InterceptorConfig.java b/src/main/java/cn/xf/basedemo/interceptor/InterceptorConfig.java index 2c0b13b..08bdc00 100644 --- a/src/main/java/cn/xf/basedemo/interceptor/InterceptorConfig.java +++ b/src/main/java/cn/xf/basedemo/interceptor/InterceptorConfig.java @@ -1,6 +1,5 @@ package cn.xf.basedemo.interceptor; -import cn.dev33.satoken.interceptor.SaInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; diff --git a/src/main/java/cn/xf/basedemo/interceptor/StpInterfaceImpl.java b/src/main/java/cn/xf/basedemo/interceptor/StpInterfaceImpl.java deleted file mode 100644 index a2098d4..0000000 --- a/src/main/java/cn/xf/basedemo/interceptor/StpInterfaceImpl.java +++ /dev/null @@ -1,36 +0,0 @@ -package cn.xf.basedemo.interceptor; - -import cn.dev33.satoken.stp.StpInterface; -import cn.xf.basedemo.common.utils.ApplicationContextUtils; -import cn.xf.basedemo.mappers.SysPermissionMapper; -import cn.xf.basedemo.mappers.SysRoleMapper; -import org.springframework.stereotype.Component; - -import java.util.List; - -/** - * @Description: 权限加载组件类 - * @ClassName: StpInterfaceImpl - * @Author: xiongfeng - * @Date: 2025/8/18 22:51 - * @Version: 1.0 - */ -@Component -public class StpInterfaceImpl implements StpInterface { - - private SysPermissionMapper sysPermissionMapper = ApplicationContextUtils.getBean(SysPermissionMapper.class); - private SysRoleMapper sysRoleMapper = ApplicationContextUtils.getBean(SysRoleMapper.class); - @Override - public List getPermissionList(Object userId, String s) { - //获取登录用户权限数据 - Long aLong = Long.valueOf(userId.toString()); - List permissionList = sysPermissionMapper.getPermissionListByRoleId(aLong); - return permissionList; - } - - @Override - public List getRoleList(Object userId, String s) { - //获取用户角色数据 - return sysRoleMapper.getRoleListByUserId((Long) userId); - } -} diff --git a/src/main/java/cn/xf/basedemo/interceptor/TokenInterceptor.java b/src/main/java/cn/xf/basedemo/interceptor/TokenInterceptor.java index 72ee4a7..0a5b52e 100644 --- a/src/main/java/cn/xf/basedemo/interceptor/TokenInterceptor.java +++ b/src/main/java/cn/xf/basedemo/interceptor/TokenInterceptor.java @@ -1,21 +1,36 @@ package cn.xf.basedemo.interceptor; +import ch.qos.logback.core.LayoutBase; import cn.xf.basedemo.common.exception.LoginException; import cn.xf.basedemo.common.exception.ResponseCode; +import cn.xf.basedemo.common.model.CustomUserDetails; import cn.xf.basedemo.common.model.LoginUser; import cn.xf.basedemo.common.utils.ApplicationContextUtils; +import cn.xf.basedemo.mappers.SysPermissionMapper; +import cn.xf.basedemo.mappers.SysRoleMapper; import com.alibaba.fastjson.JSONObject; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.config.annotation.authentication.configurers.provisioning.UserDetailsManagerConfigurer; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.springframework.web.servlet.HandlerInterceptor; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; /** * @program: spring-boot-base-demo @@ -30,10 +45,12 @@ public class TokenInterceptor implements HandlerInterceptor { @Autowired private RedisTemplate redisTemplate; + private SysPermissionMapper sysPermissionMapper = ApplicationContextUtils.getBean(SysPermissionMapper.class); + private SysRoleMapper sysRoleMapper = ApplicationContextUtils.getBean(SysRoleMapper.class); //不拦截的请求列表 - private static final List EXCLUDE_PATH_LIST = Arrays.asList("/user/login", "/web/login","/swagger-ui.html","/v3/api-docs","/swagger-ui/index.html"); + private static final List EXCLUDE_PATH_LIST = Arrays.asList("/user/login", "/web/login", "/swagger-ui.html", "/v3/api-docs", "/swagger-ui/index.html"); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { @@ -62,16 +79,42 @@ public class TokenInterceptor implements HandlerInterceptor { throw new LoginException(ResponseCode.USER_INPUT_ERROR); } redisTemplate.expire(token, 86700, TimeUnit.SECONDS); - - //用户信息设置到上下文 + //设置用户权限角色 + this.setSpringSecurityContext(loginUserInfo); + //用户信息设置到上下文(如果使用Spring security 也可设置登录用户上下文数据,下面就可不用自定义设置) SessionContext.getInstance().set(loginUserInfo); return HandlerInterceptor.super.preHandle(request, response, handler); } - +// Authentication auth = SecurityContextHolder.getContext().getAuthentication(); +// CustomUserDetails user = (CustomUserDetails) auth.getPrincipal(); +// Long userId = user.getUserId(); // 拿到登录用户 ID @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { HandlerInterceptor.super.afterCompletion(request, response, handler, ex); SessionContext.getInstance().clear(); } + + /** + * 设置用户权限角色 (Spring Security 本身的 SecurityContext 是请求级别的,每次请求都会被清理,所以每次请求都会查询权限数据并设置, + * 安全但是很慢,所以可以做一些优化,比如把权限数据放到redis中获取和用户信息一起放在jwt中,然后登录时解析在设置到Spring security上下文中) + * @param loginUserInfo + */ + private void setSpringSecurityContext(LoginUser loginUserInfo) { + //获取登录用户权限数据 + List permissionList = sysPermissionMapper.getPermissionListByRoleId(loginUserInfo.getId()); + //获取用户角色数据 + List roleList = sysRoleMapper.getRoleListByUserId(loginUserInfo.getId()); + if (!CollectionUtils.isEmpty(roleList)) { + //为角色拼接前缀 + roleList = roleList.stream().map(role -> "ROLE_" + role).collect(Collectors.toList()); + } + permissionList.addAll(roleList); + //封装用户权限角色 + List authorities = AuthorityUtils.createAuthorityList(permissionList); + //设置用户信息到SpringSecurity上下文 + UserDetails userDetails = new CustomUserDetails(loginUserInfo.getId(), loginUserInfo.getPhone(), authorities); + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); + } } diff --git a/src/main/java/cn/xf/basedemo/mappers/SysPermissionMapper.java b/src/main/java/cn/xf/basedemo/mappers/SysPermissionMapper.java index cfecd13..fe7d1ff 100644 --- a/src/main/java/cn/xf/basedemo/mappers/SysPermissionMapper.java +++ b/src/main/java/cn/xf/basedemo/mappers/SysPermissionMapper.java @@ -15,6 +15,6 @@ import java.util.List; @Mapper public interface SysPermissionMapper extends BaseMapper { - List getPermissionListByRoleId(Long useId); + List getPermissionListByRoleId(Integer useId); } diff --git a/src/main/java/cn/xf/basedemo/mappers/SysRoleMapper.java b/src/main/java/cn/xf/basedemo/mappers/SysRoleMapper.java index f8318c4..3930b0b 100644 --- a/src/main/java/cn/xf/basedemo/mappers/SysRoleMapper.java +++ b/src/main/java/cn/xf/basedemo/mappers/SysRoleMapper.java @@ -15,7 +15,7 @@ import java.util.List; @Mapper public interface SysRoleMapper extends BaseMapper { - List getRoleListByUserId(Long userId); + List getRoleListByUserId(Integer userId); } diff --git a/src/main/java/cn/xf/basedemo/service/impl/UserServiceImpl.java b/src/main/java/cn/xf/basedemo/service/impl/UserServiceImpl.java index eff1f8a..84f3c97 100644 --- a/src/main/java/cn/xf/basedemo/service/impl/UserServiceImpl.java +++ b/src/main/java/cn/xf/basedemo/service/impl/UserServiceImpl.java @@ -1,6 +1,5 @@ package cn.xf.basedemo.service.impl; -import cn.dev33.satoken.stp.StpUtil; import cn.xf.basedemo.common.model.EsBaseModel; import cn.xf.basedemo.common.model.LoginInfo; import cn.xf.basedemo.common.model.LoginUser; @@ -91,8 +90,6 @@ public class UserServiceImpl implements UserService { redisTemplate.opsForValue().set("token:" + token, JSONObject.toJSONString(loginUser), 3600, TimeUnit.SECONDS); redisTemplate.opsForValue().set("user_login_token:" + user.getId(), token, 3600, TimeUnit.SECONDS); - //登录成功 写入sa-token中 - StpUtil.login(user.getId()); return RetObj.success(loginUser); }