登录时查询用户权限和角色并加载到Spring Security的上下文中

This commit is contained in:
xiongfeng
2025-08-27 22:01:34 +08:00
parent 36747350f4
commit f5cc0345a7
11 changed files with 159 additions and 92 deletions

View File

@@ -196,8 +196,8 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>14</source>
<target>14</target>
<source>16</source>
<target>16</target>
</configuration>
</plugin>
</plugins>

View File

@@ -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<? extends GrantedAuthority> authorities;
public CustomUserDetails(Integer userId, String phone, Collection<? extends GrantedAuthority> authorities) {
this.userId = userId;
this.phone = phone;
this.authorities = authorities;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return null;
}
@Override
public String getUsername() {
return null;
}
}

View File

@@ -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> 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;
}
}

View File

@@ -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("/**");
}
}

View File

@@ -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());
}
}

View File

@@ -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;

View File

@@ -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<String> getPermissionList(Object userId, String s) {
//获取登录用户权限数据
Long aLong = Long.valueOf(userId.toString());
List<String> permissionList = sysPermissionMapper.getPermissionListByRoleId(aLong);
return permissionList;
}
@Override
public List<String> getRoleList(Object userId, String s) {
//获取用户角色数据
return sysRoleMapper.getRoleListByUserId((Long) userId);
}
}

View File

@@ -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<String> EXCLUDE_PATH_LIST = Arrays.asList("/user/login", "/web/login","/swagger-ui.html","/v3/api-docs","/swagger-ui/index.html");
private static final List<String> 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<String> permissionList = sysPermissionMapper.getPermissionListByRoleId(loginUserInfo.getId());
//获取用户角色数据
List<String> roleList = sysRoleMapper.getRoleListByUserId(loginUserInfo.getId());
if (!CollectionUtils.isEmpty(roleList)) {
//为角色拼接前缀
roleList = roleList.stream().map(role -> "ROLE_" + role).collect(Collectors.toList());
}
permissionList.addAll(roleList);
//封装用户权限角色
List<GrantedAuthority> 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);
}
}

View File

@@ -15,6 +15,6 @@ import java.util.List;
@Mapper
public interface SysPermissionMapper extends BaseMapper<SysPermission> {
List<String> getPermissionListByRoleId(Long useId);
List<String> getPermissionListByRoleId(Integer useId);
}

View File

@@ -15,7 +15,7 @@ import java.util.List;
@Mapper
public interface SysRoleMapper extends BaseMapper<SysRole> {
List<String> getRoleListByUserId(Long userId);
List<String> getRoleListByUserId(Integer userId);
}

View File

@@ -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);
}