SpringBoot整合SpringSecurity(八)动态权限

序言

SpringSecurity 进行的权限验证,有时候可能并不太满足我们的需求。有时候呢可能需要你自己去扩展达到一个对自己业务满意的验证,这时候怎么办呢?第一呢, 先不要百度,你要懂权限验证的一个流程,不懂的话可以去看我之前的博客,第二呢,就是放开手按照自己假象的思路去实践!

思路

我说一下我的需求,我想判断那个角色拥有某些url的权限,这时候该怎么进行扩展呢?

我的思路是对 自定义 AccessDecisionVoter 然后我们知道在 FilterSecurityInterceptor 里是从 SecurityMetadataSource 中拿到的权限配置项,知道了这些后,就可以做出扩展。

  1. 定义一个自己的SecurityMetadataSource 使其从数据库中查询权限自己构建 ConfigAttribute
  2. 定义一个具有自己需求的AccessDecisionVoter
  3. 规定一个自己的AccessDecisionManager

实践

SecurityMetadataSource

首先呢我们先定义一个自己的 SecurityMetadataSource ,然后每次调用的时候都去数据库里去查询。

public class DynamicFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {


    private FilterInvocationSecurityMetadataSource superMetadataSource;


    public DynamicFilterInvocationSecurityMetadataSource(FilterInvocationSecurityMetadataSource expressionBasedFilterInvocationSecurityMetadataSource) {
        this.superMetadataSource = expressionBasedFilterInvocationSecurityMetadataSource;
        //TODO 在这里去查询你的数据库
    }

    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    /**
     * 假设这就是从数据库中查询到的数据
     * 意思就是 ROLE_JAVA 的角色 才能访问 /tt
     */
    private final Map<String, String> urlRoleMap = new HashMap<String, String>() {{
        put("/tt", "ROLE_JAVA");
    }};

    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        FilterInvocation fi = (FilterInvocation) object;
        String url = fi.getRequestUrl();
        for (Map.Entry<String, String> entry : urlRoleMap.entrySet()) {
            if (antPathMatcher.match(entry.getKey(), url)) {
                return SecurityConfig.createList(entry.getValue());
            }
        }
        //如果没有匹配到就拿 咱们自定义的配置
        return superMetadataSource.getAttributes(object);
    }

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


    @Override
    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }
}

AccessDecisionVoter

紧接着我们定义一个自己的 AccessDecisionVoter 这一步的作用是对我们自己构造的 SecurityConfig.createList(entry.getValue()); 进行一个角色验证。

public class MyDynamicVoter implements AccessDecisionVoter<Object> {
    /**
     * supports 方法说明这个投票器是否可以传递到下一个投票器
     * 可以支持传递,则返回true
     *
     * @param attribute
     * @return
     */
    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

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

    @Override
    public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {
        //如果没有进行认证则永远返回 -1
        if (authentication == null) {
            return ACCESS_DENIED;
        }
        int result = ACCESS_ABSTAIN;
        Collection<? extends GrantedAuthority> authorities = extractAuthorities(authentication);
        for (ConfigAttribute attribute : attributes) {
            if (attribute.getAttribute() == null) {
                continue;
            }
            if (this.supports(attribute)) {
                result = ACCESS_DENIED;
                for (GrantedAuthority authority : authorities) {
                    if (attribute.getAttribute().equals(authority.getAuthority())) {
                        return ACCESS_GRANTED;
                    }
                }
            }
        }
        return result;
    }

    private Collection<? extends GrantedAuthority> extractAuthorities(
            Authentication authentication) {
        return authentication.getAuthorities();
    }

}

AccessDecisionManager

最后呢我们可以选择重写或者使用 Spring Security 提供的验证器 看你自己。这里呢我还是使用 Spring Security提供的 AffirmativeBased

 @Bean
 public AccessDecisionManager accessDecisionManager() {
    List<AccessDecisionVoter<?>> decisionVoters = new ArrayList<>();
    decisionVoters.add(new AuthenticatedVoter());
    decisionVoters.add(new WebExpressionVoter());
    decisionVoters.add(new MyDynamicVoter());
    return new AffirmativeBased(decisionVoters);
 }

这里顺便把WebExpressionVoter 投票器加入,让其验证除我们自定义的权限配置。

加入配置项

最后呢把我们定义好的条条框框都加入到我们的配置链中

   protected void configure(HttpSecurity http) throws Exception {
        //http.httpBasic()  //httpBasic 登录
        http.formLogin()
                .failureHandler(failureAuthenticationHandler) // 自定义登录失败处理
                .successHandler(successAuthenticationHandler) // 自定义登录成功处理
                .and()
                .logout()
                .logoutUrl("/logout")
                .and()
                .formLogin()
                .loginPage("/login")
                .loginProcessingUrl("/authentication/form") // 自定义登录路径
                .and()
                .authorizeRequests()// 对请求授权
                // 自定义FilterInvocationSecurityMetadataSource
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O fsi) {
                        fsi.setSecurityMetadataSource(mySecurityMetadataSource(fsi.getSecurityMetadataSource()));
                        fsi.setAccessDecisionManager(accessDecisionManager());
                        return fsi;
                    }
                })
                .antMatchers("/login", "/authentication/require",
                        "/authentication/form").permitAll()
                .anyRequest()
                .authenticated().and().exceptionHandling().accessDeniedHandler(accessDeniedAuthenticationHandler)
                .and()
                .csrf().disable();// 禁用跨站攻击
    }


@Bean
public DynamicFilterInvocationSecurityMetadataSource mySecurityMetadataSource(FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource) {
   return new DynamicFilterInvocationSecurityMetadataSource(filterInvocationSecurityMetadataSource);
}


@Bean
public AccessDecisionManager accessDecisionManager() {
   List<AccessDecisionVoter<?>> decisionVoters = new ArrayList<>();
   decisionVoters.add(new AuthenticatedVoter());
   decisionVoters.add(new WebExpressionVoter());
   decisionVoters.add(new MyDynamicVoter());
   return new AffirmativeBased(decisionVoters);
}

然后定义一个controller

@RestController
public class PermissionController {


    @RequestMapping("/tt")
    public String tt() {
        System.out.println("1");
        return "tt";
    }
}

让登录用户拥有ROLE_JAVA 角色的时候,即可访问/tt

当然这只是我的一个需求,具体的怎么扩展决定在你自己,只要懂了大概的流程,既可以对Spring Security的权限校验做出更好的扩展。

顺便说下我们这个自定义权限并不会影响到权限注解,权限注解是进行了切面处理所以还会在走一遍权限认证器和投票的。

本博文是基于springboot2.x 和security 5 如果有什么不对的请在下方留言。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot和Spring Security是一对好朋友,Spring Boot提供了强大的自动配置和快速开发的能力,而Spring Security则提供了完整的安全决方案,可以实现用户认证、授权、安全过滤等功能。本文将介绍如何在Spring Boot整合Spring Security实现权限控制。 1. 添加Spring Security依赖 在pom.xml文件添加以下依赖: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> ``` 2. 配置Spring SecuritySpring Boot,可以通过application.properties或application.yml文件配置Spring Security。以下是一个简单的配置: ``` spring.security.user.name=admin spring.security.user.password=123456 spring.security.user.roles=ADMIN ``` 这个配置定义了一个用户名为admin,密码为123456,角色为ADMIN的用户。在实际应用,应该将用户名和密码存储在数据库或其他安全存储。 3. 创建SecurityConfig类 创建一个继承自WebSecurityConfigurerAdapter的SecurityConfig类,并重写configure方法: ``` @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/", "/home").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .permitAll(); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("admin").password("{noop}123456").roles("ADMIN"); } } ``` configure方法定义了应用程序的安全策略,这里配置了所有请求都需要认证(即登录)才能访问,除了首页和登录页,这两个页面可以匿名访问。formLogin方法配置了自定义的登录页面,logout方法配置了退出登录的操作。 configureGlobal方法定义了一个内存的用户,用户名为admin,密码为123456,角色为ADMIN。在实际应用,应该将用户信息存储在数据库或其他安全存储。 4. 创建登录页面 在templates目录下创建一个名为login.html的登录页面,例如: ``` <!DOCTYPE html> <html> <head> <title>Login Page</title> </head> <body> <h1>Login Page</h1> <div th:if="${param.error}"> Invalid username and password. </div> <div th:if="${param.logout}"> You have been logged out. </div> <form th:action="@{/login}" method="post"> <div> <label>Username:</label> <input type="text" name="username" /> </div> <div> <label>Password:</label> <input type="password" name="password" /> </div> <div> <button type="submit">Login</button> </div> </form> </body> </html> ``` 5. 运行应用程序 在浏览器访问http://localhost:8080/login,输入用户名admin和密码123456,即可登录成功。如果输入错误的用户名或密码,则会提示“Invalid username and password.”。如果成功登录后再访问http://localhost:8080/home,则可以看到“Welcome home!”的欢迎消息。 6. 实现权限控制 上面的例子只实现了登录认证,没有实现权限控制。下面介绍如何实现权限控制。 首先需要在configureGlobal方法添加更多的用户和角色: ``` @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("admin").password("{noop}123456").roles("ADMIN") .and() .withUser("user").password("{noop}password").roles("USER"); } ``` 这里定义了一个管理员用户和一个普通用户,分别拥有ADMIN和USER两个角色。 然后在configure方法添加更多的安全策略: ``` @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/", "/home").permitAll() .antMatchers("/admin/**").hasRole("ADMIN") .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .permitAll(); } ``` 这里添加了一个安全策略,即/admin/**路径需要拥有ADMIN角色才能访问。 现在管理员用户可以访问/admin/**路径,而普通用户则不能访问。如果普通用户尝试访问/admin/**路径,则会提示“Access is denied”。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值