mirror of
https://github.com/RemainderTime/spring-boot-base-demo.git
synced 2026-03-08 13:10:45 +08:00
登录时查询用户权限和角色并加载到Spring Security的上下文中
This commit is contained in:
@@ -6,6 +6,7 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
@@ -84,6 +85,19 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler{
|
||||
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* 访问权限不足异常捕获
|
||||
* @param e
|
||||
* @return
|
||||
*/
|
||||
@ExceptionHandler(AccessDeniedException.class)
|
||||
public ResponseEntity handleAccessDenied(AccessDeniedException e) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("code", 403);
|
||||
result.put("msg", "权限不足,请联系管理员");
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 其他异常捕获
|
||||
* @param request
|
||||
|
||||
@@ -37,11 +37,6 @@ public class CustomUserDetails implements UserDetails {
|
||||
this.authorities = authorities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return null;
|
||||
|
||||
@@ -1,29 +1,32 @@
|
||||
package cn.xf.basedemo.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Description: 全局跨域配置
|
||||
*
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class GlobalCorsConfig {
|
||||
|
||||
//package cn.xf.basedemo.config;
|
||||
//
|
||||
//import cn.xf.basedemo.interceptor.CustomAccessDeniedHandler;
|
||||
//import lombok.extern.slf4j.Slf4j;
|
||||
//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.method.configuration.EnableMethodSecurity;
|
||||
//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;
|
||||
//
|
||||
///**
|
||||
// * Description: 全局跨域配置
|
||||
// *
|
||||
// */
|
||||
//@Slf4j
|
||||
//@Configuration
|
||||
//@EnableMethodSecurity(prePostEnabled = true) // 开启 @PreAuthorize/@PostAuthorize
|
||||
//public class GlobalCorsConfig {
|
||||
//
|
||||
// @ConditionalOnMissingBean
|
||||
// @Bean
|
||||
// public CorsFilter corsFilter() {
|
||||
// public FilterRegistrationBean<CorsFilter> corsFilter() {
|
||||
// CorsConfiguration config = new CorsConfiguration();
|
||||
// // 放行哪些原始域
|
||||
// //config.addAllowedOrigin("*");
|
||||
@@ -39,36 +42,10 @@ public class GlobalCorsConfig {
|
||||
// UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
|
||||
// configSource.registerCorsConfiguration("/**", config);
|
||||
//
|
||||
//// FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(configSource));
|
||||
// FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(configSource));
|
||||
// // 这个顺序很重要哦,为避免麻烦请设置在最前
|
||||
//// bean.setOrder(0);
|
||||
// return new CorsFilter(configSource);
|
||||
// bean.setOrder(0);
|
||||
// return bean;
|
||||
// }
|
||||
|
||||
@Bean
|
||||
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.addAllowedOriginPattern("*");
|
||||
config.addAllowedHeader("*");
|
||||
config.addAllowedMethod("*");
|
||||
config.setAllowCredentials(true);
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
return source;
|
||||
}
|
||||
|
||||
}
|
||||
//
|
||||
//}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package cn.xf.basedemo.config.security;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Description: spring security体系 全局跨域配置
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@EnableMethodSecurity(prePostEnabled = true) // 开启 @PreAuthorize/@PostAuthorize
|
||||
public class SpringSecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.cors(Customizer.withDefaults()) // 开启 CORS
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers("/**", "/web/**").permitAll() // 放行登录、注册接口
|
||||
.anyRequest().authenticated()
|
||||
);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
config.addAllowedOriginPattern("*");
|
||||
config.addAllowedHeader("*");
|
||||
config.addAllowedMethod("*");
|
||||
config.setAllowCredentials(true);
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
return source;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -40,8 +40,10 @@ public class UserController {
|
||||
@PostMapping("/info")
|
||||
@PreAuthorize("hasAuthority('user:add')") // 权限控制
|
||||
public RetObj info(){
|
||||
LoginUser loginUser = SessionContext.getInstance().get();
|
||||
return RetObj.success(loginUser);
|
||||
// LoginUser loginUser = SessionContext.getInstance().get();
|
||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||
CustomUserDetails user = (CustomUserDetails) auth.getPrincipal();
|
||||
return RetObj.success(user);
|
||||
}
|
||||
|
||||
@Operation(summary = "es同步用户信息", description = "用户信息")
|
||||
@@ -56,7 +58,6 @@ public class UserController {
|
||||
public RetObj getEsId(Long userId){
|
||||
return userService.getEsId(userId);
|
||||
}
|
||||
|
||||
@Operation(summary = "获取用户权限数据", description = "用户信息")
|
||||
@GetMapping("/getPermission")
|
||||
public RetObj getPermission(){
|
||||
@@ -65,4 +66,5 @@ public class UserController {
|
||||
return RetObj.success(user.getAuthorities());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
package cn.xf.basedemo.interceptor;
|
||||
|
||||
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.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
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.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
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.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @Description: 登录权限校验过滤器(过滤器职责:登录认证和权限恢复)
|
||||
* @ClassName: TokenAuthenticationFilter
|
||||
* @Author: xiongfeng
|
||||
* @Date: 2025/8/28 22:41
|
||||
* @Version: 1.0
|
||||
*/
|
||||
@Component
|
||||
public class TokenAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
//不拦截的请求列表
|
||||
private static final List<String> EXCLUDE_PATH_LIST = Arrays.asList("/user/login", "/web/login", "/swagger-ui.html", "/v3/api-docs", "/swagger-ui/index.html");
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate redisTemplate;
|
||||
|
||||
@Autowired
|
||||
private SysPermissionMapper sysPermissionMapper;
|
||||
|
||||
@Autowired
|
||||
private SysRoleMapper sysRoleMapper;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||
//登录处理
|
||||
try {
|
||||
String requestURI = request.getRequestURI();
|
||||
if (EXCLUDE_PATH_LIST.contains(requestURI) ||
|
||||
requestURI.contains("/swagger-ui") ||
|
||||
requestURI.contains("/v3/api-docs")) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
String token = request.getHeader("Authorization");
|
||||
if (StringUtils.isEmpty(token))
|
||||
token = request.getParameter("token");
|
||||
if (StringUtils.isEmpty(token)) {
|
||||
throw new LoginException("请先登录");
|
||||
}
|
||||
String value = (String) redisTemplate.opsForValue().get("token:" + token);
|
||||
if (StringUtils.isEmpty(value)) {
|
||||
throw new LoginException();
|
||||
}
|
||||
JSONObject jsonObject = JSONObject.parseObject(value);
|
||||
//JSON对象转换成Java对象
|
||||
LoginUser loginUserInfo = JSONObject.toJavaObject(jsonObject, LoginUser.class);
|
||||
if (loginUserInfo == null || loginUserInfo.getId() <= 0) {
|
||||
throw new LoginException(ResponseCode.USER_INPUT_ERROR);
|
||||
}
|
||||
redisTemplate.expire(token, 86700, TimeUnit.SECONDS);
|
||||
//用户信息设置到上下文(如果使用Spring security 也可设置登录用户上下文数据,下面就可不用自定义设置)
|
||||
SessionContext.getInstance().set(loginUserInfo);
|
||||
//设置用户权限角色
|
||||
this.setSpringSecurityContext(loginUserInfo);
|
||||
filterChain.doFilter(request, response);
|
||||
}catch (LoginException e) {
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.getWriter().write("{\"message\":\"" + e.getMessage() + "\"}");
|
||||
}
|
||||
}
|
||||
// Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||
// CustomUserDetails user = (CustomUserDetails) auth.getPrincipal();
|
||||
// Long userId = user.getUserId(); // 拿到登录用户 ID
|
||||
|
||||
/**
|
||||
* 设置用户权限角色 (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);
|
||||
}
|
||||
}
|
||||
@@ -35,20 +35,13 @@ import java.util.stream.Collectors;
|
||||
/**
|
||||
* @program: spring-boot-base-demo
|
||||
* @ClassName TokenInterceptor
|
||||
* @description:
|
||||
* @description: 拦截器职责(日志、请求校验、限流)
|
||||
* @author: xiongfeng
|
||||
* @create: 2022-06-16 14:17
|
||||
**/
|
||||
@Component
|
||||
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");
|
||||
|
||||
@@ -61,33 +54,8 @@ public class TokenInterceptor implements HandlerInterceptor {
|
||||
requestURI.contains("/v3/api-docs")) {
|
||||
return true;
|
||||
}
|
||||
//登录处理
|
||||
String token = request.getHeader("Authorization");
|
||||
if (StringUtils.isEmpty(token))
|
||||
token = request.getParameter("token");
|
||||
if (StringUtils.isEmpty(token)) {
|
||||
throw new LoginException("请先登录");
|
||||
}
|
||||
String value = (String) redisTemplate.opsForValue().get("token:" + token);
|
||||
if (StringUtils.isEmpty(value)) {
|
||||
throw new LoginException();
|
||||
}
|
||||
JSONObject jsonObject = JSONObject.parseObject(value);
|
||||
//JSON对象转换成Java对象
|
||||
LoginUser loginUserInfo = JSONObject.toJavaObject(jsonObject, LoginUser.class);
|
||||
if (loginUserInfo == null || loginUserInfo.getId() <= 0) {
|
||||
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 {
|
||||
@@ -95,26 +63,5 @@ public class TokenInterceptor implements HandlerInterceptor {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user