飙血推荐
  • HTML教程
  • MySQL教程
  • JavaScript基础教程
  • php入门教程
  • JavaScript正则表达式运用
  • Excel函数教程
  • UEditor使用文档
  • AngularJS教程
  • ThinkPHP5.0教程

Spring Security 基于URL的权限判断

时间:2021-12-07  作者:cjsblog  

1.  FilterSecurityInterceptor 源码阅读

域名域名域名erSecurityInterceptor

通过过滤器实现对HTTP资源进行安全处理。

该安全拦截器所需的 SecurityMetadataSource 类型为 FilterInvocationSecurityMetadataSource。 

doFilter方法中直接调用invoke方法

基本都是调用父类的方法,那下面就重点看下父类 AbstractSecurityInterceptor 中相关方法

为安全对象实现安全拦截的抽象类。

AbstractSecurityInterceptor 将确保安全拦截器的正确启动配置。 它还将实现对安全对象调用的正确处理,即:

  1. 从 SecurityContextHolder 获取 Authentication 对象。
  2. 通过在SecurityMetadataSource中查找安全对象请求,确定请求是与安全调用还是公共调用相关(PS:简单地来讲,就是看一下请求的资源是不是受保护的,受保护的就是安全调用,就要权限,不受保护的就不需要权限就可以访问)。
  3. 对于受保护的调用(有一个用于安全对象调用的 ConfigAttributes 列表):
    1. 如果 域名thenticated() 返回 false,或者 alwaysReauthenticate 为 true,则根据配置的 AuthenticationManager 对请求进行身份验证。 通过身份验证后,将 SecurityContextHolder 上的 Authentication 对象替换为返回值。
    2.  根据配置的AccessDecisionManager授权请求。
    3. 通过配置的RunAsManager执行任何run-as替换。
    4. 将控制权传递回具体的子类,它实际上将继续执行对象。返回一个 InterceptorStatusToken 以便在子类完成对象的执行后,其 finally 子句可以确保 AbstractSecurityInterceptor 被调用并使用 finallyInvocation(InterceptorStatusToken) 正确处理。
    5. 具体的子类将通过 afterInvocation(InterceptorStatusToken, Object) 方法重新调用 AbstractSecurityInterceptor。
    6. 如果 RunAsManager 替换了 Authentication 对象,则将 SecurityContextHolder 返回到调用 AuthenticationManager 后存在的对象。
    7. 如果定义了AfterInvocationManager,则调用它并允许它替换将要返回给调用者的对象。
  4.  对于公开的调用(安全对象调用没有 ConfigAttributes):
    1. 如上所述,具体的子类将返回一个 InterceptorStatusToken,在执行完安全对象后,该 InterceptorStatusToken 随后被重新呈现给 AbstractSecurityInterceptor。 AbstractSecurityInterceptor 在它的 afterInvocation(InterceptorStatusToken, Object) 被调用时不会采取进一步的行动。
  5. 控制再次返回到具体的子类,以及应该返回给调用者的对象。然后子类会将该结果或异常返回给原始调用者。

下面具体来看

从这里我们可以知道返回null和空集合是一样的。

接下来看授权

这是我们要重点关注的,可以看到,授权靠的是 域名de(authenticated, object, attributes) 

因此,我们想要实现自己的基于请求Url的授权只需自定义一个 AccessDecisionManager 即可

接下来,我们来具体实现一下

2.  自定义基于url的授权

先把Spring Security授权的大致流程流程摆在这儿:

自定义FilterInvocationSecurityMetadataSource

package 域名域名;

import 域名域名ermissionService;
import 域名域名域名wired;
import 域名域名igAttribute;
import 域名域名rityConfig;
import 域名域名erInvocation;
import 域名域名域名erInvocationSecurityMetadataSource;
import 域名域名onent;
import 域名.AntPathMatcher;

import 域名yList;
import 域名ection;
import 域名;
import 域名;

/**
 * @Author ChengJianSheng
 * @Date 2021/12/2
 */
@Component
public class MyFilterSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

    @Autowired
    private SysPermissionService sysPermissionService;

    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        FilterInvocation fi = (FilterInvocation) object;
        String url = 域名equestUrl();
        String httpMethod = 域名equest().getMethod();

        List<ConfigAttribute> attributes = new ArrayList<>();

        Map<String, String> urlRoleMap = 域名llUrlRole();

        for (域名y<String, String> entry : 域名ySet()) {
            if (域名h(域名ey(), url)) {
                return 域名teList(域名alue());
            }
        }

        // 返回null和空列表是一样的,都表示当前访问的资源不需要权限,所有人都可以访问
        return attributes;
//        return null;
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return 域名signableFrom(clazz);
    }
}

这里需要说明一下,其实Spring Security里面说的role不一定表示的是我们自己建的那个角色表,我们可以这样理解,就是它这里的所谓role只是一个权限标识。我们在建表的时候,通常最基本的是5张表(用户表、角色表、权限表、用户角色关系表、角色权限关系表),我们可以把受保护的资源(通常是一个url)与角色关联起来,建立哪些角色可以访问哪些资源,也可以直接判断资源的权限(通常是权限编码/标识)。

只要有这个关系,剩下的就是写法不同而已。如果你把role理解成资源的权限标识的话,那么返回的Collection<ConfigAttribute>中就最多有一个元素,如果理解成角色的话,那么可能有多个元素。就这么点儿东西,写法不同而已,本质是一样的。

自定义AccessDecisionManager

package 域名域名;

import 域名域名ssDecisionManager;
import 域名域名ssDeniedException;
import 域名域名igAttribute;
import 域名域名fficientAuthenticationException;
import 域名域名entication;
import 域名域名tedAuthority;
import 域名域名onent;

import 域名ection;

/**
 * @Author ChengJianSheng
 * @Date 2021/12/2
 */
@Component
public class MyAccessDecisionManager implements AccessDecisionManager {
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        Collection<? extends GrantedAuthority> authorities = 域名uthorities();
        域名tln(authorities);
        域名tln(configAttributes);
        //  查看当前用户是否有对应的权限访问该保护资源
        for (ConfigAttribute attribute : configAttributes) {
            for (GrantedAuthority authority : authorities) {
                if (域名uthority().equals(域名ttribute())) {
                    return;
                }
            }
        }
        throw new AccessDeniedException("Access is denied");
    }

    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}

decide方法的三个参数,依次表示:

  • 调用者(非空)
  • 被调用的安全对象
  • 与被调用的安全对象关联的配置属性

配置WebSecurityConfig

package 域名域名ig;

import 域名域名cessDecisionManager;
import 域名域名lterSecurityMetadataSource;
import 域名域名erDetailsService;
import 域名域名域名wired;
import 域名域名;
import 域名域名iguration;
import 域名域名域名ctPostProcessor;
import 域名域名域名域名enticationManagerBuilder;
import 域名域名域名域名Security;
import 域名域名域名域名ecurityConfigurerAdapter;
import 域名域名域名ptPasswordEncoder;
import 域名域名域名wordEncoder;
import 域名域名域名erSecurityInterceptor;

/**
 * @Author ChengJianSheng
 * @Date 2021/12/6
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserDetailsService myUserDetailsService;
    @Autowired
    private MyAccessDecisionManager myAccessDecisionManager;
    @Autowired
    private MyFilterSecurityMetadataSource myFilterSecurityMetadataSource;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        域名DetailsService(myUserDetailsService).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        域名Login()
                .and()
                .authorizeRequests()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        域名ecurityMetadataSource(myFilterSecurityMetadataSource);
                        域名ccessDecisionManager(myAccessDecisionManager);
                        return object;
                    }
                })
                .anyRequest().authenticated();
    }



    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

其它不重要的就直接贴出来了

域名

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://域名/POM/4.0.0" xmlns:xsi="http://域名/2001/XMLSchema-instance"
         xsi:schemaLocation="http://域名/POM/4.0.0 https://域名/xsd/maven-4.域名">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>域名</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>域名ple</groupId>
    <artifactId>security-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>security-demo</name>

    <properties>
        <域名ion>1.8</域名ion>
    </properties>
    <dependencies>
        <dependency>
            <groupId>域名</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>域名</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>域名</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>域名ectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>域名</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>域名rity</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>域名</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>域名ectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

域名

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/demo126?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false
    driver-class-name: 域名.域名er
    username: root
    password: 123456
  jpa:
    database: mysql
    show-sql: true

域名

package 域名域名ty;

import 域名er;
import 域名er;

import 域名istence.*;
import 域名alizable;

/**
 * @Author ChengJianSheng
 * @Date 2021/12/6
 */
@Getter
@Setter
@Entity
@Table(name = "sys_permission")
public class SysPermissionEntity implements Serializable {

    @Id
    @GeneratedValue(strategy = 域名)
    private Integer id;

    /** 权限编码(标识) */
    private String code;

    /** 权限名称 */
    private String name;

    /** 权限URL */
    private String url;

}

域名

package 域名域名ty;

import 域名er;
import 域名er;

import 域名istence.*;
import 域名alizable;
import 域名;

/**
 * @Author ChengJianSheng
 * @Date 2021/12/6
 */
@Getter
@Setter
@Entity
@Table(name = "sys_role")
public class SysRoleEntity implements Serializable {

    @Id
    @GeneratedValue(strategy = 域名)
    private Integer id;

    /** 角色编码 */
    private String code;

    /** 角色名称 */
    private String name;

    @ManyToMany
    @JoinTable(name = "sys_role_permission", joinColumns = {@JoinColumn(name = "role_id")}, inverseJoinColumns = {@JoinColumn(name = "permission_id")})
    private Set<SysPermissionEntity> permissions;
}

域名

package 域名域名ty;

import 域名er;
import 域名er;

import 域名istence.*;
import 域名alizable;
import 域名;

/**
 * @Author ChengJianSheng
 * @Date 2021/12/6
 */
@Getter
@Setter
@Entity
@Table(name = "sys_user")
public class SysUserEntity implements Serializable {

    @Id
    @GeneratedValue(strategy = 域名)
    private Integer id;

    /** 用户名 */
    private String username;

    /** 密码 */
    private String password;

    @ManyToMany
    @JoinTable(name = "sys_user_role",
            joinColumns = {@JoinColumn(name = "user_id")},
            inverseJoinColumns = {@JoinColumn(name = "role_id")})
    private Set<SysRoleEntity> roles;
}

域名

package 域名域名sitory;

import 域名域名serEntity;
import 域名.域名epository;
import 域名.域名pecificationExecutor;

/**
 * @Author ChengJianSheng
 * @Date 2021/12/6
 */
public interface SysUserRepository extends JpaRepository<SysUserEntity, Integer>, JpaSpecificationExecutor<SysUserEntity> {

    SysUserEntity findByUsername(String username);
}

域名 

package 域名域名;

import 域名域名ermissionEntity;
import 域名域名ermissionRepository;
import 域名域名ermissionService;
import 域名域名ice;

import 域名urce;
import 域名;
import 域名;
import 域名域名ectors;

/**
 * @Author ChengJianSheng
 * @Date 2021/12/6
 */
@Service
public class SysPermissionServiceImpl implements SysPermissionService {

    @Resource
    private SysPermissionRepository sysPermissionRepository;

    @Override
    public Map<String, String> getAllUrlRole() {
        List<SysPermissionEntity> list = 域名All();
        return 域名am().collect(域名p(SysPermissionEntity::getUrl, SysPermissionEntity::getCode));
    }
}

域名

package 域名域名in;

import 域名rgsConstructor;
import 域名gsConstructor;
import 域名域名tedAuthority;
import 域名域名域名leGrantedAuthority;
import 域名域名域名Details;

import 域名ection;
import 域名;

/**
 * @Author ChengJianSheng
 * @Date 2021/12/6
 */
@NoArgsConstructor
@AllArgsConstructor
public class MyUserDetails implements UserDetails {

    private String username;
    private String password;
    private boolean enabled;
    private Set<SimpleGrantedAuthority> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }
}

域名

package 域名域名;

import 域名域名erDetails;
import 域名域名ermissionEntity;
import 域名域名serEntity;
import 域名域名serRepository;
import 域名域名域名leGrantedAuthority;
import 域名域名域名Details;
import 域名域名域名DetailsService;
import 域名域名域名nameNotFoundException;
import 域名域名ice;

import 域名urce;
import 域名sactional;
import 域名;
import 域名域名ectors;

/**
 * @Author ChengJianSheng
 * @Date 2021/12/6
 */
@Transactional
@Service
public class MyUserDetailsService implements UserDetailsService {

    @Resource
    private SysUserRepository sysUserRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUserEntity sysUserEntity = 域名ByUsername(username);
        if (null == sysUserEntity) {
            throw new UsernameNotFoundException("用户不存在");
        }
        Set<SimpleGrantedAuthority> authorities = 域名oles().stream()
                .flatMap(roleId->域名ermissions().stream())
                .map(SysPermissionEntity::getCode)
                .map(SimpleGrantedAuthority::new)
                .collect(域名t());

        return new MyUserDetails(域名sername(), 域名assword(), true, authorities);
    }
}

域名

package 域名域名roller;

import 域名.域名apping;
import 域名.域名estMapping;
import 域名.域名Controller;

/**
 * @Author ChengJianSheng
 * @Date 2021/12/6
 */
@RestController
@RequestMapping("/hello")
public class HelloController {

    @GetMapping("/sayHello")
    public String sayHello() {
        return "Hello";
    }

    @GetMapping("/sayHi")
    public String sayHi() {
        return "Hi";
    }
}

数据库脚本如下

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT \'权限编码(标识)\',
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT \'权限名称\',
  `url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT \'权限URL\',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_permission
-- ----------------------------
INSERT INTO `sys_permission` VALUES (1, \'home\', \'首页\', \'/home/**\');
INSERT INTO `sys_permission` VALUES (2, \'user:add\', \'添加用户\', \'/user/add\');
INSERT INTO `sys_permission` VALUES (3, \'user:delete\', \'删除用户\', \'/user/delete\');
INSERT INTO `sys_permission` VALUES (4, \'hello:sayHello\', \'打招呼\', \'/hello/sayHello\');

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT \'角色编码\',
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT \'角色名称\',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, \'employee\', \'员工\');
INSERT INTO `sys_role` VALUES (2, \'engineer\', \'工程师\');
INSERT INTO `sys_role` VALUES (3, \'leader\', \'组长\');

-- ----------------------------
-- Table structure for sys_role_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_permission`;
CREATE TABLE `sys_role_permission`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `role_id` int(11) NOT NULL COMMENT \'角色ID\',
  `permission_id` int(11) NOT NULL COMMENT \'权限ID\',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_role_permission
-- ----------------------------
INSERT INTO `sys_role_permission` VALUES (1, 1, 1);
INSERT INTO `sys_role_permission` VALUES (2, 2, 1);
INSERT INTO `sys_role_permission` VALUES (3, 2, 2);
INSERT INTO `sys_role_permission` VALUES (4, 3, 1);
INSERT INTO `sys_role_permission` VALUES (5, 3, 2);
INSERT INTO `sys_role_permission` VALUES (6, 3, 3);
INSERT INTO `sys_role_permission` VALUES (7, 3, 4);

-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT \'用户名\',
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT \'密码\',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, \'zhangsan\', \'$2a$10$e4wFsFHQCNjPe5tTJMPkRuKGwmMGC45pfjMupY9nwbTuoKQ0bKc/u\');

-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL COMMENT \'用户ID\',
  `role_id` int(11) NOT NULL COMMENT \'角色ID\',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (1, 1, 1);
INSERT INTO `sys_user_role` VALUES (2, 1, 2);
INSERT INTO `sys_user_role` VALUES (3, 1, 3);

SET FOREIGN_KEY_CHECKS = 1;

浏览器访问 http://localhost:8080/hello/sayHi 正常返回,不用登录,因为没有在sys_permission表中配置该资源,也就是说它不是一个受保护的资源(公开资源)

访问http://localhost:8080/hello/sayHello则需要先登录,用zhangsan登录成功以后正确返回 

项目结构如下

 

标签:编程
湘ICP备14001474号-3  投诉建议:234161800@qq.com   部分内容来源于网络,如有侵权,请联系删除。