开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 28 天,点击查看活动详情
在前面几篇文章中,我介绍了如何使用 Spring Security 框架进行安全认证。 今天,我将介绍如何为不同的 URL 配置不同的认证规则。
01-创建 API 接口
继续沿用 Spring Boot「41」一文搞懂 Spring Security 是如何工作的? 中的 HelloWorld 程序。 在原来程序的基础上,增加两个接口 User 列表接口:
@RestController
public class UserController {
@Autowired
private DemoUserRepository userRepository;
@GetMapping("/users")
public List<DemoUser> getUsers() {
List<DemoUser> all = getUserRepository().findAll();
return all;
}
public DemoUserRepository getUserRepository() {
return userRepository;
}
}
Permission 接口:
@RestController
public class PermissionController {
@Autowired
private DemoPermissionRepository permissionRepository;
@GetMapping("/permissions")
public List<DemoPermission> getPermissions() {
List<DemoPermission> all = getPermissionRepository().findAll();
return all;
}
public DemoPermissionRepository getPermissionRepository() {
return permissionRepository;
}
}
02-基于 URL 设置安全相关配置
使用 Spring Security 时,默认情况下,访问所有 URL 都会被拦截,若未认证,则不允许用户访问。 接下来,针对上节增加的两个 API 接口,我们打算制定如下的访问控制规则:
- 对于 /users 开头的 URL,允许所有用户访问。
- 针对 /permissions 开头的 URL,只允许具备 ADMIN 权限的用户访问。
这种访问控制如何在 Spring Security 框架中实现呢? 可以通过向 ApplicationContext 中注入一个 SecurityFilterChain 实现。 我们来看下代码:
@Configuration
public class SecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests()
.antMatchers("/users/**") // /users 开头的 URL 允许所有人访问
.permitAll()
.and()
.authorizeHttpRequests()
.antMatchers("/permissions/**") // /permissions 开头的 URL 只允许 ADMIN 访问
.hasRole("ADMIN")
.anyRequest()
.authenticated()
.and()
.httpBasic();
return http.build();
}
}
下面我来简单解释一下上面配置的含义。
authorizeHttpRequests()
意为将框架配置为,在控制用户访问时,使用如下的规则。antMatchers("/users/**").permitAll()
意为对满足这种 URL 规则的访问,使用 permitAll 规则,即允许任何人访问。and()
指与下面的规则同时生效antMatchers("/permissions/**").hasRole("ADMIN")
意为对满足这种 URL 规则的访问,访问者必须具有 ADMIN 角色。若没有认证通过,则返回 “401 Unauthorized”; 对于认证通过,但是不具备 ADMIN 角色的请求,则返回 “403 Forbidden”。anyRequest().authenticated()
是默认配置,针对的是前面规则约束不到的请求,例如 "/roles" 请求并不匹配前面两种规则,则适用默认配置。这里要求其他请求必须是认证过的。 需要注意的是,默认配置只能有一个,且需要在规则的最后面。
03-基于内存创建用户
有了上述的配置,我们来创建两个用户。 这里我们使用 InMemoryUserDetailsManager,即基于内存的用户信息存储,不用依赖数据库,测试起来更方便。
@Bean(name = "userDetailsService")
public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
User.UserBuilder users = User.withDefaultPasswordEncoder();
UserDetails user = users
.username("user")
.password("password")
.roles("USER")
.build();
UserDetails admin = users
.username("admin")
.password("password")
.roles("USER", "ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
我创建了两个用户:其中一个名为 user,它具有 USER 角色;另一个名为 admin,它具有 ADMIN 角色。
04-运行测试用例
上述工作完成之后,让我们启动 HelloWorld 应用。 测试 http://localhost:8080/users 是否是不需要验证就能访问:
curl -i http://localhost:8080/users
HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 02 Mar 2023 14:14:17 GMT
[{"userId":1,"username":"admin","password":"{noop}admin","locked":0,"ctime":20230211143234,"enabled":true,"accountNonExpired":true,"credentialsNonExpired":true,"accountNonLocked":true,"authorities":null},{"userId":2,"username":"lihua","password":"{noop}lihua123","locked":0,"ctime":20230211143234,"enabled":true,"accountNonExpired":true,"credentialsNonExpired":true,"accountNonLocked":true,"authorities":null},{"userId":3,"username":"hanmeimei","password":"{noop}hanmeimei123","locked":0,"ctime":20230211143234,"enabled":true,"accountNonExpired":true,"credentialsNonExpired":true,"accountNonLocked":true,"authorities":null}]
能够拿到用户列表,说明访问成功了。
下面,我们来测试下 http://localhost:8080/permission 能否访问:
curl -i http://localhost:8080/permissions
HTTP/1.1 401
Set-Cookie: JSESSIONID=A5000511919EE9745B91E5CA0B704AE6; Path=/; HttpOnly
WWW-Authenticate: Basic realm="Realm"
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Length: 0
Date: Thu, 02 Mar 2023 14:14:25 GMT
结果是 401,说明需要认证。接下来,我们添加用户名、密码来访问:
curl -u admin:password -i http://localhost:8080/permissions
HTTP/1.1 200
Set-Cookie: JSESSIONID=E9A29F2BAE0D1708649439D21E04BEDB; Path=/; HttpOnly
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 02 Mar 2023 14:18:41 GMT
[{"permissionId":1,"name":"新增用户","permissionValue":"user:add","status":1,"authority":"user:add"},{"permissionId":2,"name":"删除用户","permissionValue":"user:delete","status":1,"authority":"user:delete"},{"permissionId":3,"name":"查看用 户","permissionValue":"user:get","status":1,"authority":"user:get"}]
结果是 200,说明我们访问成功了。 最后,我们测试下,没有 ADMIN 权限的用户 user,访问 http://localhost:8080/permissions 是什么反应?
curl -u user:password -i http://localhost:8080/permissions
HTTP/1.1 403
Set-Cookie: JSESSIONID=F3FF230E5A1741E7146BD5A1DED66B63; Path=/; HttpOnly
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 02 Mar 2023 14:19:51 GMT
{"timestamp":"2023-03-02T14:19:51.685+00:00","status":403,"error":"Forbidden","path":"/permissions"}
返回结果是 403,拒绝访问,跟我们的预期一致。
05-总结
今天,我介绍了如何在 Spring Security 中配置不同的 URL 需要不同的认证级别。 并且,基于之前的 HelloWorld 程序,实现了基于 URL 的访问控制。最后,对实现的程序进行了验证,符合起初的预期。
希望今天的内容能对你有所帮助。