SpringSecurity授权

  之前的文章中,一直在写SpringSecurity的认证方面的知识,本片文章SpringSecurity学习的终章,将讲述一下授权方面的内容。

1.授权的定义

  我们这里讲的授权是指能否访问相关的资源,而不是前端中页面某个用户能否看到某个按钮,一个资源能否被访问是SpringSecurity所关注的,按钮能否被看见只是用户体验的问题,如下图所示:


2.SpringSecurity在系统中的应用

  一般来说我们在公司中会开发两种系统,一种是业务系统,一种是内管系统。什么是业务系统呢?只有登录和简单的角色就是业务系统,比如说我们登录云闪付、淘宝这样的系统直接就可以购买东西,顶多就是有一些什么VIP角色就够了,角色不会经常改变。但是内管系统就不一样了,比如一些人力资源系统,leader、总公司、分公司不同的用户拥有的权限是不一样的,而且这些权限是经常改变的。SpringSecurity对这两类系统都有很好的支持。


3.如何把控权限

  一般我们的应用系统都会两个信息,一个是系统配置信息,配置了对应的资源需要什么的权限。一个是用户权限信息,存储了用户对应的权限。所以用户访问某个资源的时候,我们可以通过资源查找需要的权限,通过用户查找其所拥有的权限然后判断用户是否能访问该资源


4.SpringSecurity授权原理解析

  作为终章,我们的Filter基本上也画的差不多了,授权当中我们最关系的是最后3个Filter,如下图所示:


4.1AnonymousAuthenticationFilter

  这个Filter作为绿色Filter当中的最后一环,做了什么事情呢?



  如果说前面的Filter没有把用户的Authentication放到Context中,这个时候Filter会创建一个名为anonymousUser的Authentication,然后传递到后面的FilterSecurityInterceptor,由它来决定是否能问restApi。

4.2请求鉴权流程

  先看一张图,可能东西有点多,不过我们一点点来解释。



  当一个服务请求到达之后,经历到FilterSecurityInterceptor的时候,会由3个很重要的东东来决定是否允许访问后面的资源。第一个是SecurityConfig生成的ConfigAttribute,这个就是我们代码里面写的这些东西:



  第二个是SecurityContextHolder中的Authentication,其实就是看看用户的权限有哪些。第三个很重要就是AccessDecisionManager,决定了是否访问的生杀大权。它是一个接口,有一个抽象实现类AbstractAccessDecisionManager,它利用AccessDecisionVoter进行投票,它包含了一组投票器,但是版本更新升级后,现在由一个叫做WebExpressionVoter包揽了所有的投票工作。AbstractAccessDecisionManager这个抽象实现类有3个实现类就是图中的3个绿色方框,不过Spring主要用的是AffirmativeBased这个实现类,它是什么意思呢?如果说投票组中有一个否认就拒绝,相当于一票否决权。UnanimousBased是一票肯定权~~

5.权限表达式

  SpringSecurity内置的权限表达式如下:


5.1实战

  我们的一个整体思路如下图所示:



我们希望把公共的权限模块放到权限模块中,关于用户的权限模块单独放到一个模块中,如果用户自己想设置权限可以在自己的模块中实现对应的权限。所以我们需要定义一个AuthorizeConfigProvider接口由各个模块去实现,放入自己想要的权限。在定义一个AuthorizeConfigManager来统一收集所有的Provider,使其生效。

5.1.1 AuthorizeConfigProvider接口

public interface AuthorizeConfigProvider {
    
    void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config);

}

config里面的参数就是authorizeRequests()方法里面返回的对象。

5.1.2 ImoocAuthorizeConfigProvider

ImoocAuthorizeConfigProvider对AuthorizeConfigProvider的实现,其实我们把之前APP和BROWSER中的一些公共配置放到这里面来了。

@Component
@Order(Integer.MIN_VALUE)
public class ImoocAuthorizeConfigProvider implements AuthorizeConfigProvider {

    @Autowired
    private SecurityProperties securityProperties;
    
    @Override
    public void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) {
        config.antMatchers(
                SecurityConstants.DEFAULT_UNAUTHENTICATION_URL,
                SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE,
                SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_OPENID,
                securityProperties.getBrowser().getLoginPage(),
                SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX+"/*",
                securityProperties.getBrowser().getSignUpUrl(),
                securityProperties.getBrowser().getSession().getSessionInvalidUrl(),
                securityProperties.getBrowser().getSignOutUrl())
        .permitAll();
    }

}

5.1.3 AuthorizeConfigManager

用于组合Provider的接口

public interface AuthorizeConfigManager {
    
    void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config);

}

5.1.4ImoocAuthorizeConfigManager

ImoocAuthorizeConfigManager对AuthorizeConfigManager的实现,这里它遍历所有的provider,并把其中的配置放进去。

@Component
public class ImoocAuthorizeConfigManager implements AuthorizeConfigManager {
    
    @Autowired
    private List<AuthorizeConfigProvider> authorizeConfigProviders;

    @Override
    public void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) {
        for (AuthorizeConfigProvider authorizeConfigProvider : authorizeConfigProviders) {
            authorizeConfigProvider.config(config);
        }
    }
}

5.1.5 BrowserSecurityConfig

  authorizeConfigManager.config(http.authorizeRequests());一句话把之前的代码省略了~~如下图所示:


被省略的代码

具体实现:

@Configuration
public class BrowserSecurityConfig extends AbstractChannelSecurityConfig {

    @Autowired
    private SecurityProperties securityProperties;
    
    @Autowired
    private DataSource dataSource;
    
    @Autowired
    private UserDetailsService userDetailsService;
    
    @Autowired
    private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;
    
    @Autowired
    private ValidateCodeSecurityConfig validateCodeSecurityConfig;
    
    @Autowired
    private SpringSocialConfigurer imoocSocialSecurityConfig;
    
    @Autowired
    private SessionInformationExpiredStrategy sessionInformationExpiredStrategy;
    
    @Autowired
    private InvalidSessionStrategy invalidSessionStrategy;
    
    @Autowired
    private LogoutSuccessHandler logoutSuccessHandler;
    
    @Autowired
    private AuthorizeConfigManager authorizeConfigManager;
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        
        applyPasswordAuthenticationConfig(http);
        
        http.apply(validateCodeSecurityConfig)
                .and()
            .apply(smsCodeAuthenticationSecurityConfig)
                .and()
            .apply(imoocSocialSecurityConfig)
                .and()
            .rememberMe()
                .tokenRepository(persistentTokenRepository())
                .tokenValiditySeconds(securityProperties.getBrowser().getRememberMeSeconds())
                .userDetailsService(userDetailsService)
                .and()
            .sessionManagement()
                .invalidSessionStrategy(invalidSessionStrategy)
                .maximumSessions(securityProperties.getBrowser().getSession().getMaximumSessions())
                .maxSessionsPreventsLogin(securityProperties.getBrowser().getSession().isMaxSessionsPreventsLogin())
                .expiredSessionStrategy(sessionInformationExpiredStrategy)
                .and()
                .and()
            .logout()
                .logoutUrl("/signOut")
                .logoutSuccessHandler(logoutSuccessHandler)
                .deleteCookies("JSESSIONID")
                .and()
            .csrf().disable();
        
        authorizeConfigManager.config(http.authorizeRequests());
        
    }

    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
//      tokenRepository.setCreateTableOnStartup(true);
        return tokenRepository;
    }
    
}

5.1.6 应用自定义DemoAuthorizeConifgProvider

  应用自定义的DemoAuthorizeConifgProvider会被装载到容器中,和core模块中的配置同时生效。这是因为都被Manager扫描到了。

@Component
public class DemoAuthorizeConifgProvider implements AuthorizeConfigProvider {

    /* (non-Javadoc)
     * @see com.imooc.security.core.authorize.AuthorizeConfigProvider#config(org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry)
     */
    @Override
    public void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) {
        
        config.antMatchers("/demo.html")
            .hasRole("ADMIN");
    }

}

OK,一个简单的SpringSecurity授权应用就搭建好了。

6.RBAC模型

  上面的实战是针对简单的权限规则搭建的,这里我们要针对复杂的权限规则讲一讲。这里要用到RBAC模型。RBAC模型由五张表组成,其中3张是实体表,2张是关系表。如下图所示:



  人力资源的小MM可以添加删除用户表中的内容,同时也可以添加角色,所以用户表角色表是由业务人员维护滴。资源表主要是菜单、按钮对应的URL,这个由开发人员维护。而且这个模型中,只有这张表是开发人员来维护。至于实现,这里就不贴出来了,需要代码的小伙伴可以私信我。
SpringSecurity系列今天就算结束了,从6.1号到6.23号,耗费的时间还是蛮长的~预先透露下一个专题系列:ElasticSearch

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容