专栏首页关忆北.基于RBAC模型的SpringSecurity权限控制能力

基于RBAC模型的SpringSecurity权限控制能力

RBAC权限模型

全名为:Role-Based Access Control 译为基于角色的访问控制。

RBAC权限框架基于角色进行鉴权,在该框架中具有三大模块:角色(Role)、用户(User)、权限(Permissions),

RBAC使用最小特权原则,当前请求访问的用户具备那些角色,该角色具备那些权限,所具备的权限中是否包含本次访问所需的权限?若具有,正常访问返回,若不具有,给予用户提示,所以,RBAC可以把权限粒度做到方法级。

SpringSecurity是基于RBAC模型轻量级权限控框架,与之对等的还有Apache Shiro,由于Spring的生态不断完善、功能日益丰富,使得SpringSecurity越来越越受欢迎。 一般的,SpringSecurity的权限控制设计思路为:User - User_Role -Role -Role_Menu -Menu,即:用户属于什么角色,该角色具有什么权限,具有该权限可以访问那些页面,如若把权限控制在方法级别,可以使用SpringSecurity注解在后端方法上,从而做到按钮级别的权限控制,以上,便完成了权限访问控制。

数据库设计便为:

  • User:用户表
  • User_Role:用户角色中间表
  • Role:角色表
  • Role_Menu:角色菜单中间表
  • Menu:菜单表

(用户可能有多个角色,一个角色可能有多个用户,所以用户和角色是多对多的关系) ​ Menu可以理解为权限,在Web中,菜单中的显示与否可以视为用户是否具备该权限

如此便完成了权限控制的设计方案。

SpringSecurity使用步骤:

引入SpringSecurity模块

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

加入这个依赖后表示所有的接口都是被保护的状态,访问的时候被Security拦截。

在浏览器输入该请求路径,会自重定向到Spring Security的登录页。默认的用户名是user,密码请去IDEA的Consolse去找项目每次启动时随机生成的字符串:

Using generated security password: 5a38aea2-81d0-485d-bf5c-12c73b0aad27

(复制passwor后的内容即可访问)

同时也支持在数据库配置用户名和密码(正式项目一般处理方式)或在配置文件配置用户名密码,本文使用的是yml配置,properties同理,如果不知道如何配置,请自行百度。配置后在Console中便不自动生成password

配置HTTP拦截规则

配置接口放行规则

SecurityConfig.class

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserService userService;

    @Autowired
    private RedisService redisUtil;


    @Autowired
    private SecurityFilter securityFilter;

    @Autowired
    private OwnAccessDecisionManager ownAccessDecisionManager;


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService);
    }

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


    @Bean
    /**
     * 定义角色继承
     */
    RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        roleHierarchy.setHierarchy("ROLE_dba> ROLE_admin > ROLE_user");
        return roleHierarchy;
    }

    /**
     * 配置HTTP请求规则
     * 什么请求路径需要什么权限才能访问,
     * 登录接口,都可访问
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/**").hasRole("admin")
                .antMatchers("user/**").hasAnyRole("admin", "user")
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginProcessingUrl("/doLogin")
                .usernameParameter("userName")
                .passwordParameter("passWord")
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
                        //登陆成功处理句柄,前后分离项目,给前端返回Json即可
                        resp.setContentType("application/json;charset=utf-8");
                        Map<String, Object> map = new HashMap<>();
                        map.put("status", HttpServletResponse.SC_OK);
                        User principal = (User) authentication.getPrincipal();
                        String token = JwtUtil.sign(principal.getUsername(), principal.getPassword());
                        map.put("msg", authentication.getPrincipal());
                        map.put("token", token);
                        map.put("userName", principal.getUsername());
                        redisUtil.setCacheObject(BusinessConstant.REDIS_RELATED.PREFIX + LocalDateUtils.getStartTimeOfDayStr() + principal.getId(), map);
                        ResponseUtil.responseJson(resp, HttpStatus.OK.value(), map);
                    }
                })
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
                        //登录失败处理  AuthenticationException:锁定异常问题
                        resp.setContentType("application/json;charset=utf-8");
                        PrintWriter out = resp.getWriter();
                        Map<String, Object> map = new HashMap<>();
                        map.put("status", HttpServletResponse.SC_UNAUTHORIZED);
                        if (e instanceof LockedException) {
                            map.put("msg", ResponseEnum.USER_ACCOUNT_LOCKED.getMessage());
                        } else if (e instanceof BadCredentialsException) {
                            map.put("msg", ResponseEnum.USER_NOT_EXIST_OR_ERROR.getMessage());
                        } else {
                            map.put("msg", ResponseEnum.LOGIN_FILURE.getMessage());
                        }
                        out.write(new ObjectMapper().writeValueAsString(map));
                        out.flush();
                        out.close();
                    }
                })
                .permitAll()
                .and()
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessHandler(new LogoutSuccessHandler() {
                    @Override
                    public void onLogoutSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
                        resp.setContentType("application/json;charset=utf-8");
                        PrintWriter out = resp.getWriter();
                        out.write(new ObjectMapper().writeValueAsString("注销成功!"));
                        out.flush();
                        out.close();
                    }
                })
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                        o.setAccessDecisionManager(ownAccessDecisionManager);
                        o.setSecurityMetadataSource(securityFilter);
                        return o;
                    }
                })
                .and()
                .csrf().disable()
                .exceptionHandling()
            
            //没有权限时返回Json,而不是重定向到登录页,方便前后端分离项目使用
                .authenticationEntryPoint(new AuthenticationEntryPoint() {
                    @Override
                    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException authException) throws IOException, ServletException {
                        httpServletResponse.setContentType("application/json;charset=utf-8");
                        PrintWriter out = httpServletResponse.getWriter();
              
                        if (authException instanceof InsufficientAuthenticationException) {
                            throw new RuntimeException(ResponseEnum.SYSTEM_INNER_ERROR.getMessage());
                        }
//                        ResponseEnum.SYSTEM_INNER_ERROR.assertException(authException);
                        out.write(new ObjectMapper().writeValueAsString(authException));
                        out.flush();
                        out.close();
                    }
                });
    }

SecurityFilter.class

根据请求地址分析出该地址需要那些角色,并查看请求的用户是否具备该角色

@Component
public class SecurityFilter implements FilterInvocationSecurityMetadataSource{

    @Autowired
    private MenuService menuService;

    @Autowired
    private RedisService redisService;

    /**
     * Ant规则匹配符
     */
    AntPathMatcher pathMatcher = new AntPathMatcher();

    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        String requestUrl = ((FilterInvocation) o).getRequestUrl();
        List<Menu> allMenus;
        allMenus = redisService.getCacheObject(BusinessConstant.REDIS_RELATED.MENU_ALL);
        if (CollectionUtils.isEmpty(allMenus)) {
            allMenus = menuService.getAllMenus();
        }
        for (Menu menu : allMenus) {
            if (pathMatcher.match(menu.getPattern(), requestUrl)) {
                List<Role> roles = menu.getRoles();
                String[] rolesStr = new String[roles.size()];
                for (int i = 0; i < roles.size(); i++) {
                    rolesStr[i] = roles.get(i).getName();
                }
                return SecurityConfig.createList(rolesStr);
            }
        }
        //需要的角色都不满足条件,非法请求
        return SecurityConfig.createList("ROLE_login");
    }

    @Override
    /**
     *根据需要的角色查看当前用户是否具有该角色
     */
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

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


}

OwnAccessDecisionManager.class

为当前的访问规则进行决策,是否给予访问的权限。

@Component
public class OwnAccessDecisionManager implements AccessDecisionManager {
    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
        for (ConfigAttribute attribute : collection) {
            if ("ROLE_login".equals(attribute.getAttribute())) {
                if (authentication instanceof AnonymousAuthenticationToken) {
                    throw new AccessDeniedException(ResponseEnum.PERMISSION_NOT_SAFE.getMessage());
                } else {
                    return;
                }
            }
            //查询访问所需角色,当前登录用户是否具备所需角色的其中的一个
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for (GrantedAuthority authority : authorities) {
                if (authority.getAuthority().equals(attribute.getAttribute())) {
                    return;
                }
            }
            throw new AccessDeniedException(ResponseEnum.USER_ROLE_PERMISSION_ERROR.getMessage());
        }
    }

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

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

使用postman测试,所以关闭CSRF攻击,正式环境请开启 记得要删掉super.configure(http); 不然会报错IllegalStateException: Can't configure anyRequest after itself ObjectMapper类是Jackson库的主要类。它提供一些功能将转换成Java对象匹配JSON结构,反之亦然。它使用JsonParser和JsonGenerator的实例实现JSON实际的读/写。

表单登录测试

使用post请求构造表单登录,SpringSecurity已做密码脱敏,权限中默认使用"ROLE_"为前缀。

表单登出测试

登出配置如上代码,构造get请求即可。

使用数据库的方式做登录认证

由于篇幅原因,不宜过长,所以我是分开书写的,权限功能需要整合数据库相关,在我的另一篇文章中:

SpringBoot整合Redis、MyBatis-Plus

因为敲完这个demo,时间不是很充足,所以没有更新文章,SpringBoot下与数据库交互使用权限认证请去我的github去寻找源码,思路根据江南一点雨(松哥)的权限认证思路而来。

项目源码Git地址

松哥Github地址

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!
本文分享自作者个人站点/博客:https://blog.csdn.net/weixin_42313773复制
如有侵权,请联系 cloudcommunity@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • 基于RBAC模型的权限系统设计(Github开源项目)

    计划在Team的Github开源项目里加入权限控制的业务功能。从而实现权限控制。在很多管理系统里都是有权限管理这些通用模块的,当然在企业项目里,权限控制是很繁杂...

    SmileNicky
  • 基于角色访问控制RBAC权限模型的动态资源访问权限管理实现

    前面主要介绍了元数据管理和业务数据的处理,通常一个系统都会有多个用户,不同用户具有不同的权限,本文主要介绍基于RBAC动态权限管理在crudapi中的实现。

    crudapi
  • 基于SpringCloud的RBAC权限管理系统

    基于 Spring Cloud Greenwich.SR2 、Spring Security OAuth2 的RBAC权限管理系统;基于数据驱动视图的理念封装 ...

    程序源代码
  • RBAC:基于角色的权限访问控制

    RBAC模型(Role-Based Access Control:基于角色的访问控制)模型是20世纪90年代研究出来的一种新模型,但其实在20世纪70年代的多用...

    看、未来
  • 视频系列 | Casbin权限实战:基于角色的RBAC授权

    身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。系统确定您是否就是您所说的使用凭据。在公共和专用网络中,系统通过登录密码验证用户身份。身份...

    Tinywan
  • k8s 基于角色的权限控制 RBAC

    RBAC 之所以一直没有写这个,一方面是因为它确实并不复杂,二来平常确实接触不多,今天就来顺路讲讲它

    LinkinStar
  • 【程序源代码】RBAC基于角色的权限管理系统

    基于角色的权限管理系统(RBAC),采用Springboot开发。系统简单易懂,前端使用Vue、Quasarframework开发,页面简洁美观。后端核心框架使...

    程序源代码
  • RBAC模型与权限系统的梳理(附案例源码)

    在进行查询时,自定义了一个异常类, 用于在用户查询不到是,打印出"用户不存在或密码有误"的异常

    时间静止不是简史
  • RBAC新解:基于资源的权限管理(Resource-Based Access Control)

    本文讨论以角色概念进行的权限管理策略及主要以基于角色的机制进行权限管理是远远不够的。同时我将讨论一种我认为更好的权限管理方式。 什么是角色 当说到程序的权限管理...

    MonroeCode
  • RBAC 模型 - 权限系统是如何进行架构设计的?

    大家在平时使用网页的时候,遇到和权限相关的场景应该很多,比如视频网站的会员视频,管理后台的访问控制,那么,本文将带大家了解一下,权限系统的通用设计模型理念,和如...

    用户3806669
  • 基于SSM框架的RBAC权限系统设计与实现(附源码、论文 )

    鉴于信息科技的发展,信息管理系统已应用于社会的方方面面,尤其是对于拥有大量信息数据的组织和企业,作用更为突出。但是,随着工作内容的扩大,涉及的信息和人员数量增加...

    上分如喝水
  • 公司新来了一个同事,把权限系统设计的炉火纯青!

    点击关注公众号,Java干货及时送达 作者:小小____ 来源:segmentfault.com/a/1190000023052493 思维导图如下 RBA...

    Java技术栈
  • 万字长文,SpringSecurity

    [wp_editor_md_ce446408a534c04c207dbf04846f415a.jpg]

    mySoul
  • 公司新来了一个同事,把权限系统设计的炉火纯青!

    作者:小小____ 来源:segmentfault.com/a/1190000023052493 思维导图如下 RBAC权限分析 RBAC 全称为基于角色的权...

    纯洁的微笑
  • SpringSecurity + JWT,从入门到精通!

    RBAC 全称为基于角色的权限控制,本段将会从什么是 RBAC,模型分类,什么是权限,用户组的使用,实例分析等几个方面阐述 RBAC,绘制思维导图如下:

    后端码匠
  • 公司新来了一个同事,把权限系统设计的炉火纯青!

    点击上方“芋道源码”,选择“设为星标” 管她前浪,还是后浪? 能浪的浪,才是好浪! 每天 10:33 更新文章,每天掉亿点点头发... 源码精品专栏 原创 |...

    芋道源码
  • SpringBoot整合Shiro实现基于角色的权限访问控制(RBAC)系统简单设计从零搭建

    SpringBoot整合Shiro实现基于角色的权限访问控制(RBAC)系统简单设计从零搭建

    EalenXie
  • Springboot+Spring-Security+JWT 实现用户登录和权限认证「建议收藏」

    下面来说说关于单点登录中目前比较流行的一种使用方式,就是springsecurity+jwt实现无状态下用户登录;

    全栈程序员站长
  • 微服务网关——设计篇

    在《微服务网关——需求篇》中,我们讨论了微服务网关的需求,本文将对微服务网关进行设计。考虑到实际情况的差异,这里实际给出的是设计选项,最终设计基于实际场景来确定...

    架构之家

扫码关注腾讯云开发者

领取腾讯云代金券