Springsecurity的授权流程

Springsecurity的授权流程

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

Springsecurity的授权流程_第1张图片

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

1. 授权的流程

Springsecurity的授权流程_第2张图片

  • 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);
            }
    
        }
    }
    
  • SecurityMetadataSource: 安全数据源的接口可以理解为是一种安全对象的概念模型。即我们访问的资源(url),可以抽象成一个带有权限的对象,而这个接口就是用来设置安全的对象的权限信息。该类的继承关系如下:

    Springsecurity的授权流程_第3张图片

    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);
    }
    
    

    Object表示安全对象 :

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

    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);
                }
            }
    
  • AccessDisisionManager: 决策管理器,决定当前对象对访问资源到底有没有权限。在其实习类中,Spring Security引入了投票器的概念,真正的授权功能是通过一组AccessDecisionVoter来实现的。
    AccessDecisionManager维护着一个AccessDecisionVoter列表参与授权的投票。根据处理投票的策略不同
    Spring SecurityAccessDecisionManager有3个不同的实现。

    Springsecurity的授权流程_第4张图片

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

Springsecurity的授权流程_第5张图片

  • 子类的说明:

    • 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

菜单表:

Springsecurity的授权流程_第6张图片

菜单类:

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;

自己定义一个类实现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;  //支持当前对象
    }
}

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;
    }
    //===============================================================

    //省略其它不必要属性
    // ...

}

用户认证处理:

@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;
    }
}

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;
    }
}

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);
    }

}

说明:
Springsecurity的授权流程_第7张图片

你可能感兴趣的:(java,springsecurity,java,spring,security)