一、前提
书接上回 SpringSecurity5.7+最新案例 -- 用户名密码+验证码+记住我······
本文 继续处理SpringSecurity授权 ......
目前由 难 -> 简,即自定义数据库授权,注解授权,config配置授权
二、自定义授权
0. 数据准备
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`age` int(11) NULL DEFAULT NULL,
`address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`phone` varchar(13) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`passwd` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`pwd` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`role` json NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user
-- ----------------------------
BEGIN;
INSERT INTO `user` VALUES (1, 'a', 35, '太原', '11', '$2a$10$tbFi2xQwHQcjIp4jt5eGZ..CER7ws.Pzg9RiBM1tTGH1omkNprXqC', '123', '[{\"name\": \"系统管理员\", \"role\": \"ROLE_ADMIN\"}, {\"name\": \"普通用户\", \"role\": \"ROLE_USER\"}]');
INSERT INTO `user` VALUES (2, 'b', 51, '沧州', '22', '$2a$10$c8.tVJ36cqcTHV/lGugyc.uf3ft9nQ4jNJpqhhWESpAv7uOspDg1K', '456', '[{\"name\": \"普通用户\", \"role\": \"ROLE_USER\"}]');
INSERT INTO `user` VALUES (3, 'c', 43, '兖州', '33', '$2a$10$akbjbzIjdGjy3/4sUvizae11LvEZXxLUPO0n.4x1NfPupCJYI4aUm', '789', '[{\"name\": \"游客\", \"role\": \"ROLE_GUEST\"}]');
COMMIT;
-- ----------------------------
-- Table structure for menu
-- ----------------------------
DROP TABLE IF EXISTS `menu`;
CREATE TABLE `menu` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`pattern` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`role` json NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of menu
-- ----------------------------
BEGIN;
INSERT INTO `menu` VALUES (1, '/admin/**', '[{\"name\": \"系统管理员\", \"role\": \"ROLE_ADMIN\"}]');
INSERT INTO `menu` VALUES (2, '/user/**', '[{\"name\": \"普通用户\", \"role\": \"ROLE_USER\"}]');
INSERT INTO `menu` VALUES (3, '/guest/**', '[{\"name\": \"普通用户\", \"role\": \"ROLE_USER\"}, {\"name\": \"游客\", \"role\": \"ROLE_GUEST\"}]');
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
1. 说明
由于使用的是jpa,以及在mysql中使用到了json数据,所以先在项目中引入json支持, 使用 mybatis 的绕行
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>2.3.5</version>
</dependency>
2. UserDstail变化
3. SecurityConfig中配置变化如下
4. 实现 FilterInvocationSecurityMetadataSource
@Component
public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
// 这里注入的查询menu的接口类
final MenuRepository menuRepository;
public MySecurityMetadataSource(MenuRepository menuRepository) {
this.menuRepository = menuRepository;
}
AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
String requestURI = ((FilterInvocation) object).getRequest().getRequestURI();
// 获取无需权限就能访问的公共资源, 不需权限的访问,直接返回,不需要在进入数据量中查询
String[] pubSource = PublicRequestSource.get();
for (String source : pubSource) {
if (antPathMatcher.match(source, requestURI)) {
return null;
}
}
// 如果要访问的资源不在公共资源里面,则去数据库中查询,然后创建访问权限
List<MenuEntity> allMenu = menuRepository.findAll();
for (MenuEntity menu : allMenu) {
if (antPathMatcher.match(menu.getPattern(), requestURI)) {
List<RoleEntity> role = menu.getRole();
String[] roles = role.stream().map(RoleEntity::getRole).toArray(String[]::new);
return SecurityConfig.createList(roles);
}
}
return null;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
5. 配置公共资源
-- 也可以写在配置文件中
public class PublicRequestSource {
public static String[] get() {
return new String[]{
"/hello", "/login"
};
}
}
6. 实现ObjectPostProcessor
@Component
public class MyObjectPostProcessor implements ObjectPostProcessor<FilterSecurityInterceptor> {
@Resource
private MySecurityMetadataSource mySecurityMetadataSource;
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setSecurityMetadataSource(mySecurityMetadataSource);
// 打开公共资源访问权限
object.setRejectPublicInvocations(false);
return object;
}
}
7. 创建TestController测试请求接口权限
// 实际这里会写在多个controller类下,这里为了测试
@RestController
public class TestController {
@GetMapping("/admin/hello")
public String admin() {
return "hello admin";
}
@GetMapping("/user/hello")
public String user() {
return "hello user";
}
@GetMapping("/guest/hello")
public String guest() {
return "hello guest";
}
@GetMapping("/hello")
public String hello() {
return "hello";
}
}
8. 然后用postman登录测试接口把
嗯,其它的就不测试了......
三、注解授权
1. 开启全局授权
@SpringBootApplication
// 开启全局授权(详细解释如下)
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SplitBackWeb03Application {
public static void main(String[] args) {
SpringApplication.run(SplitBackWeb03Application.class);
}
}
- perPostEnabled: 开启 Spring Security 提供的四个权限注解,@PostAuthorize、@PostFilter、@PreAuthorize 以及@PreFilter。
- securedEnabled: 开启 Spring Security 提供的 @Secured 注解支持,该注解不支持权限表达式
- jsr250Enabled: 开启 JSR-250 提供的注解,主要是@DenyAll、@PermitAll、@RolesAll 同样这些注解也不支持权限表达式
# 以上注解含义如下:
- @PostAuthorize: 在目前标方法执行之后进行权限校验。
- @PostFiter: 在目标方法执行之后对方法的返回结果进行过滤。
- @PreAuthorize:在目标方法执行之前进行权限校验。
- @PreFiter:在目前标方法执行之前对方法参数进行过滤。
- @Secured:访问目标方法必须具各相应的角色。
- @DenyAll:拒绝所有访问。
- @PermitAll:允许所有访问。
- @RolesAllowed:访问目标方法必须具备相应的角色。
这些基于方法的权限管理相关的注解,一般来说只要设置 prePostEnabled=true
就够用了。
2. 注意事项
当开启了开启全局授权后,无需额外配置(即SecurityConfig最简如下),当然,也可以和自定义授权一起使用(但不推荐)
httpSecurity.formLogin().and().csrf().disable();
3. 创建测试controller类
@RestController
@RequestMapping("/hello")
public class AuthorizeMethodController {
// 当角色为 ADMIN 并且 认证的用户名为“a”时,才允许访问,否则403
@PreAuthorize("hasRole('ADMIN') and authentication.name=='a'")
@GetMapping
public String hello() {
return "hello";
}
// 当认证的用户名 和 参数中传递的用户名 一致 时,才允许访问,否则403
@PreAuthorize("authentication.name==#name")
@GetMapping("/name")
public String hello(String name) {
return "hello:" + name;
}
// 过滤作用,如传递的UserList id为1,2,3的对象,则后台实际收到的是id为1,3的对象的数据
// filterTarget 必须是数组 集合 类型
@PreFilter(value = "filterObject.id%2!=0",filterTarget = "users")
@PostMapping("/users")
public void addUsers(@RequestBody List<User> users) {
System.out.println("users = " + users);
}
// 认证作用,当参数中的id=1的时候,可以返回相应的数据,否则403
@PostAuthorize("returnObject.id==1")
@GetMapping("/userId")
public User getUserById(Integer id) {
return new User(id, "mose");
}
// 过滤作用,如结果集中的UserList id为1,2,3的对象,则返回实际的对象只有id 为 2 的对象
// 返回数据 最好是 数组 或者集合,不然无效
@PostFilter("filterObject.id%2==0")
@GetMapping("/lists")
public List<User> getAll() {
return userService.findAll();
}
// 只能判断角色,当角色为 ROLE_USER 可访问
@Secured({"ROLE_USER"})
@GetMapping("/secured")
public User getUserByUsername() {
return new User(99, "secured");
}
// 只能判断角色,当角色具有其中一个即可
@Secured({"ROLE_ADMIN","ROLE_USER"})
@GetMapping("/username")
public User getUserByUsername2(String username) {
return new User(99, username);
}
// 允许所有
@PermitAll
@GetMapping("/permitAll")
public String permitAll() {
return "PermitAll";
}
// 拒绝所有
@DenyAll
@GetMapping("/denyAll")
public String denyAll() {
return "DenyAll";
}
// 角色判断,当具有其中一个角色即可
@RolesAllowed({"ROLE_ADMIN","ROLE_USER"})
@GetMapping("/rolesAllowed")
public String rolesAllowed() {
return "RolesAllowed";
}
}
四、配置授权
1. 在securityConfig中配置即可,如下
httpSecurity.authorizeHttpRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.antMatchers("/guest").hasRole("GUEST")
.anyRequest().authenticated()
.and().formLogin()
.and().csrf().disable();
2. 解释
方法 | 说明 |
---|---|
hasAuthority(String authority) | 当前用户是否具备指定权限 |
hasAnyAuthority(String... authorities) | 当前用户是否具备指定权限中任意一个 |
hasRole(String role) | 当前用户是否具备指定角色 |
hasAnyRole(String... roles); | 当前用户是否具备指定角色中任意一个 |
permitAll(); | 放行所有请求/调用 |
denyAll(); | 拒绝所有请求/调用 |
isAnonymous(); | 当前用户是否是一个匿名用户 |
isAuthenticated(); | 当前用户是否已经认证成功 |
isRememberMe(); | 当前用户是否通过 Remember-Me 自动登录 |
isFullyAuthenticated(); | 当前用户是否既不是匿名用户又不是通过 Remember-Me 自动登录的 |
hasPermission(Object targetId, Object permission); | 当前用户是否具备指定目标的指定权限信息 |
hasPermission(Object targetId, String targetType, Object permission); | 当前用户是否具备指定目标的指定权限信息 |