这几天在研究开源 IoT 框架————Thingsboard ,Tb 使用了 Spring Security 来进行用户权限控制。之前一直想深入学习一下 Spring Security,借这个机会正好研究一下。
一、基本原理
Spring Security 是借助过滤器来协助完成的。
二、Getting Start
1.引入 Spring Security 包
第一步,当然是用 Maven 引入对应的包,我这里是一个 Spring Boot 项目,直接使用 spring-boot-starter-security 即可
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
配置完成后,我们新建一个 Controller 进行测试,代码如下
@RestController
public class TestController {
@GetMapping("/hello")
public String hello(){
return "hello";
}
}
完成后,我们重新启动项目,访问 localhost:8080/hello
页面提示需要登录
使用用户名user
和从控制台找到第一次启动时生成的密码登录,就可以看到我们在 TestController 中定义的内容了。
访问每一个接口都需要先登录,这并非我们想要的,那么能不能选择一部分接口要登录,而另一部分可以直接访问呢?答案就是————WebSecurityConfigurerAdapter。
2.创建配置类 WebSecurityConfigurerAdapter
我们创建一个 MySecurityConfiguration 使其继承自 WebSecurityConfigurerAdapter。
并重写其configure(HttpSecurity http)方法、configure(AuthenticationManagerBuilder auth)方法。
@Configuration
@EnableWebSecurity
public class MySecurityConfiguration extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder(){
return new PasswordEncoder() {
@Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}
@Override
public boolean matches(CharSequence charSequence, String s) {
return Objects.equals(charSequence.toString(),s);
}
};
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password("admin").roles("ADMIN");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/**").hasRole("ADMIN")
.and() .formLogin()
.loginProcessingUrl("/login")
.permitAll()
.and().csrf().disable();
}
}
其中 configure(AuthenticationManagerBuilder auth) 定义了存储在内存的角色或权限,这里我们新建了一个 ADMIN 角色;
而 configure(HttpSecurity http) 定义了访问哪些接口需要什么样权限或角色,这里是/api
下面的接口需要 ADMIN 角色才能访问
至于其他的部分,我们留到后面再讨论。
此时我们再创建一个名为 AdminController 的类,该类提供了一个 /api/admin
的接口
@RestController
@RequestMapping("/api")
public class AdminController {
@GetMapping("/admin")
public String getAdminById(AdminId adminId){
return "admin";
}
}
此时我们重新启动项目,和我们预想的一样————访问/api/admin
接口提示我们需要登录,而之前的/hello
接口不需要登录就能访问了。
三、核心配置———WebMvcConfigurerAdapter
四、注解
Controller 其中使用到了 Spring Security 的这几个注解
1.@PreAuthorize
在操作前进行权限的校验,在 Thingboard 中 Controller
大部分都是通过 @PreAuthorize 来实现鉴权的。
例如:AdminController
中的 getAdminSettings()
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/settings/{key}", method = RequestMethod.GET)
@ResponseBody
public AdminSettings getAdminSettings(@PathVariable("key") String key) throws ThingsboardException {
//省略了其他代码
.....
}
通过hasAuthority()
参数来指定调用该方法所需的权限级别。
例如:调用 getAdminSettings()
方法时就会先验证用户是否为系统管理员。
此外,@PreAuthorize
也支持 Spring 表达式语言,提供了基于表达式的访问控制。
//同时拥有normal和admin权限才可以使用被修饰的方法
@PreAuthorize("hasAuthority('TENANT_NORMAL') AND hasAuthority('TENANT_ADMIN')")
我们可以通过 AND 连接两个hasAuthority
来表示需要同时拥有 normal 和 admin 权限才可以使用.
(1)hasAuthority()
我们可以从的 security.access.expression 包下的SecurityExpressionRoot
public final boolean hasAuthority(String authority) {
return this.hasAnyAuthority(authority);
}
public final boolean hasAnyAuthority(String... authorities) {
return this.hasAnyAuthorityName((String)null, authorities);
}
public final boolean hasRole(String role) {
return this.hasAnyRole(role);
}
public final boolean hasAnyRole(String... roles) {
return this.hasAnyAuthorityName(this.defaultRolePrefix, roles);
}
//上面的方法最终都会将数据交给 hasAnyAuthorityName 进行处理
private boolean hasAnyAuthorityName(String prefix, String... roles) {
Set<String> roleSet = this.getAuthoritySet();
String[] var4 = roles;
int var5 = roles.length;
for(int var6 = 0; var6 < var5; ++var6) {
String role = var4[var6];
String defaultedRole = getRoleWithDefaultPrefix(prefix, role);
if (roleSet.contains(defaultedRole)) {
return true;
}
}
return false;
}
从 hasAuthority 的源码中我们可以看到,无论我们使用hasAuthority()
还是hasAnyAuthority()
,最终都会交给hasAnyAuthorityName()
来进行处理。
hasAnyAuthorityName()
使用getAuthoritySet()
方法获取到权限的集合,再判断是否和传入的参数契合,契合则返回 true,否则返回 flase。
评论区