欢迎关注公众号【Ccww笔记】,原创技术文章第一时间推出

前言

权限管理系统的组件分析以及认证过程的往期文章:

1. 权限管理相关概念

  权限管理是一个几乎所有后台系统的都会涉及的一个重要组成部分,主要目的是对整个后台管理系统进行权限的控制。常见的基于角色的访问控制,其授权模型为“用户-角色-权限”,简明的说,一个用户拥有多个角色,一个角色拥有多个权限。其中,

  • 用户: 不用多讲,大家也知道了;

  • 角色: 一个集合的概念,角色管理是确定角色具备哪些权限的一个过程 ;

  • 权限: 1).页面权限,控制你可以看到哪个页面,看不到哪个页面;     2). 操作权限,控制你可以在页面上进行哪些操作(查询、删除、编辑等);     3).数据权限,是控制你可以看到哪些数据。

实质是:

  权限(Permission) = 资源(Resource) + 操作(Privilege)  

  角色(Role) = 权限的集合(a set of low-level permissions)  

  用户(User) = 角色的集合(high-level roles)

权限管理过程:

  1. 鉴权管理,即权限判断逻辑,如菜单管理(普通业务人员登录系统后,是看不到【用户管理】菜单的)、功能权限管理(URL访问的管理)、行级权限管理等

  2. 授权管理,即权限分配过程,如直接对用户授权,直接分配到用户的权限具有最优先级别、对用户所属岗位授权,用户所属岗位信息可以看作是一个分组,和角色的作用一样,但是每个用户只能关联一个岗位信息等。

  在实际项目中用户数量多,逐一的为每个系统用户授权,这是极其繁琐的事,所以可以学习linux文件管理系统一样,设置group模式,一组有多个用户,可以为用户组授权相同的权限,简便多了。这样模式下:   每个用户的所有权限=用户个人的权限+用户组所用的权限 用户组、用户、与角色三者关系如下:

再结合权限管理的页面权限、操作权限,如菜单的访问、功能模块的操作、按钮的操作等等,可把功能操作与资源统一管理,即让它们直接与权限关联起来,关系图如下:

2. 授权过程分析

2.1 授权访问权限工作流程:

  FilterSecurityInterceptor
doFilter()->invoke()
->AbstractSecurityInterceptor
beforeInvocation()
->SecurityMetadataSource 获取ConfigAttribute属性信息(从数据库或者其他数据源地方)
getAttributes()
->AccessDecisionManager() 基于AccessDecisionVoter实现授权访问
Decide()
->AccessDecisionVoter 受AccessDecisionManager委托实现授权访问
vote()

默认授权过程会使用这样的工作流程,接下来来分析各个组件的功能与源码。

2.2 AbstractSecurityInterceptor分析

  FilterSecurityInterceptor为授权拦截器, 在FilterSecurityInterceptor中有一个封装了过滤链request以及responseFilterInvocation对象进行操作,在FilterSecurityInterceptor,主要由invoke()调用其父类AbstractSecurityInterceptor的方法。

invoke()分析:

 public void invoke(FilterInvocation fi) throws IOException, ServletException {
.....
// 获取accessDecisionManager权限决策后结果状态、以及权限属性
InterceptorStatusToken token = super.beforeInvocation(fi);

try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
super.finallyInvocation(token);
}

super.afterInvocation(token, null);
}
}

  AbstractSecurityInterceptor 的授权过滤器主要方法beforeInvocation(),afterInvocation()以及authenticateIfRequired(),其最主要的方法beforeInvocation() 分析如下:

protected InterceptorStatusToken beforeInvocation(Object object) {
....
//从SecurityMetadataSource的权限属性
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
if (attributes == null || attributes.isEmpty()) {
.....
publishEvent(new PublicInvocationEvent(object));
return null; // no further work post-invocation
}

//调用认证环节获取authenticated(包含用户的详细信息)
Authentication authenticated = authenticateIfRequired();

// Attempt authorization
try {
//进行关键的一步:授权的最终决策
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException accessDeniedException) {
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
accessDeniedException));

throw accessDeniedException;
} // Attempt to run as a different user
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
attributes);

if (runAs == null) {
if (debug) {
logger.debug("RunAsManager did not change Authentication object");
}

// no further work post-invocation
return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
attributes, object);
}
else {
if (debug) {
logger.debug("Switching to RunAs Authentication: " + runAs);
}

SecurityContext origCtx = SecurityContextHolder.getContext();
SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
SecurityContextHolder.getContext().setAuthentication(runAs);

// need to revert to token.Authenticated post-invocation
return new InterceptorStatusToken(origCtx, true, attributes, object);
}
}

2.3 SecurityMetadataSource

  SecurityMetadataSource是从数据库或者其他数据源中加载ConfigAttribute,为了在AccessDecisionManager.decide() 最终决策中进行match。其有三个方法:

Collection<ConfigAttribute> getAttributes(Object var1) throws IllegalArgumentException;//加载权限资源

Collection<ConfigAttribute> getAllConfigAttributes();//加载所有权限资源

boolean supports(Class<?> var1);

2.4 AccessDecisionManager

  AccessDecisionManagerAbstractSecurityInterceptor 拦截器调用进行最终访问控制决策。 而且由AuthenticationManager创建的Authentication object中的GrantedAuthority,首先被授权模块中的 AccessDecisionManager读取使用,当复杂的GrantedAuthority,getAuthority()为null,因此需要AccessDecisionManager专门支持GrantedAuthority实现以便了解其内容。

AccessDecisionManager接口方法:

 void decide(Authentication authentication, Object secureObject,    Collection<ConfigAttribute> attrs) throws AccessDeniedException;

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);

2.5 AccessDecisionVoter

  AccessDecisionManager.decide()将使用AccessDecisionVoter进行投票决策。AccessDecisionVoter进行投票访问控制决策,访问不通过就抛出AccessDeniedException

AccessDecisionVoter接口方法:

 int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attrs);

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);

AccessDecisionVoter核心方法vote() 通常是获取Authentication的GrantedAuthority已定义好的ConfigAttributes进行match,如果成功为投同意票,匹配不成功为拒绝票,当ConfigAttributes中无属性时,才投弃票。

Spring Security提供了三种投票方式去实现AccessDecisionManager接口进行投票访问控制决策:

  • ConsensusBased: 大多数voter同意访问就授权访问

  • AffirmativeBased: 只要一个以上voter同意访问就授权访问,全部

  • UnanimousBased : 只有全体同意了才授权访问

AccessDecisionVoter用三个静态变量表示voter投票情况:

  • ACCESS_ABSTAIN: 弃权

  • ACCESS_DENIED: 拒绝访问

  • ACCESS_GRANTED: 允许访问

Note: 当所有voter都弃权时使用变量allowIfEqualGrantedDeniedDecisions来判断,true为通过,false抛出AccessDeniedException。

此外可自定义AccessDecisionManager实现接口,因为可能某些AccessDecisionVoter具有权重比高投票权或者某些AccessDecisionVoter具有一票否定权。AccessDecisionVoter的Spring security实现类RoleVoterAuthenticatedVoterRoleVoter为最为常见的AccessDecisionVoter,其为简单的权限表示,并以前缀为ROLE_,vote匹配规则也跟上面一样。

源码分析:

Public int vote(Authentication authentication,Object object,Collection<ConfigAttribute>attributes){
//用户传递的authentication为null,拒绝访问
if(authentication==null){

return ACCESS_DENIED;

}

int result=ACCESS_ABSTAIN;

Collection<?extendsGrantedAuthority>authorities=extractAuthorities(authentication); //依次进行投票
for(ConfigAttributeattribute:attributes){ if(this.supports(attribute)){

result=ACCESS_DENIED;

//Attempt to find a matching granted authority

for(GrantedAuthorityauthority:authorities){

if(attribute.getAttribute().equals(authority.getAuthority())){

returnACCESS_GRANTED;
}
}
}
}

3. 案例-自定义组件

自定义组件:

  1. 自定义FilterSecurityInterceptor,可仿写FilterSecurityInterceptor,实现抽象类AbstractSecurityInterceptor以及Filter接口,其主要的是把自定义的SecurityMetadataSource自定义accessDecisionManager配置到自定义FilterSecurityInterceptor的拦截器中

  2. 自定义SecurityMetadataSource,实现接口FilterInvocationSecurityMetadataSource,实现从数据库或者其他数据源中加载ConfigAttribute(即是从数据库或者其他数据源中加载资源权限)

  3. 自定义accessDecisionManager,可使用基于AccessDecisionVoter实现权限认证的官方UnanimousBased

  4. 自定义AccessDecisionVoter

3.1 自定义MyFilterSecurityInterceptor

自定义MyFilterSecurityInterceptor主要工作为:

  • 加载自定义的SecurityMetadataSource到自定义的FilterSecurityInterceptor中;

  • 加载自定义的AccessDecisionManager到自定义的FilterSecurityInterceptor中;

  • 重写invoke方法

    @Component
    public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
    private FilterInvocationSecurityMetadataSource securityMetadataSource; @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    FilterInvocation fi = new FilterInvocation(request, response, chain);
    invoke(fi);
    }

    private void invoke(FilterInvocation fi) throws IOException, ServletException {
    //fi里面有一个被拦截的url
    //里面调用MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限
    //再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够
    InterceptorStatusToken token = super.beforeInvocation(fi);
    try {
    //执行下一个拦截器
    fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
    } finally {
    super.afterInvocation(token, null);
    }
    }

    @Override
    public void destroy() {

    }

    @Override
    public Class<?> getSecureObjectClass() {
    return null;
    }

    @Override
    public SecurityMetadataSource obtainSecurityMetadataSource() {
    return this.securityMetadataSource;
    }

    public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
    return this.securityMetadataSource;
    }

    //设置自定义的FilterInvocationSecurityMetadataSource
    @Autowired
    public void setSecurityMetadataSource(MyFilterInvocationSecurityMetadataSource messageSource) {
    this.securityMetadataSource = messageSource;
    }

    //设置自定义的AccessDecisionManager
    @Override
    @Autowired
    public void setAccessDecisionManager(AccessDecisionManager accessDecisionManager) {
    super.setAccessDecisionManager(accessDecisionManager);
    }
    }

3.2 自定义MyFilterInvocationSecurityMetadataSource

自定义MyFilterInvocationSecurityMetadataSource主要工作为:

  • 从数据源中加载ConfigAttribute到SecurityMetadataSource资源器中

  • 重写getAttributes()加载ConfigAttribute为AccessDecisionManager.decide()授权决策做准备。

    @Component
    public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { private Map<String, Collection<ConfigAttribute>> configAttubuteMap = null; private void loadResourceDefine() {
    //todo 加载数据库的所有权限
    Collection<ConfigAttribute> attributes;
    } @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
    AntPathRequestMatcher matcher;
    String resUrl;
    HttpServletRequest request = ((FilterInvocation) object).getRequest();
    //1.加载权限资源数据
    if (configAttubuteMap == null) {
    loadResourceDefine();
    }
    Iterator<String> iterator = configAttubuteMap.keySet().iterator();
    while (iterator.hasNext()) {
    resUrl = iterator.next();
    matcher = new AntPathRequestMatcher(resUrl);
    if (matcher.matches(request)) {
    return configAttubuteMap.get(resUrl);
    }
    }
    return null;
    } @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
    return null;
    } @Override
    public boolean supports(Class<?> clazz) {
    return FilterInvocation.class.isAssignableFrom(clazz);
    }
    }

3.3 自定义MyAccessDecisionManager

自定义MyAccessDecisionManager主要工作为:

  • 重写最终授权决策decide(),自定义授权访问策略

    @Component
    public class MyAccessDecisionManager implements AccessDecisionManager {
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
    ConfigAttribute c;
    String needRole;
    if(null== configAttributes || configAttributes.size() <=0) {
    return;
    }
    //1.获取已定义的好资源权限配置
    Iterator<ConfigAttribute> iterable=configAttributes.iterator();
    while (iterable.hasNext()){
    c=iterable.next();
    needRole=c.getAttribute();
    //2.依次比对用户角色对应的资源权限
    for (GrantedAuthority grantedAuthority:authentication.getAuthorities()){
    if(needRole.trim().equals(grantedAuthority.getAuthority())){
    return;
    }
    }
    } } @Override
    public boolean supports(ConfigAttribute attribute) {
    return true;
    } @Override
    public boolean supports(Class<?> clazz) {
    return true;
    }
    }

3.4 配置SecurityConfig

配置SecurityConfig主要工作为:

  • 将FilterSecurityInterceptor拦截器加载WebSecurityConfig中

    protected void configure(HttpSecurity http) throws Exception {
    http.headers().frameOptions().disable().and()
    //表单登录
    .formLogin()
    .loginPage(SecurityConstants.APP_FORM_LOGIN_PAGE)
    .loginProcessingUrl(SecurityConstants.APP_FORM_LOGIN_URL)
    .successHandler(authenticationSuccessHandler())
    .failureHandler(authenticationFailureHandler())
    .and()
    //应用sms认证配置
    .apply(smsAuthenticationSecurityConfig)
    .and()
    //允许通过
    .authorizeRequests()
    .antMatchers(SecurityConstants.APP_MOBILE_VERIFY_CODE_URL,
    SecurityConstants.APP_USER_REGISTER_URL,
    SecurityConstants.APP_FORM_LOGIN_INDEX_URL)
    .permitAll()//以上的请求都不需要认证
    .and()
    //“记住我”配置
    .rememberMe()
    .tokenRepository(jdbcTokenRepository())//token入库处理类
    .tokenValiditySeconds(SecurityConstants.REMEMBER_ME_VERIFY_TIME)//remember-me有效时间设置
    .rememberMeParameter(SecurityConstants.REMEMBER_ME_PARAM_NAME)//请求参数名设置
    .and()
    .csrf().disable();
    //增加自定义权限授权拦截器
    http.addFilterBefore(myFilterSecurityInterceptor,FilterSecurityInterceptor.class);
    }

    总结

      Spring Security授权过程中,可以会涉主要涉及了上面上面所述的组件,其中主要的还是跟着源码多跑几遍,了解其中的原理,才能更加流畅的码代码。到此为止写完Spring Security的认证和授权分析流程,接下来会结合前面小节,写一个Spring security完美的权限管理系统。

    各位看官还可以吗?喜欢的话,动动手指点个赞

    [权限管理系统篇] (五)-Spring security(授权过程分析)的更多相关文章

    1. Spring Security 解析(五) —— Spring Security Oauth2 开发

      Spring Security 解析(五) -- Spring Security Oauth2 开发   在学习Spring Cloud 时,遇到了授权服务oauth 相关内容时,总是一知半解,因此决 ...

    2. Spring Security授权 AccessDecisionManager

      Spring Security授权 AccessDecisionManager 博客分类: Security Spring   在前面那篇博客有一段配置: <http auto-config=& ...

    3. spring security 授权方式(自定义)及源码跟踪

      spring security 授权方式(自定义)及源码跟踪 ​ 这节我们来看看spring security的几种授权方式,及简要的源码跟踪.在初步接触spring security时,为了实现它的 ...

    4. SpringBoot安全篇Ⅵ --- 整合Spring Security

      知识储备: 关于SpringSecurity的详细学习可以查看SpringSecurity的官方文档. Spring Security概览 应用程序的两个主要区域是"认证"和&qu ...

    5. SpringSecurity权限管理系统实战—五、整合SpringSecurity(下)

      系列目录 前言 上篇文章SpringSecurity整合了一半,这次把另一半整完,所以本篇的序号接着上一篇. 七.自定义用户信息 前面我们登录都是用的指定的用户名和密码或者是springsecurit ...

    6. 深入理解Spring Security授权机制原理

      原创/朱季谦 在Spring Security权限框架里,若要对后端http接口实现权限授权控制,有两种实现方式. 一.一种是基于注解方法级的鉴权,其中,注解方式又有@Secured和@PreAuth ...

    7. 权限管理3-整合Spring Security

      一.Spring Security介绍 1.框架介绍 Spring 是一个非常流行和成功的 Java 应用开发框架.Spring Security 基于 Spring 框架,提供了一套 Web 应用安 ...

    8. ABP+AdminLTE+Bootstrap Table权限管理系统第五节--WBEAPI及SwaggerUI

      一,Web API ABP的动态WebApi实现了直接对服务层的调用(其实病没有跨过ApiController,只是将ApiController公共化,对于这一点的处理类似于MVC,对服务端的 调用没 ...

    9. 权限管理系统(五):RBAC新解,基于资源的权限管理

      本文讨论以角色概念进行的权限管理策略及主要以基于角色的机制进行权限管理是远远不够的.同时我将讨论一种我认为更好的权限管理方式. 1.什么是角色 当说到程序的权限管理时,人们往往想到角色这一概念.角色是 ...

    随机推荐

    1. App稳定性测试Monkey

      1.$ adb shell monkey <event-count>                <event-count>是随机发送事件数 例:adb shell monk ...

    2. 安装eclipse血泪史

      从大一到大三,屡次卸掉eclipse又屡次安装上,每次都要卡壳,所以这里开帖贴出自己的血泪史,以帮助大家 首先找一篇安装教程,网上有很多,这里不再赘述.举例 https://blog.csdn.net ...

    3. Git的使用和基本概念理解

      参考:https://www.liaoxuefeng.com/wiki/896043488029600 一).git的使用: 1.创建版本库(Resopsitory),相当于一个目录,目录中所有的文件 ...

    4. 使用boost实现线程池thread pool | boost thread pool example

      本文首发于个人博客https://kezunlin.me/post/f241bd30/,欢迎阅读! boost thread pool example Guide boost thread pool ...

    5. C# - VS2019 WinFrm应用程序开发报表 - ReportViewer控件初涉

      前言 简单报表我们可以通过label.textBox和PrintDialog来实现,但是一般在实际生产过程中,用户的报表需求一般都是比较复杂的. 本篇主要记录对于传统中国式复杂报表的处理方法和解决思路 ...

    6. Android 8.1 自定义热点的时候设置了热点maxLength="32",但是在希伯来语等状态下还是发现在没到32个字符之前就无法把热点设置成功了

      初步认为应该是与热点名称的字节数有关. 然后开始查看源码. /Settings/res/xml/tether_prefs.xml 中的 <Preference android:key=" ...

    7. UML元素绘制方式

      UML是由视图(View).图(Diagrams).模型元素(Model elements)是和通用机制等几个部分构成. 视图:视图是对系统的抽象表示,UML共有9种不同的图类型. 模型元素:代表面向 ...

    8. 解决failed to push some refs to 'git@github.com:TQBX/GIT-Github-.git'问题

      解决以下问题问题: git pull origin master --allow-unrelated-histories 进入vim界面->ESC->:wq 重复第一步->git p ...

    9. 驰骋工作流系统-Java共工作流引擎配置定时任务

      关键词:工作流定时任务  流程引擎定时任务设置  工作流系统定时任务配置  开源工作流引擎 开源工作流系统 一.定时任务的作用 发送邮件,发送短信. 处理节点自动执行的任务.比如:一个节点的待办工作是 ...

    10. TensorFlow2.0极简安装(亲测有效)

      x相信每一个学习深度学习的人来说都知道Google的深度学习框架TensorFlow,估计每个人都想成为一个TF Boy(TensorFlow Boy).我也是这个想法,于是我踏上了安装TensorF ...