基于内存的授权操作
写在前面
本篇来学习Spring Security中的授权操作,建议先阅读之前Shiro框架相关的几篇内容,通过对比学习可以加深对授权的理解。
授权
授权就是当用户通过认证之后,需要访问某一资源的时候,我们需要检查用户是否具备访问该资源的权限,如果具备就允许访问;反之则不允许。
认证
我们知道用户想要进行授权,前提是已经通过了认证,而SpringSecurity存在多种认证方式,查看一下这个AuthenticationManagerBuilder类,可以发现它存在inMemoryAuthentication(内存)、jdbcAuthentication(数据库)和ldapAuthentication(LDAP)等三种认证方式:
1 | public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication() throws Exception { |
其实这些不同来源的数据认证都被被封装为一个UserDetailsService接口,任何实现了该接口的对象都可以作为认证的数据源,如下所示:
1 | public interface UserDetailsService { |
这个接口中只有一个loadUserByUsername方法,它通过用户名来加载用户信息。前面说过SpringSecurity中与Web安全相关的配置都在WebSecurityConfigurerAdapter
类中,而这类中有一个userDetailsService
方法:
1 | protected UserDetailsService userDetailsService() { |
因此开发者可以通过重写这个userDetailsService
方法,进而提供一个UserDetailsService
实例来配置用户信息。
在MyWebSecurityConfig类中新增一个userDetailsService
方法,里面的代码如下所示:
1 | @Bean |
当前之前基于内存的验证方式也是可以使用的,但是两者只能选择其中任意一种:
1 | @Override |
提供测试接口
为了后续测试需要,接下来提供一些测试接口。修改HelloController类中的代码为如下所示:
1 | @RestController |
对于这三个接口来说,/hello
接口是认证后所有人都可以访问的;/admin/hello
接口是认证后具有admin角色的人才可以访问;/user/hello
接口是认证后具有user角色的人才可以访问的;还有具备admin角色的人也能访问所有具备user角色才能访问的资源,言外之意就是具备admin角色的人自动具备user角色。
权限拦截设置
接下来设置权限的拦截规则,开发者只需在configure(HttpSecurity http)
方法中新增如下代码:
1 | http.authorizeRequests() |
可以看到这里我们采用了Ant风格的路径匹配符,Ant风格的路径匹配符在Spring全家桶中使用非常广泛,且易于使用。其中通配符**
表示匹配多层路径;*
表示匹配一层路径;?
表示匹配任意单个字符。
以之前在HelloController中提供的测试接口为例,当请求路径为/hello
,那么用户只需登录即可访问;当请求路径为/admin/hello
,那么用户需要登录且必须具备admin角色才能访问;当请求路径为/user/hello
,那么用户需要登录且必须具备user角色才能访问。
请注意上面三行代码的书写顺序,在前面学习Shiro框架的时候就说过,URL在匹配的时候是按照从上到下的顺序来匹配的,一旦匹配成功后续就不再匹配了,而SpringSecurity和Shiro一样,因此上述拦截规则非常重要。
如果开发者将anyRequest放在antMatchers的前面,即如下所示:
1 | http.authorizeRequests() |
此时启动项目,系统就会抛出异常,提示anyRequest不能放在antMatchers前面,因为anyRequest包括所有请求,而antMatchers只是其中的某一个请求,因此将anyRequest放在antMatchers前面是毫无意义的。也就是说anyRequest应该放在最后面,表示除了前面设置的拦截规则外,其余请求应当如何处理。(SpringSecurity 2.1.16版本不抛异常,但是后续测试权限访问会失效,即用户可以越权访问它不具备角色的资源。)
SpringSecurity框架中与规则拦截相关的配置类为AbstractRequestMatcherRegistry
,里面有一个antMatchers(HttpMethod method)
方法,这个方法源码如下所示:
1 | public C antMatchers(HttpMethod method) { |
可以看到它直接返回的就是匹配任意URL的规则,并没有旧版本中对anyRequest是否配置进行检测,因此启动时是不会抛出异常。
项目测试
接下来启动项目,然后以test用户登录,之后依次访问/hello
,/admin/hello
和/user/hello
这三个接口:
登录成功:
可以访问/hello
接口:
可以访问/user/hello
接口:
无法访问/admin/hello
接口:
之后使用envy用户进行登录,可以发现结果类似,由于envy用户不具备user角色,因此无法访问/user/hello
接口:
角色继承
现在有一个问题,就是之前我们要求的:具备admin角色的人也能访问所有具备user角色才能访问的资源,言外之意就是具备admin角色的人自动具备user角色。但是目前上述代码还未实现这个功能,需要使用角色继承来实现。
角色继承顾名思义就是让上一级角色自动具备下一级角色的权限。开发者只需在MyWebSecurityConfig
类中新增roleHierarchy
方法,并设置角色继承信息:
1 | //角色继承 |
请注意,在配置角色继承时,需要给角色添加ROLE_
前缀,查看一下之前test用户登录时的信息:
看到没有在这个authority中,之前设置的user变成了ROLE_user,而这个就是user权限信息,同时ROLE_admin > ROLE_user
表示ROLE_admin
权限自动具备ROLE_user
权限。
之后重新启动项目,让envy用户登录,之后访问/user/hello
接口,可以看到此时由于envy用户具备admin角色,而admin角色自动具备user角色,因此现在envy用户是可以访问该接口的:
现在有一个问题就是明明开发者在userDetailsService方法中设置的用户角色是没有添加ROLE_
前缀的,怎么后续会有这个前缀呢?
1 | @Bean |
点击这个roles,查看一下它的源码,发现这其实是一个roles方法:
1 | public User.UserBuilder roles(String... roles) { |
仔细看这个遍历的代码,可以发现当用户设置的role中以ROLE_
开头时,那么就返回role和一句提示语:不能以ROLE_
开头,它会自动添加ROLE_
。Assert.isTrue(boolean,expression)
方法是当boolean为true则不抛出异常,如果boolean为false,则抛出异常并执行expression语句。
开发者可以将之前的userDetailsService方法进行修改:
1 | @Bean |
之后启动项目,可以发现控制台的确输出上述信息:
1 | Factory method 'userDetailsService' threw exception; nested exception is java.lang.IllegalArgumentException: ROLE_admin cannot start with ROLE_ (it is automatically added) |
这样本篇关于Spring Security中基于内存授权操作的学习就到此为止,后续学习如何基于数据库进行认证。