一.简介
1.1概念
所谓授权,举个例子:某个用户想要访问某个资源(接口、页面、功能等),我们应该先去检查该用户是否具备对应的权限,如果具备就允许访问,如果不具备,则不允许访问。也就是说,授权是用来判断控制用户是否就有相应权限的问题的!
在我们认证之后,才会考虑授权的问题,也就是你得先登录进来,然后才有资格判断你有没有权限
1.2粒度
在开始授权之前,先讲下什么叫"粒度",授权粒度就是对授权细分的级别、程度的意思。
在Spring Security中,授权粒度有如下几种:
- 支持基于 URL 的请求授权
- 基于方法访问的授权
- 基于对象访问的授权
一共3种授权粒度,但是对于开发来说,常用的是基于URL和方法两种粒度进行授权。
1.3授权实现方式
想实现授权,可以采用如下方式:
- 基于内存模型实现授权
- 基于默认数据库模型实现授权
- 基于自定义数据库模型实现授权
这篇文章中先学习基于内存模型来实现授权的方法,但是对于该实现方案,在真正开发时几乎不用。只做学习使用。
二. 创建SpringSecurity项目
参考之前的文章,这边不做叙述。
三. 基于内存模型实现授权
3.1创建测试接口
在开始授权代码之前,先创建3个用于测试的Web接口,分别供3个不同的用户角色来进行操作。
3.1.1创建/admin/hello接口:
AdminController 类代码如下:
@RestController
@RequestMapping("/admin")
public class AdminController {
@GetMapping("/hello")
public String hello() {
return "hello, admin";
}
}
3.1.2创建/user/hello接口:
UserController 类代码如下:
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("hello")
public String hello() {
return "hello, user";
}
}
3.1.3创建/visitor/hello接口:
VisitorController 类代码如下:
@RestController
@RequestMapping("/visitor")
public class VisitorController {
@GetMapping("/hello")
public String hello() {
return "hello, visitor";
}
}
对于以上三个接口,做如下解释:
- /visitor/hello 任何人都可以访问;
- /admin/hello 具有 admin 角色的人才能访问;
- /user/hello 具有 user 角色的人才能访问;
- 所有 user 角色能够访问的接口资源,admin 角色也都能够访问。
4.2配置资源访问权限
先定义一个SecurityConfig配置类,在该类中对各接口进行授权配置,代码如下:
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**")
.hasRole("ADMIN")
.antMatchers("/user/**")
.hasAnyRole("USER","ADMIN")
.antMatchers("/visitor/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll();
}
}
上面代码含义如下:
- 如果请求的路径是 “/admin/**” 开头的接口,用户需要具有 admin 角色;
- 如果请求的路径是 “/user/**” 开头的接口,用户需要具有 user 或 admin 角色;
- 如果请求的路径是 “/visitor/**” 开头的接口,哪个用户都可以随便访问;
- 剩余的其他格式的请求路径,必须认证(登录)后才可以访问。
注意:
代码中配置规则的顺序非常重要!和 Shiro 类似,Spring Security 在匹配的时候也是按照从上往下的顺序来进行匹配,一旦匹配到了就不再继续匹配了。
利用上面的配置规则,我们就可以对相应的接口进行限制了,这时候这些接口,就不是你想访问就能访问的了,你必须拥有对应的角色或者权限才行。
4.3Ant匹配符
采用了 Ant 风格的路径匹配符,它的匹配规则如下:
4.4基于内存模型创建用户角色
对各种接口进行限制后,接下来我们要给用户分配角色权限才行,否则用户是没办法访问有些接口的。那么怎么给用户分配角色呢?
在Spring Security4.x版本中,我们登陆的用户有一个默认的ROLE_USER角色,但是在Spring Security5.x版本中,把这个默认的角色给去掉了。此时访问我们的接口时,如果用户没有被分配相关的角色权限,即使用户登录成功,也未必可以访问接口。只能自己给用户手动分配角色。
Spring Security中基于内存创建用户角色,有两种方式:
- 在configure(AuthenticationManagerBuilder auth)方法中定义;
- 创建UserDetailsService对象。
这两种方式其实用哪一种都可以,无所谓优劣,看个人习惯。
4.5创建分配角色的第一种方式configure()
先来看看第一种实现方式,这个实现方式主要是利用AuthenticationManagerBuilder来进行实现。
在上面创建的SecurityConfig类中,添加一个新的configure()方法,创建用户,并给该用户分配用户名、密码、角色权限等信息,并对该用户的密码采用不加密。代码如下:
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
......
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser(User.withUsername("admin").password("123").roles("ADMIN", "USER").build())
.withUser(User.withUsername("user").password("123").roles("USER").build())
//设置密码编码器
.passwordEncoder(NoOpPasswordEncoder.getInstance());
}
......
}
通过这几行代码,我们就在内存中创建了2个用户,分别是admin和user用户,密码都是123。
4.6创建分配角色的第二种方式UserDetailsService
第二种创建方式,这种创建方式主要是利用InMemoryUserDetailsManager来实现。我们还是在上面的SecurityConfig类中,编写相应代码。
这种方式中,我们创建用户和分配密码,与第一种方式基本类似,但是密码编码器需要单独配置,通过@Bean直接生成PasswordEncoder对象即可。代码如下:
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
.......
/**
* 内存中创建多个用户角色的方式2:
* 基于内存的多用户支持.在内存中创建多个用户与角色.
*/
@Bean
public UserDetailsService createUserDetailService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
//在内存中创建admin与user用户
manager.createUser(User.withUsername("admin").password("123").roles("ADMIN", "USER").build());
manager.createUser(User.withUsername("user").password("123").roles("USER").build());
return manager;
}
/**
* 由于5.x版本之后默认启用了委派密码编码器,因而按照以往的方式设置内存密码将会读取异常,所在需要暂时将密码编码器设置为NoOpPasswordEncoder.
* 后面我们可以修改成BCryptPasswordEncoder.
* 这里必须设置一个密码编码器,否则无法通过对用户名和密码的验证.
*/
@Bean
public PasswordEncoder passwordEncoder() {
//return new BCryptPasswordEncoder();
return NoOpPasswordEncoder.getInstance();
}
.......
}
InMemoryUserDetailsManager是UserDetailsService接口中的一个实现类,InMemoryUserDetailsManager的类结构图如下:
它可以把用户数据保存在内存中,在一些不需要引入数据库的场景下很有用,也就是如果我们不想持久化保存用户角色信息,用InMemoryUserDetailsManager类的createUser()方法生成用户,并赋予相应的角色即可。
五.验证
项目启动起来,然后以不同的身份登录进来,分别访问不同的接口,当某个用户在不具备相应角色时,会出现403提示信息。只有具有相应的角色权限时,才可以访问对应的接口。截图如下: