授权原理

授权原理

在Spring Security权限框架里,若要对后端http接口实现权限受权控制,有两种实现方式。

1、重写 #configure(HttpSecurity http) 方法,主要配置 URL 的权限控制

@Override
protected void configure(HttpSecurity http) throws Exception {

    http
            // 配置请求地址的权限
            .authorizeRequests()
            .antMatchers("/test/echo").permitAll() // 所有用户可访问
            .antMatchers("/test/admin").hasRole("ADMIN") // 需要 ADMIN 角色
            .antMatchers("/test/normal").access("hasRole('ROLE_NORMAL')") // 需要 NORMAL 角色。
            // 任何请求,访问的用户都需要经过认证
            .anyRequest().authenticated()
            .and()
            // 设置 Form 表单登录
            //自定义登录页面,可以通过 #loginPage(String loginPage) 设置
            .formLogin()
            // .loginPage("/login") // 登录 URL 地址
            .permitAll() // 所有用户可访问
            .and()
            // 配置退出相关
            .logout()
            // .logoutUrl("/logout") // 退出 URL 地址
            .permitAll(); // 所有用户可访问

}

2、修改 WebSecurityConfig配置类,增加 @EnableGlobalMethodSecurity 注解,开启对 Spring Security 注解的方法,进行权限验证。

@Configuration 

@EnableGlobalMethodSecurity(prePostEnabled = true) 

public class SecurityConfig extends WebSecurityConfigurerAdapter { }

@RestController
@RequestMapping("/demo")
public class DemoController {
    
    @PermitAll
    @GetMapping("/echo")
    public String demo() {
        return "示例返回";
    }

    @GetMapping("/home")
    public String home() {
        return "我是首页";
    }

    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @GetMapping("/admin")
    public String admin() {
        return "我是管理员";
    }

    @PreAuthorize("hasRole('ROLE_NORMAL')")
    @GetMapping("/normal")
    public String normal() {
        return "我是普通用户";
    }
}

授权流程

在认证过程打印的过滤器链

  1. WebAsyncManagerIntegrationFilter
  2. SecurityContextPersistenceFilter
  3. HeaderWriterFilter
  4. LogoutFilter
  5. UsernamePasswordAuthenticationFilter
  6. JwtAuthorizationTokenFilter
  7. RequestCacheAwareFilter
  8. SecurityContextHolderAwareRequestFilter
  9. SessionManagementFilter
  10. ExceptionTranslationFilter
  11. FilterSecurityInterceptor

在完成认证过程后,最后需要实现授权,通过FilterSecurityInterceptor完成。

FilterSecurityInterceptor

过滤器链将请求传递转发FilterSecurityInterceptor时,会执行FilterSecurityInterceptor的doFilter方法:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    this.invoke(new FilterInvocation(request, response, chain));
}

在这段代码当中,FilterInvocation类是一个有意思的存在,其实它的功能很简单,就是将上一个过滤器传递过滤的request,response,chain复制保存到FilterInvocation里,专门供FilterSecurityInterceptor过滤器使用。它的有意思之处在于,是将多个参数统一概括到一个类当中,起到统一管理做用,你想,如果N多个参数,传进来都分散到类的各个地方,参数多了,代码多了,方法过于分散时,可能就很容易形成阅读过程当中,弄糊涂这些个参数都是哪里来了。但若统一概括到一个类里,就能很快定位其来源,方便代码阅读。网上有人提到该FilterInvocation类还起到解耦做用,即避免与其余过滤器使用一样的引用变量。

FilterInvocation

public class FilterInvocation {
  
     private FilterChain chain;
     private HttpServletRequest request;
     private HttpServletResponse response;
  
  
     public FilterInvocation(ServletRequest request, ServletResponse response,
           FilterChain chain) {
        if ((request == null) || (response == null) || (chain == null)) {
           throw new IllegalArgumentException("Cannot pass null values to constructor");
        }
  
        this.request = (HttpServletRequest) request;
        this.response = (HttpServletResponse) response;
        this.chain = chain;
     }
     ......
  }

FilterSecurityInterceptor的doFilter方法里调用invoke(fi)方法

public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
        if (this.isApplied(filterInvocation) && this.observeOncePerRequest) {
            //筛选器已应用于此请求,每一个请求处理一次,因此不需从新进行安全检查 
            filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
        } else {
            // 第一次调用此请求时,需执行安全检查
            if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
                filterInvocation.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE);
            }
			//1.受权具体实现入口
            InterceptorStatusToken token = super.beforeInvocation(filterInvocation);

            try {
                //2.受权经过后执行的业务
                filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
            } finally {
                super.finallyInvocation(token);
            }
			//3.后续处理
            super.afterInvocation(token, (Object)null);
        }
    }

super.beforeInvocation(filterInvocation)

受权机制实现的入口是super.beforeInvocation(fi),其具体实如今父类AbstractSecurityInterceptor中实现,beforeInvocation(Object object)的实现主要包括如下步骤:

1、获取需访问的接口权限;

2、获取认证经过以后保存在 SecurityContextHolder的用户信息,其中,authorities是一个保存用户所拥有所有权限的集合;

3、尝试受权,用户信息authenticated、请求携带对象信息object、所访问接口的权限信息attributes,传入到decide方法;

主要逻辑块:

1、Collection attributes = this.obtainSecurityMetadataSource().getAttributes(object);

2、Authentication authenticated = this.authenticateIfRequired();

3、this.attemptAuthorization(object, attributes, authenticated);

用户信息获取
private Authentication authenticateIfRequired() {
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    if (authentication.isAuthenticated() && !this.alwaysReauthenticate) {
        if (this.logger.isTraceEnabled()) {
            this.logger.trace(LogMessage.format("Did not re-authenticate %s before authorizing", authentication));
        }

        return authentication;
    } else {
        authentication = this.authenticationManager.authenticate(authentication);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug(LogMessage.format("Re-authenticated %s before authorizing", authentication));
        }

        SecurityContextHolder.getContext().setAuthentication(authentication);
        return authentication;
    }
}

在认证过程经过后,执行SecurityContextHolder.getContext().setAuthentication(authentication)将用户信息保存在Security框架当中,以后可经过SecurityContextHolder.getContext().getAuthentication()获取到保存的用户信息;

SecurityContextHolder

认证成功,请求会重新回到UsernamePasswordAuthenticationFilter,然后会通过其父类AbstractAuthenticationProcessingFilter.successfulAuthentication方法将认证对象封装成SecurityContext设置到SecurityContextHolder中

protected void successfulAuthentication(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain, Authentication authResult)
			throws IOException, ServletException {


	if (logger.isDebugEnabled()) {
		logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
				+ authResult);
	}

	//认证成功,吧Authentication 设置到SecurityContextHolder
	SecurityContextHolder.getContext().setAuthentication(authResult);
	//处理记住我业务逻辑
	rememberMeServices.loginSuccess(request, response, authResult);

	// Fire event
	if (this.eventPublisher != null) {
		eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
				authResult, this.getClass()));
	}
	//重定向登录成功地址
	successHandler.onAuthenticationSuccess(request, response, authResult);
}
尝试受权
private void attemptAuthorization(Object object, Collection<ConfigAttribute> attributes, Authentication authenticated) {
    try {
        this.accessDecisionManager.decide(authenticated, object, attributes);
    } catch (AccessDeniedException var5) {
        if (this.logger.isTraceEnabled()) {
            this.logger.trace(LogMessage.format("Failed to authorize %s with attributes %s using %s", object, attributes, this.accessDecisionManager));
        } else if (this.logger.isDebugEnabled()) {
            this.logger.debug(LogMessage.format("Failed to authorize %s with attributes %s", object, attributes));
        }

        this.publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, var5));
        throw var5;
    }
}

decide()是决策管理器AccessDecisionManager定义的一个方法。

public interface AccessDecisionManager {
    void decide(Authentication var1, Object var2, Collection<ConfigAttribute> var3) throws AccessDeniedException, InsufficientAuthenticationException;

    boolean supports(ConfigAttribute var1);

    boolean supports(Class<?> var1);
}

AccessDecisionManager是一个interface接口,这是受权体系的核心。FilterSecurityInterceptor 在鉴权时,就是经过调用AccessDecisionManager的decide()方法来进行受权决策,若能经过,则可访问对应的接口。

AccessDecisionManager类的方法具体实现都在子类当中,包含AffirmativeBased、ConsensusBased、UnanimousBased三个子类;

  • AffirmativeBased表示一票经过,这是AccessDecisionManager默认类;

    (1)只要有AccessDecisionVoter的投票为ACCESS_GRANTED则同意用户进行访问;

    (2)如果全部弃权也表示通过;

    (3)如果没有一个人投赞成票,但是有人投反对票,则将抛出AccessDeniedException。 Spring security默认使用的是AffirmativeBased。

  • ConsensusBased表示少数服从多数;

    (1)如果赞成票多于反对票则表示通过。

    (2)反过来,如果反对票多于赞成票则将抛出AccessDeniedException。

    (3)如果赞成票与反对票相同且不等于0,并且属性 allowIfEqualGrantedDeniedDecisions的值为true,则表 示通过,否则将抛出异常 AccessDeniedException。参数allowIfEqualGrantedDeniedDecisions的值默认为true。

    (4)如果所 有的AccessDecisionVoter都弃权了,则将视参数allowIfAllAbstainDecisions的值而定,如果该值为 true则表示通过,否则将抛出异常AccessDeniedException。参数allowIfAllAbstainDecisions的值默认 为false

  • UnanimousBased表示一票反对;

    (1)如果受保护对象配置的某一个ConfigAttribute被任意的AccessDecisionVoter反对了,则将抛出 AccessDeniedException。

    (2)如果没有反对票,但是有赞成票,则表示通过。

    (3)如果全部弃权了,则将视参数allowIfAllAbstainDecisions的值而定,true则通过,false则抛出 AccessDeniedException。

点进去AffirmativeBased类里,能够看到里面有一行代码int result = voter.vote(authentication, object, configAttributes):

这里的AccessDecisionVoter是一个投票器,用到委托设计模式,即AffirmativeBased类会委托投票器进行选举,而后将选举结果返回赋值给result,而后判断result结果值,若为1,等于ACCESS_GRANTED值时,则表示可一票经过,也就是,容许访问该接口的权限。

这里,ACCESS_GRANTED表示赞成、ACCESS_DENIED表示拒绝、ACCESS_ABSTAIN表示弃权:

public interface AccessDecisionVoter<S> {
       int ACCESS_GRANTED = 1;//表示赞成
       int ACCESS_ABSTAIN = 0;//表示弃权
       int ACCESS_DENIED = -1;//表示拒绝
       ......
       }

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值