mirror of
https://github.com/RemainderTime/spring-boot-base-demo.git
synced 2026-02-06 15:10:56 +08:00
登录时查询用户权限和角色并加载到Spring Security的上下文中
This commit is contained in:
4
pom.xml
4
pom.xml
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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("/**");
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user