复制代码
import cn.hutool.core.collection.CollUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.NoHandlerFoundException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Set;
/**
* @Date: 2021/11/01
* @Desc: 统一异常处理
*/
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 地址解析URL不存在异常处理
*
* @param request
* @param response
* @param ex
* @return
*/
@ExceptionHandler(value = {NoHandlerFoundException.class})
@ResponseBody
public ApiResult noHandlerFoundException(HttpServletRequest request, HttpServletResponse response, Exception ex) {
ApiResult result = buildApiResult(ErrorCode.HTTP_URL_NO_HANDLER_ERROR);
printErrorLog(request, ex);
return result;
}
/**
* 通用异常处理
*/
@ExceptionHandler(value = Exception.class)
@ResponseBody
public ApiResult handleException(HttpServletRequest request, HttpServletResponse response, Exception ex) {
ApiResult result = buildApiResult(ErrorCode.SYSTEM_ERROR);
printErrorLog(request, ex);
return result;
}
/**
* 业务异常处理
*/
@ExceptionHandler(value = BusinessException.class)
@ResponseBody
public ApiResult handleBusinessException(HttpServletRequest request, HttpServletResponse response, BusinessException ex) {
ApiResult result = buildApiResult(ex.getCode(), ex.getMessage());
printErrorLog(request, ex);
return result;
}
/**
* dubbo-rpc业务异常处理
*/
@ExceptionHandler(value = DubboRpcError.class)
@ResponseBody
public ApiResult handleDubboRpcException(HttpServletRequest request, HttpServletResponse response, DubboRpcError ex) {
ApiResult result = buildApiResult(ex.getCode(), ex.getMessage());
printErrorLog(request, ex);
return result;
}
/**
* 参数异常处理
*/
@ExceptionHandler(value = {BindException.class, MethodArgumentNotValidException.class})
@ResponseBody
public ApiResult handleBindException(HttpServletRequest request, HttpServletResponse response, BindException ex) {
ApiResult result = buildApiResult(ErrorCode.PARAM_INVALID_ERROR);
printErrorLog(request, ex);
// 获取异常信息
BindingResult exceptions = ex.getBindingResult();
// 判断异常中是否有错误信息,如果存在就使用异常中的消息,否则使用默认消息
if (exceptions.hasErrors()) {
List<ObjectError> errors = exceptions.getAllErrors();
if (!errors.isEmpty()) {
// 这里列出了全部错误参数,按正常逻辑,只需要第一条错误即可
FieldError fieldError = (FieldError) errors.get(0);
return buildApiResult(ErrorCode.PARAM_INVALID_ERROR.code, fieldError.getDefaultMessage());
}
}
return result;
}
/**
* get方法 单独参数校验异常
*/
@ExceptionHandler(value = ConstraintViolationException.class)
@ResponseBody
public ApiResult handleConstraintViolationException(HttpServletRequest request, HttpServletResponse response, ConstraintViolationException ex) {
ApiResult result = buildApiResult(ErrorCode.PARAM_INVALID_ERROR);
printErrorLog(request, ex);
Set<ConstraintViolation<?>> validationSet = ex.getConstraintViolations();
if (CollUtil.isNotEmpty(validationSet)) {
for (ConstraintViolation constraintViolation : validationSet) {
return buildApiResult(ErrorCode.PARAM_INVALID_ERROR.code, constraintViolation.getMessage());
}
}
return result;
}
/**
* 参数缺失异常处理
*/
@ExceptionHandler(value = MissingServletRequestParameterException.class)
@ResponseBody
public ApiResult handleMissingServletRequestParameterException(HttpServletRequest request, HttpServletResponse response, MissingServletRequestParameterException ex) {
ApiResult result = buildApiResult(ErrorCode.PARAM_INVALID_ERROR);
printErrorLog(request, ex);
return result;
}
/**
* 方法不支持异常处理
*/
@ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
@ResponseBody
public ApiResult handleMethodNotSupportException(HttpServletRequest request, HttpServletResponse response, HttpRequestMethodNotSupportedException ex) {
ApiResult result = buildApiResult(ErrorCode.HTTP_METHOD_NOT_SUPPORT_ERROR.code, ex.getMessage());
printErrorLog(request, ex);
return result;
}
/**
* 请求体为空异常处理
*/
@ExceptionHandler(value = HttpMessageNotReadableException.class)
@ResponseBody
public ApiResult handleHttpMessageNotReadableException(HttpServletRequest request, HttpServletResponse response, HttpRequestMethodNotSupportedException ex) {
ApiResult result = buildApiResult(ErrorCode.HTTP_MESSAGE_NOT_READABLE_ERROR);
printErrorLog(request, ex);
return result;
}
@ExceptionHandler(IllegalArgumentException.class)
@ResponseBody
public ApiResult handleIllegalArgumentExceptionHandler(HttpServletRequest request, IllegalArgumentException e) {
ApiResult result = buildApiResult(ErrorCode.PARAM_INVALID_ERROR.code, e.getMessage());
printErrorLog(request, e);
return result;
}
/**
* 捕捉AccessDeniedException,spring security抛出的无权限访问的异常信息
* @param e
* @return
*/
**@ExceptionHandler(value = AccessDeniedException.class)
@ResponseBody
public ApiResult handleAccessDeniedException(HttpServletRequest request,AccessDeniedException e) {
ApiResult result = buildApiResult(ErrorInfo.USER_NOT_POWER_ERROR.covert());
printErrorLog(request, e);
return result;
}**
public void printErrorLog(HttpServletRequest request, Exception ex) {
log.error("url:{}", request.getRequestURI(), ex);
}
public ApiResult buildApiResult(Integer code, String message) {
return new ApiResult(code, I18nLocalUtils.get(message), TraceIdHolder.get());
}
public ApiResult buildApiResult(ErrorCode errorCode) {
return new ApiResult(errorCode.getCode(), errorCode.getMessage(),TraceIdHolder.get());
}
}
pom.xml
复制代码
<!--引入spring security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
配置文件
复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.annotation.Resource;
/**
* @author super
*/
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private AuthenticationTokenFilter authenticationTokenFilter;
@Resource
private AuthenticationEntryPoint authenticationEntryPoint;
@Resource
private AccessDeniedHandler accessDeniedHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 关闭csrf
.csrf().disable()
// 不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于部分接口 允许匿名访问
.antMatchers("/api/v1/init/**").permitAll()
.antMatchers("/api/v1/check/checkAuth").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
//添加过滤器
http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
//配置异常处理器
http.exceptionHandling()
//配置认证失败处理器
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
//允许跨域
http.cors();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
/**
* @author super
*/
@Component
@Slf4j
public class AuthenticationTokenFilter extends OncePerRequestFilter {
// todo
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String traceId = getTraceIdFromHeader(request);
TraceIdHolder.set(traceId);
CurrentUserDto user = 获取用户信息,根据token.自己需求订
CurrentUserHolder.set(user);
//放入日志上下文
MDC.put(CommonConstants.TRACE_ID, traceId);
// 加入上下文
Locale locale = Locale.forLanguageTag(CommonUtils.getLang(CurrentUserHolder.get()));
LocaleContextHolder.setLocale(locale);
String appid = request.getHeader("appid");
log.info("invoke appid:{}",appid);
List<String> permissions;
if (Objects.equals(appid, AppIdEnum.B_ADMIN.getCode())) {
// todo 根据用户信息查询权限
permissions = ListUtil.toList("test","10000","20000");
}else {
permissions = ListUtil.toList("visitor");
}
UserDetailDto detailDto = new UserDetailDto(user, permissions);
// 存入SecurityContextHolder,获取权限信息封装到Authentication中
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(detailDto,null,detailDto.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//放行
filterChain.doFilter(request, response);
}
/**
* 获取当前登录用户信息
*
* @return
*/
private CurrentUserDto getSessionUser(HttpServletRequest request) {
ProfileType profile = ProfileType.getFromSpringProfilesActiveProps();
log.info("invoke AuthInterceptor getSessionUser profile = {}", profile);
//本地环境 不走gateway 构建虚拟用户
if (ProfileType.LOCAL == profile) {
String lang = request.getHeader("Accept-Language");
log.info("invoke getSessionUser lang :{}",lang);
return buildLocalTestUser(lang);
}
SessionRenewalResponse sessionUser = SessionUserUtil.getSessionUser(request);
// 有些路径访问不需要用户登录的 在gateway层已经做校验了 此处忽略
// 2021-12-2 4:42 表示需要判断token是否存在
if (ObjectUtil.isNull(sessionUser)) {
throw new BusinessException(ErrorInfo.USER_TOKEN_ERROR.covert());
}
// 封装数据
CurrentUserDto vo = new CurrentUserDto();
vo.setId(sessionUser.getInfoId());
vo.setName(sessionUser.getRealName());
Long gender = sessionUser.getGender();
if (ObjectUtil.isNotNull(gender)) {
vo.setGender(gender.intValue());
vo.setGenderName(GenderEnums.getNameByCode(gender.intValue()));
}
vo.setExtend(sessionUser.getExtend());
vo.setCredentialIdentifier(sessionUser.getCredentialIdentifier());
vo.setLang(sessionUser.getLang());
return vo;
}
/**
* local环境虚拟用户
*
* @return
*/
private CurrentUserDto buildLocalTestUser(String lang) {
CurrentUserDto vo = new CurrentUserDto();
vo.setId(30000199L);
vo.setName("TEST-USER");
vo.setGender(GenderEnums.FEMALE.code);
vo.setGenderName(GenderEnums.FEMALE.name);
vo.setCredentialIdentifier("15210006666");
vo.setSubSystem(SystemConstants.SUB_SYSTEM_ADMIN);
vo.setLang(lang);
return vo;
}
/**
* 获取traceId
*
* @return
*/
private String getTraceIdFromHeader(HttpServletRequest request) {
// 从header中获取
String traceId = request.getHeader("traceid");
return TraceIdUtils.generateIfBlank(traceId);
}
}
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @author super
*/
@Data
@NoArgsConstructor
public class UserDetailDto implements UserDetails {
private CurrentUserDto userDto;
private List<String> permissions;
public UserDetailDto(CurrentUserDto userDto, List<String> permissions) {
this.userDto = userDto;
this.permissions = permissions;
}
@JSONField(serialize = false)
private List<SimpleGrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if (authorities != null) {
return authorities;
}
// 把permissions中String类型的权限信息封装成SimpleGrantedAuthority对象
authorities = new ArrayList<>();
for (String permission : permissions) {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permission);
authorities.add(authority);
}
return authorities;
}
@Override
public String getPassword() {
return null;
}
@Override
public String getUsername() {
return null;
}
@Override
public boolean isAccountNonExpired() {
return false;
}
@Override
public boolean isAccountNonLocked() {
return false;
}
@Override
public boolean isCredentialsNonExpired() {
return false;
}
@Override
public boolean isEnabled() {
return false;
}
}
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author super
*/
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
WebUtils.renderString(response, ErrorInfo.USER_TOKEN_PORT_ERROR.covert());
}
}
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author super
*/
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
WebUtils.renderString(response, new ErrorCode(5000,"用户认证失败请查询登录"));
}
}
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author super
*/
public class WebUtils
{
/**
* 将字符串渲染到客户端
*
* @param response 渲染对象
* @param errorCode 异常(待渲染的字符串)
* @return null
*/
public static String renderString(HttpServletResponse response, ErrorCode errorCode) {
try
{
response.setStatus(errorCode.getCode());
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
ApiResult result = new ApiResult(errorCode.getCode(), errorCode.getMessage(), TraceIdHolder.get());
response.getWriter().print(result);
}
catch (IOException e)
{
e.printStackTrace();
}
return null;
}
}
自定义权限校验方法
复制代码
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.StrUtil;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
/**
* 自定义权限校验方法
* @author super
*/
@Component("ex")
public class FoxExpressionRoot {
public boolean hasAuthority(String authority){
//获取当前用户的权限
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
UserDetailDto loginUser = (UserDetailDto) authentication.getPrincipal();
List<String> permissions = loginUser.getPermissions();
//判断用户权限集合中是否存在authority
return permissions.contains(authority) || permissions.contains("visitor");
}
public Boolean hasAnyAuthority(String... authorities){
//获取当前用户的权限
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
UserDetailDto loginUser = (UserDetailDto) authentication.getPrincipal();
List<String> list = ListUtil.toList("visitor");
list.addAll(Arrays.asList(authorities));
List<String> permissions = loginUser.getPermissions();
//判断用户权限集合中是否存在authority
for (String authority : list) {
if (permissions.contains(authority)) {
return true;
}
}
return false;
}
}
结果
复制代码
/**
* 健康检查
*
* @return
*/
@GetMapping("/healthCheck")
@ResponseBody
@PreAuthorize("@ex.hasAnyAuthority('10000','40000')")
public String healthCheck(HttpServletRequest request, HttpServletResponse response) {
return JSONObject.toJSONString(SecurityContextHolder.getContext().getAuthentication());
}