Spring Security基于数据库配置权限(角色,路径)

来源:互联网 发布:alias是什么软件 编辑:程序博客网 时间:2024/05/29 10:10

Spring Security基于数据库配置权限(角色,路径)

传统的后台管理系统,在权限处理上通常5个表:用户表角色表资源表用户角色关联表角色资源关联表。现在为了避免重复造轮子,自己写拦截处理,我们可以使用Spring Security来做权限控制。
Spring Security官方推荐通过配置来实现角色和资源的对应,这样的问题是假如需要线上配置角色与资源对应就不行了,所以下面讲讲如何基于数据库中存储角色资源用户 来使用Spring Security做权限控制。

1.首先是数据准备

创建权限相关的五张表和对应的实体类。以及相关的查询实现等。

2.配置类SecurityConfiguration

只要使用spring security都要有这样一个配置类(如果是类配置的话),继承WebSecurityConfigurerAdapter 主要实现两个方法configure(AuthenticationManagerBuilder auth)

configure(HttpSecurity http)

  • AuthenticationManagerBuilder 主要配置身份认证来源,也就是用户及其角色。

  • HttpSecurity 主要配置路径,也就是资源的访问权限(是否需要认证,需要什么角色等)。

2.1 configure(AuthenticationManagerBuilder auth) 相关

该方法本身只需要设置一个身份认证来源,所以我们需要先写个方法来创建该来源

    @Bean    public BCryptPasswordEncoder passwordEncoder() {        return new BCryptPasswordEncoder(); //创建密码加密对象    }    /**     * @return 封装身份认证提供者     */    @Bean    public DaoAuthenticationProvider authenticationProvider() {        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();        authenticationProvider.setUserDetailsService(customUserService);  //自定义的用户和角色数据提供者        authenticationProvider.setPasswordEncoder(passwordEncoder()); //设置密码加密对象        return authenticationProvider;    }     @Override    protected void configure(AuthenticationManagerBuilder auth) throws Exception {        auth.authenticationProvider(authenticationProvider()); //设置身份认证提供者    }

自定义的类需要实现UserDetailsService
实现接口中的方法

UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;

UserDetails中包含用户的用户名,密码,角色等信息。所以需要自己创建一个类实现UserDetails,这样方便我们赋值我们查到的角色集合
比如创建一个UserPrincipal

public class UserPrincipal implements UserDetails {    private User user;    private List<String> roleCodes;    UserPrincipal(User user,List<String> roleCodes){        this.user = user;        this.roleCodes = roleCodes;    }   /**    * 设置用户角色集合    */    @Override    public Collection<? extends GrantedAuthority> getAuthorities() {        return roleCodes.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());    }    @Override    public String getPassword() {        return user.getPassword();    }    @Override    public String getUsername() {        return user.getUsername();    }    ......}

CustomUserService 提供用户和对应的角色

public class CustomUserService implements UserDetailsService {    @Override    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {        User user = userRepository.findByUsername(username); //根据用户名查询用户        if (user == null) {            throw new UsernameNotFoundException("can not found username " + username);        }        return new UserPrincipal(user,getRoleCodes(user));    }    private List<String> getRoleCodes(User user){       return ....//根据用户查询该用户拥有的角色编号集合    }}

这样就完成了对用户和角色的查询,接下来就是配置路径(资源)和角色映射了

2.2 configure(HttpSecurity http)相关

主要就配置登陆路径,错误页面路径,后置处理程序等。

    @Override    protected void configure(HttpSecurity http) throws Exception {        http.formLogin().loginPage("/login").defaultSuccessUrl("/",true)                .and()                .exceptionHandling().accessDeniedPage("/error")                .and()                .csrf().disable()                .authorizeRequests().anyRequest().authenticated()                .withObjectPostProcessor(postProcessor);//设置后置处理程序对象    }

加上前面的代码,配置类基本就完成了,记得加上@Configuration注解。接下来就是利用角色对路径进行判断了,这部分处理都封装在postProcessor 中的 FilterSecurityInterceptor中。

3.权限处理配置 postProcessor

现在需要定义一个类来实现 ObjectPostProcessor<FilterSecurityInterceptor>

public class CustomPostProcessor implements ObjectPostProcessor<FilterSecurityInterceptor> {    @Override    public <T extends FilterSecurityInterceptor> T postProcess(T fsi) {        fsi.setAccessDecisionManager(accessDecisionManager); //权限决策处理类        fsi.setSecurityMetadataSource(filterSecurityMetadataSource); //路径(资源)拦截处理        return fsi;    }}

3.1 路径拦截处理类

filterSecurityMetadataSource 需要实现 FilterInvocationSecurityMetadataSource接口,Collection<ConfigAttribute> getAttributes(Object object)会返回改路径所需的角色集合到权限决策处理类中供其使用.

public class CustomFilterSecurityMetadataSource implements FilterInvocationSecurityMetadataSource{    @Override    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {        FilterInvocation fi = (FilterInvocation) object; //当前请求对象        if (isMatcherAllowedRequest(fi)) return null ; //return null 表示允许访问,不做拦截        List<ConfigAttribute> configAttributes = getMatcherConfigAttribute(fi.getRequestUrl());        return configAttributes.size() > 0 ? configAttributes : deniedRequest(); //返回当前路径所需角色,如果没有则拒绝访问    }    @Override    public Collection<ConfigAttribute> getAllConfigAttributes() {        return null;    }    @Override    public boolean supports(Class< ? > aClass) {        return FilterInvocation.class.isAssignableFrom(aClass);    }    /**     * 获取当前路径所需要的角色     * @param url 当前路径     * @return 所需角色集合     */    private List<ConfigAttribute> getMatcherConfigAttribute(String url){        return roleResourceRepository.findByResource_ResUrl(url).stream()                .map(roles -> new SecurityConfig(roles.getRole().getRoleCode()))                .collect(Collectors.toList());    }    /**     * 判断当前请求是否在允许请求的范围内     * @param fi 当前请求     * @return 是否在范围中     */    private boolean isMatcherAllowedRequest(FilterInvocation fi){        return allowedRequest().stream().map(AntPathRequestMatcher::new)                .filter(requestMatcher -> requestMatcher.matches(fi.getHttpRequest()))                .toArray().length > 0;    }    /**     * @return 定义允许请求的列表     */    private List<String> allowedRequest(){        return Arrays.asList("/login","/css/**","/fonts/**","/js/**","/scss/**","/img/**");    }    /**     * @return 默认拒绝访问配置     */    private List<ConfigAttribute> deniedRequest(){        return Collections.singletonList(new SecurityConfig("ROLE_DENIED"));    }}

3.2 权限决策处理

accessDecisionManager需要实现 AccessDecisionManager接口。实现方法decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) 来做权限决策

  • authentication可获取当前用户拥有的角色集合
  • configAttributes 路径拦截处理中查询到的,能访问当前路径的角色集合
public class CustomAccessDecisionManager implements AccessDecisionManager {    @Override    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {        if(authentication == null){            throw new AccessDeniedException("permission denied");        }        //当前用户拥有的角色集合        List<String> roleCodes = authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());        //访问路径所需要的角色集合        List<String> configRoleCodes = configAttributes.stream().map(ConfigAttribute::getAttribute).collect(Collectors.toList());        for (String roleCode : roleCodes){            if(configRoleCodes.contains(roleCode)){                return;            }        }        throw new AccessDeniedException("permission denied");    }}

这样就完成了基于数据库存储的Spring Security权限控制实现。

阅读全文
1 0
原创粉丝点击