• Springsecurity的授权流程


    Springsecurity的授权流程

    所谓授权,就是对访问资源管理,看你是否有权限进行访问。访问资源通常是我们后端定义的url。我们对访问资源定义好权限后,用户进行登录后,访问其它url,这个时候,需要判断用户携带的权限和url定义的权限是否匹配,如果匹配,就允许访问,否则提示权限不足。在SpringSecurity中授权主要通过最后一个过滤器FilterSecurityIntercepter来处理。

    在这里插入图片描述

    省略了其它不重要的过滤器

    1. 授权的流程

    在这里插入图片描述

    • FilterSecurityInterceptor:授权过滤器,即对访问资源进行拦截,然后调用其它接口进行授权处理

      //该类定义的源码信息
      public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
          private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied";
          private FilterInvocationSecurityMetadataSource securityMetadataSource;
          private boolean observeOncePerRequest = true;
        
           public void invoke(FilterInvocation fi) throws IOException, ServletException {
              if (fi.getRequest() != null && fi.getRequest().getAttribute("__spring_security_filterSecurityInterceptor_filterApplied") != null && this.observeOncePerRequest) {
                  fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
              } else {
                  if (fi.getRequest() != null && this.observeOncePerRequest) {
                      fi.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE);
                  }
      
                  InterceptorStatusToken token = super.beforeInvocation(fi);
      
                  try {
                      fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
                  } finally {
                      //调用了父类的方法
                      super.finallyInvocation(token);
                  }
      
                  super.afterInvocation(token, (Object)null);
              }
      
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
    • SecurityMetadataSource: 安全数据源的接口可以理解为是一种安全对象的概念模型。即我们访问的资源(url),可以抽象成一个带有权限的对象,而这个接口就是用来设置安全的对象的权限信息。该类的继承关系如下:

      在这里插入图片描述

      Spring SecuritySecurityMetadataSource提供了两个子接口 :

      • MethodSecurityMetadataSource:
        由Spring Security Core定义,用于表示安全对象是方法调用(MethodInvocation)的安全元数据源。

      • FilterInvocationSecurityMetadataSource:
        由Spring Security Web定义,用于表示安全对象是Web请求(FilterInvocation)的安全元数据源。通常使用此对象获取请求资源的。通常通过实现此接口,来保存数据库中查询的角色信息,来表示权限

      //该接口方法的定义
      public interface SecurityMetadataSource extends AopInfrastructureBean {
      	// ~ Methods
      	// =====================================================================
      	/**
      	 *获取某个受保护的安全对象object的所需要的权限信息,是一组ConfigAttribute对象的集合,如果该安全对象object不被当前		SecurityMetadataSource对象支持,则抛出异常IllegalArgumentException。该方法通常配合
      	 boolean supports(Class clazz)一起使用,先使用boolean supports(Class clazz)确保安全对象能被当前		  SecurityMetadataSource支持,然后再调用该方法。
      	 */
      	Collection<ConfigAttribute> getAttributes(Object object)
      			throws IllegalArgumentException;
      
      	/**
      	 获取该SecurityMetadataSource对象中保存的针对所有安全对象的权限信息的集合。该方法的主要目的是被AbstractSecurityInterceptor用于启动时校验每个ConfigAttribute对象。
      	 */
      	Collection<ConfigAttribute> getAllConfigAttributes();
      
      	/**
      	 * 这里clazz表示安全对象的类型,该方法用于告知调用者当前SecurityMetadataSource是否支持此类安全对象,只有支持的时候,才能对这类安全对象调用getAttributes方法。
      	 */
      	boolean supports(Class<?> clazz);
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22

      Object表示安全对象 :

      //获取访问的资源url
      FilterInvocation filterInvocation = (FilterInvocation) o;
      //获取请求url
      String requestUrl = filterInvocation.getRequestUrl();
      
      • 1
      • 2
      • 3
      • 4

      Collection 保存安全对象的权限信息

      //将访问资源的权限保存
      for (Menu menu : menusWithRole) {
                  if (antPathMatcher.match(menu.getUrl(),requestUrl)){
                      //讲角色的英文名 ROLE_*  保存到集合里面
                      String[] roleArr = menu.getRoles().stream().map(Role::getName).toArray(String[]::new);
                      return SecurityConfig.createList(roleArr);
                  }
              }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    • AccessDisisionManager: 决策管理器,决定当前对象对访问资源到底有没有权限。在其实习类中,Spring Security引入了投票器的概念,真正的授权功能是通过一组AccessDecisionVoter来实现的。
      AccessDecisionManager维护着一个AccessDecisionVoter列表参与授权的投票。根据处理投票的策略不同
      Spring SecurityAccessDecisionManager有3个不同的实现。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s4RfcRl4-1667285364067)(Springsecurity的授权流程.assets/image-20221101140518484.png)]

    • 此接口的继承关系如下:

    在这里插入图片描述

    • 子类的说明:

      • AffirmativeBased: 只要有一个投票器投票通过,就允许访问资源。
      • ConsensusBased: 超过一半的投票器通过才允许访问资源。
      • UnanimousBased: 所有投票器都通过才允许访问资源。
    • 投票者:

    RoleVoterSpring Security内置的一个AccessDecisionVoter,其会将ConfigAttribute简单的看作是一个角色名称,在投票的时如果拥有该角色即投赞成票。如果ConfigAttribute是以“ROLE_”开头的,则将使用RoleVoter进行投票。当用户拥有的权限中有一个或多个能匹配受保护对象配置的以“ROLE_”开头的ConfigAttribute时其将投赞成票;如果用户拥有的权限中没有一个能匹配受保护对象配置的以“ROLE_”开头的ConfigAttribute,则RoleVoter将投反对票;如果受保护对象配置的ConfigAttribute中没有以ROLE_开头的,则RoleVoter将弃权。

    2. 授权的使用

    2.1 资源权限的配置

    思路:每次请求一个url,将所有的菜单权限信息查询出来。判断菜单里面的url和请求的url是否匹配,如果匹配,将菜单的角色保存ConfigAttribute

    菜单表:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h7gX9Yz0-1667285364080)(Springsecurity的授权流程.assets/image-20221101142247919.png)]

    菜单类:

    public class Menu implements Serializable {
        /**
         * id
         */
        @ApiModelProperty("id")
        @TableId(type = IdType.AUTO)
        private Integer id;
    
        /**
         * 访问的url
         */
        @ApiModelProperty(" url")
        private String url;
    
        */
    	/* 
    	 * 当前url的权限
    	 */
        @ApiModelProperty("角色列表")
        @TableField(exist = false) //声明表字段不存在,否则可能会报错
        private List<Role> roles;
    
        @TableField(exist = false)
        private static final long serialVersionUID = 1L;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    自己定义一个类实现FilterInvocationSecurityMetadataSource接口。

    /**
     * 根据url获取对应的角色,并保存
     * FilterInvocationSecurityMetadataSource
     * 该类的主要功能就是通过当前的请求地址,获取该地址需要的用户角色。
     */
    @Component
    public class CustomeFilter implements FilterInvocationSecurityMetadataSource {
        @Autowired
        private MenuService menuService;
        // 声明一个ant路径匹配器
        private AntPathMatcher antPathMatcher=new AntPathMatcher();
        @Override
        public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
            FilterInvocation filterInvocation = (FilterInvocation) o;
            //获取请求url
            String requestUrl = filterInvocation.getRequestUrl();
            //获取所有的菜单 
            List<Menu> menusWithRole = menuService.getAllMenusWithRole();
            //获取匹配路径对应角色
            for (Menu menu : menusWithRole) {
                if (antPathMatcher.match(menu.getUrl(),requestUrl)){
                    //将角色的英文名 ROLE_*  保存到集合里面
                    String[] roleArr = menu.getRoles().stream().map(Role::getName).toArray(String[]::new);
                    return SecurityConfig.createList(roleArr);
                }
            }
            //没有匹配的默认登录即可
            return SecurityConfig.createList("ROLE_LOGIN");
        }
    
        @Override
        public Collection<ConfigAttribute> getAllConfigAttributes() {
            return null;
        }
    
        @Override
        public boolean supports(Class<?> aClass) {
            return true;  //支持当前对象
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    2.2 登录用户的权限配置

    用户对象:

    @TableName(value ="t_admin")
    
    public class Admin implements Serializable, UserDetails {
        /**
         * id
         */
        @TableId(type = IdType.AUTO)
        private Integer id;
    	
         /**
         * 用户的权限
         * @return
         */
        @TableField(exist = false)
        private List<Role> roles;
    	//==========================当前对象所具备的权限===================================
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            //将角色名转成SimpleGrantedAuthority对象放入集合中
            List<SimpleGrantedAuthority> roleList = roles.stream()
                    .map(role -> new SimpleGrantedAuthority(role.getName()))
                    .collect(Collectors.toList());
            return roleList;
        }
        //===============================================================
    
        //省略其它不必要属性
        // ...
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    用户认证处理:

    @Service
    public class UserDetailServiceImpl implements UserDetailsService {
        @Autowired
        private AdminMapper adminMapper;
        @Autowired
        private RoleMapper roleMapper;
        @Override
        public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
            LambdaQueryWrapper<Admin> queryWrapper=new LambdaQueryWrapper<>();
            queryWrapper.eq(Admin::getUsername,s);
            queryWrapper.eq(Admin::getEnabled,1);
            Admin admin = adminMapper.selectOne(queryWrapper);
            //设置权限
            if (admin!=null){
                admin.setRoles(roleMapper.getRoles(admin.getId()));
            }
            return admin;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    2.3 授权决策

    思路:访问资源的权限有了,当前用户的权限有了,然后进行匹配,就可以知道当前用户到底有没有权限访问这个资源了。

    自己定义一个类实现AccessDecisionManager接口,重写其方法,按照自己的业务需求进行权限判断。

    /**
     * 判断url的角色和用户的角色是否匹配\
     * 对访问的资源进行授权,决策。要求资源必须配置
     * 对一次访问授权,需要传入三个信息。
     * (1)认证过的Authentication,确定了谁正在访问资源。
     * (2)被访问的资源object。
     * (3)访问资源要求的权限配置ConfigAttribute。
     */
    @Component
    public class CusteomUrlDecisionManager implements AccessDecisionManager {
        @Override
        public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
            //获取当前url的角色
            for (ConfigAttribute configAttribute : collection) {
                // roleConfig为SecurityConfig配置的角色
                String roleConfig = configAttribute.getAttribute();
                //判断是否登录角色
                if ("ROLE_LOGIN".equals(roleConfig)) {
                    //判断是否登录
                    if (authentication instanceof AnonymousAuthenticationToken) {
                        throw new AccessDeniedException("尚未登录,请登录");
                    } else {
                        return;
                    }
    
                } else {
                    //获取用户角色
                    Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
                    for (GrantedAuthority authority : authorities) {
                        if (authority.getAuthority().equals(roleConfig)) {
                            return;
                        }
                    }
                }
            }
            throw new AccessDeniedException("权限不足,请联系管理员");
        }
    
        @Override
        public boolean supports(ConfigAttribute configAttribute) {
            return true;
        }
    
        @Override
        public boolean supports(Class<?> aClass) {
            return true;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    2.4 注册自定义的实现类

    自定义的资源权限配置,决策管理器需要注册到SpringSecurity中,才可以生效。

    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        //密码加密工具
        @Bean
        public PasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
        //jwt过滤器
        @Autowired
        public JwtTokenFilter jwtTokenFilter;
        //失败处理
        @Autowired
        private RestAccessDenyHandler restAccessDenyHandler;
        @Autowired
        private RestAuthenticationEntryPoint authenticationEntryPoint;
        //注入自定义实现类
        @Autowired
        private CustomeFilter customeFilter;
        @Autowired
        private CusteomUrlDecisionManager custeomUrlDecisionManager;
    	/*
    	 *访问资源放行配置
    	 */
        @Override
        public void configure(WebSecurity web) throws Exception {
            web.ignoring()   //swagger+登录 允许
                    .antMatchers(
                            "/login",
                            "/logout",
                            "/css/**",
                            "/js/**",
                            "/index.html",
                            "/favicon.ico",
                            "/doc.html",
                            "/webjars/**",
                            "/swagger-resources/**",
                            "/v2/api-docs/**",
                            "/kaptcha"
                    );
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //授权管理
            http.authorizeRequests()
                    .anyRequest().authenticated()
                    //===========设置动态权限决策 FilterSecurityInterceptor是授权管理器==========
                    .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                        @Override
                        public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                            //=======================设置自定义实习类=======================
                            object.setAccessDecisionManager(custeomUrlDecisionManager);
                            object.setSecurityMetadataSource(customeFilter);
                            return object;
                        }
                    });
    
            //关闭csrf  使用jwt,不需要csrf_token进行安全保护
            http.csrf()
                    .disable()
            //基于token,不需要session
                    .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
            //添加jwt 登录授权过滤器
            http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
            //添加自定义未授权和未登录结果返回
            http.exceptionHandling()
                    .accessDeniedHandler(restAccessDenyHandler)
                    .authenticationEntryPoint(authenticationEntryPoint);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    说明:
    在这里插入图片描述

  • 相关阅读:
    【C++】抽象类 与 C++
    【PowerQuery】Excel的PowerQuery按需刷新
    Linux用户管理— 用户相关文件-passwd文件- shadow文件-其他相关文件
    进程调度算法之时间片轮转调度(RR),优先级调度以及多级反馈队列调度
    操作系统之《PV操作》【知识点+详细解题过程】
    mysql修改字符集
    论区块链应用开发中的技术选型
    在Visual Studio Code macOS上尽量用Clang编译C++
    【云原生】DevOps(八):Jenkins集成Kubernetes
    【Pytorch with fastai】第 3 章 :数据伦理
  • 原文地址:https://blog.csdn.net/weixin_48917089/article/details/127633684