当前位置: 首页 > article >正文

使用组合模式实现权限验证的例子

步骤

为了演示组合模式实现权限验证的功能,我需要做以下几个步骤:

  • 创建一个Spring Boot项目,并添加spring-boot-starter-security依赖
  • 创建一个简单的Controller类,提供两个受保护的资源:/admin和/user
  • 创建一个SecurityConfig类,继承WebSecurityConfigurerAdapter,并配置自定义的AccessDecisionManager和AccessDecisionVoter
  • 创建一个CustomAccessDecisionManager类,实现AccessDecisionManager接口,并注入一个AccessDecisionVoter列表
  • 创建两个CustomAccessDecisionVoter类,分别实现AccessDecisionVoter接口,并根据不同的逻辑进行投票
  • 在application.properties中配置一些用户和角色信息

首先,创建一个Spring Boot项目,并添加spring-boot-starter-security依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
</dependencies>

然后,创建一个简单的Controller类,提供两个受保护的资源:/admin和/user:

@RestController
public class HelloController {

    @GetMapping("/admin")
    public String admin() {
        return "Hello, admin!";
    }

    @GetMapping("/user")
    public String user() {
        return "Hello, user!";
    }
}

接着,创建一个SecurityConfig类,继承WebSecurityConfigurerAdapter,并配置自定义的AccessDecisionManager和AccessDecisionVoter:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 配置自定义的AccessDecisionManager
        http.authorizeRequests()
                .anyRequest().authenticated()
                .accessDecisionManager(customAccessDecisionManager());
    }

    // 定义一个Bean,返回一个CustomAccessDecisionManager对象,并注入两个CustomAccessDecisionVoter对象
    @Bean
    public AccessDecisionManager customAccessDecisionManager() {
        List<AccessDecisionVoter<?>> voters = new ArrayList<>();
        voters.add(new CustomAccessDecisionVoter1());
        voters.add(new CustomAccessDecisionVoter2());
        return new CustomAccessDecisionManager(voters);
    }

    // 在内存中配置一些用户和角色信息
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("admin").password("{noop}123").roles("ADMIN")
                .and()
                .withUser("user").password("{noop}123").roles("USER");
    }
}

接下来,创建一个CustomAccessDecisionManager类,实现AccessDecisionManager接口,并注入一个AccessDecisionVoter列表:

public class CustomAccessDecisionManager implements AccessDecisionManager {

    // 注入一个AccessDecisionVoter列表
    private List<AccessDecisionVoter<?>> voters;

    public CustomAccessDecisionManager(List<AccessDecisionVoter<?>> voters) {
        this.voters = voters;
    }

    // 实现decide方法,根据投票结果决定是否授权访问
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        // 定义一个变量,记录赞成票数
        int grant = 0;
        // 遍历所有的投票者,调用它们的vote方法,并根据返回值进行统计
        for (AccessDecisionVoter voter : voters) {
            int result = voter.vote(authentication, object, configAttributes);
            switch (result) {
                case AccessDecisionVoter.ACCESS_GRANTED:
                    grant++;
                    break;
                case AccessDecisionVoter.ACCESS_DENIED:
                    throw new AccessDeniedException("访问被拒绝");
                default:
                    break;
            }
        }
        // 如果没有赞成票,则抛出异常
        if (grant == 0) {
            throw new AccessDeniedException("访问被拒绝");
        }
    }

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

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

最后,创建两个CustomAccessDecisionVoter类,分别实现AccessDecisionVoter接口,并根据不同的逻辑进行投票:

public class CustomAccessDecisionVoter1 implements AccessDecisionVoter<Object> {

    // 实现vote方法,根据请求的URL和用户的角色进行投票
    @Override
    public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {
        // 获取请求对象
        HttpServletRequest request = ((FilterInvocation) object).getRequest();
        // 获取请求的URL
        String url = request.getRequestURI();
        // 获取用户的角色
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        // 如果请求的是/admin,且用户的角色是ADMIN,则投赞成票
        if (url.equals("/admin") && authorities.contains(new SimpleGrantedAuthority("ROLE_ADMIN"))) {
            return ACCESS_GRANTED;
        }
        // 否则,投弃权票
        return ACCESS_ABSTAIN;
    }

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

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}
public class CustomAccessDecisionVoter2 implements AccessDecisionVoter<Object> {

    // 实现vote方法,根据请求的URL和用户的角色进行投票
    @Override
    public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {
        // 获取请求对象
        HttpServletRequest request = ((FilterInvocation) object).getRequest();
        // 获取请求的URL
        String url = request.getRequestURI();
        // 获取用户的角色
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        // 如果请求的是/user,且用户的角色是USER,则投赞成票
        if (url.equals("/user") && authorities.contains(new SimpleGrantedAuthority("ROLE_USER"))) {
            return ACCESS_GRANTED;
        }
        // 否则,投弃权票
        return ACCESS_ABSTAIN;
    }

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

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

这样,我们就用最简练的代码写出了一个使用组合模式实现权限验证的例子。您可以运行这个项目,并使用不同的用户和密码访问不同的资源。例如:

  • 使用admin/123访问/admin,可以成功访问,返回Hello, admin!
  • 使用admin/123访问/user,会失败访问,抛出AccessDeniedException异常
  • 使用user/123访问/user,可以成功访问,返回Hello, user!
  • 使用user/123访问/admin,会失败访问,抛出AccessDeniedException异常

使用了两个CustomAccessDecisionVoter对象来组合一个CustomAccessDecisionManager对象,并通过它来决定一个请求是否能够访问受保护的资源。这就是使用组合模式实现权限验证的功能。


http://www.kler.cn/a/37229.html

相关文章:

  • 【数据分析 - 基础入门之pandas篇③】- pandas数据结构——DataFrame
  • 决策树(Decision Tree)
  • 【接口/性能测试】Jmeter引用 jar包的三种方式(详细)
  • 进程和线程的区别
  • Linux下软件安装的命令
  • 简化生活之让AI以指定格式输出
  • 添加gitee的SSH公钥
  • 山东农信:走好“三农”数字化转型的最后一公里
  • Android 生成pdf文件
  • 基于scrcpy的Android群控项目重构,获取Android屏幕元素信息并编写自动化事件
  • Spring中事务传播机制的理解与简单试用
  • 短视频抖音账号矩阵系统源码---功能架构示例1
  • 【开源技术分享】Java读写操作Mp3的库:MP3AGIC,使用MP3AGIC获取ID3v1值和获取ID3v2专辑封面和修改ID3v2封面图片等mp3信息
  • 【python手写算法】【多元】利用梯度下降实现线性拟合
  • Docker 部署 Jenkins (一)
  • 利用langchain-ChatGLM、langchain-TigerBot实现基于本地知识库的问答应用
  • 神码融信金融SBG交付二部VP李先林受邀为第十二届中国PMO大会演讲嘉宾
  • Ubuntu 18.04 下 uhd+gnuradio 安装指南,国产B210
  • android linker加载和链接机制
  • JMeter中同步定时器与线程组中线程数和Ramp-Up的关系