当前位置: 首页 > article >正文

SpringBoot项目实现用户token和资源的多重校验

1、需求

在spring security双token机制实现一文中已经实现了token的校验,在实际的项目中还需要根据用户的角色或用户Id对数据资源进行校验。
例如,有两个项目A和B,张三和李四都是项目实施人员这一角色,张三是项目A的项目组成员,李四是项目B的项目组成员,他们只能访问自己所属项目的资源。

2、实现

这里对spring security双token机制实现中的代码进行一部分改造。

2.1 改造TokenAuthenticationFilter

部分公共资源是可以不需要项目的权限就可以访问的,在前面的例子中,比如一些公共的模块(公司组织架构查询,帮助等)。token校验成功后可以将我们请求的资源需要的校验写入上下文中,以便过滤器链处理(代码中的NOTE注释)。

@Component
public class TokenAuthenticationFilter extends OncePerRequestFilter {

    private UserMapper userMapper;

    private TokenMapper tokenMapper;

    private static final Logger logger = LoggerFactory.getLogger(TokenAuthenticationFilter.class);

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        if (userMapper == null) {
            userMapper = SpringUtils.getBean(UserMapper.class);
        }
        if (tokenMapper == null) {
            tokenMapper = SpringUtils.getBean(TokenMapper.class);
        }
        String tokenHeaderStr = request.getHeader("authorization");
        String token = tokenHeaderStr.substring(7);
        String userId = authenticateToken(token);
        if (userId == null || userId.isEmpty()) {
            logger.error("无效的token");
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            response.setCharacterEncoding("UTF-8");
            response.getWriter().write("无效的token");
            return;
        }
        User user = userMapper.findByUserIdWithRole(userId);
        String requestUri = request.getServletPath();

        // NOTE: 在上面校验用户的逻辑完成之后可以通过这样的方式将资源权限相关的属性写入Authentication上下文中,这里getNeedProjectAuth()方法获取该接口是否需要校验用户的项目权限
        List<GrantedAuthority> authorityList =
                AuthorityUtils.createAuthorityList(user.getRole().getRoleCode(), getNeedProjectAuth(requestUri).toString());

		// NOTE: 这里将用户的信息写入Authentication上下文
        Authentication authentication =
                new UsernamePasswordAuthenticationToken(user, token, authorityList);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        filterChain.doFilter(request, response);
    }

	// 实现shouldNotFilter方法,设置无需token校验的url
    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        String fullUri = request.getRequestURI();
        String context_path = request.getContextPath();
        String uri = fullUri.substring(fullUri.indexOf(context_path) + context_path.length());
        return Arrays.asList(acceptUrls).contains(uri);
    }
    
    private String authenticateToken(String tokenStr) {
        Token token = tokenMapper.getTokenByAccessToken(tokenStr);
        if (token == null) {
            return null;
        }
        LocalDateTime now = LocalDateTime.now();
        if (token.getAccessExpireTime().isAfter(now)) {
            return token.getUserId();
        }
        return null;
    }
}

2.2 实现ProjectAuthenticationFilter

校验token成功之后通过ProjectAuthenticationFilter 来校验用户的项目权限,从上下文中读取Authentication,获得项目权限校验信息(根据url判断是否需要校验项目权限,用户是否具有该项目的权限)

@Component
public class ProjectAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private ProjectUserMapper projectUserMapper;

    private static final Logger logger = LoggerFactory.getLogger(ProjectAuthenticationFilter.class);

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        // ADMIN用户可以访问所有的项目资源
        if (auth.getAuthorities().toArray()[0].toString().toUpperCase().equals("ROLE_ADMIN")) {
            filterChain.doFilter(request, response);
        } else {
    		/**
     		* 获取用户是否有请求项目资源的权限,方法略
     		*/

            filterChain.doFilter(request, response);
        }
    }

	// 在这里判断是否需要校验权限
    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
    	// NOTE:从上下文中获取Authentication ,根据接口所需资源情况判断是否需要校验用户的项目权限
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        boolean needProjectAuth = Boolean.parseBoolean(auth.getAuthorities().toArray()[1].toString());
        return !needProjectAuth;
    }
}

2.3 按校验顺序注册过滤器

这里需要改造下spring security配置类,按照恰当的顺序注册过滤器链(代码中的NOTE注释)。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private TokenAuthenticationFilter tokenAuthenticationFilter;

    @Autowired
    private ServiceAuthenticationFilter serviceAuthenticationFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    	// NOTE:先经过token校验过滤器,再经过项目校验过滤器
        http.csrf().disable().authorizeRequests()
                .anyRequest().authenticated()
                .and().addFilterBefore(tokenAuthenticationFilter, BasicAuthenticationFilter.class)
                .addFilterAfter(projectAuthenticationFilter, TokenAuthenticationFilter.class)
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

http://www.kler.cn/a/556040.html

相关文章:

  • 【原创】vue-element-admin-plus完成编辑页面中嵌套列表功能
  • 基于AVue的二次封装:快速构建后台管理系统的CRUD方案
  • Amazon Lex:AI对话引擎重构企业服务新范式
  • 应用层的协议-http/https的状态码
  • OSPF | 理论 / 实验
  • ubuntu 守护进程
  • QSplashScreen --软件启动前的交互
  • 项目汇报PPT转视频制作 | 有字幕和配音版
  • 【知识】深度学习中,应该先zero_grad还是先backward?
  • 让浏览器AI起来:基于大模型Agent的浏览器自动化工具
  • 实现rolabelimg对于dota格式文件的直接加载和保存
  • 轻松美化双系统启动界面与同步时间设置(Windows + Ubuntu)
  • 每天设计者模式-1:基础面试题
  • AF3 _build_query_to_hit_index_mapping函数解读
  • windows 安装 stable diffusion
  • CPCI机箱 阿尔泰科技4槽2U CPCI测控机箱 后IO走线 CPCIC7604A
  • Java——列表(List)
  • org.mortbay.jetty和org.eclipse.jetty的区别
  • 第4章 信息系统架构(五)
  • CNN 卷积神经网络【更新中】