springboot 项目集成spring security

复制代码
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());
    }
相关推荐
Re2752 分钟前
springboot源码分析--初始加载配置类
java·spring boot
敖正炀17 分钟前
Spring 依赖注入方式及原理
spring
敖正炀17 分钟前
Spring循环依赖详解
spring
快来卷java27 分钟前
深入剖析 JVM:从组成原理到调优实践
java·jvm·spring boot·spring cloud·数据挖掘·maven
Mr.wangh2 小时前
SpringBoot 配置⽂件
java·spring boot·spring
秋野酱2 小时前
基于javaweb的SpringBoot水果生鲜商城系统设计与实现(源码+文档+部署讲解)
java·spring boot·后端
时间会给答案scidag2 小时前
构建第一个SpringBoot程序
java·spring boot
菲兹园长2 小时前
Spring Web MVC(Spring MVC)
前端·spring·mvc
Leo1877 小时前
parallelStream线程问题及解决方案
java·spring boot
shaoweijava8 小时前
基于SpringBoot的美食设计网站(源码+数据库)
数据库·spring boot·mysql·mybatis