• 权限系统漫谈


    权限系统,在应用开发中是一种不可缺少的系统,主要进行权限的控制和管理,典型的权限设计方案有RBAC,常见的权限框架有shiro和security,下面对相关内容进行简单的聊聊。
    一、RBAC
    1、RBAC模型
    RBAC模型(Role-Based Access Control:基于角色的访问控制),通过角色关联用户,角色关联权限,这种间接的方式赋予用户的权限,如下
    在这里插入图片描述
    2、RBAC组成
    在RBAC模型中,有3个基础组成部分,分别是用户、角色和权限。RBAC通过定义角色的权限,并对用户授予某个角色从而控制用户的权限,实现了用户和权限的逻辑分离,方便了权限的管理。

    • User(用户):每个用户都有唯一的UID识别,并被授予不同的角色;
    • Role(角色):不同角色具有不同的权限;
    • Permission(权限):访问权限;
    • 用户-角色映射:用户和角色之间的映射关系;
    • 角色-权限映射:角色和权限之间的映射;

    如管理员和普通用户被授予不同的权限,普通用户只能去修改和查看用户,而不能创建创建用户和冻结用户,而管理员由于被授 予所有权限,所以可以做所有操作。
    在这里插入图片描述
    3、RBAC安全原则
    RBAC支持三个著名的安全原则:最小权限原则、责任分离原则和数据抽象原则,如下

    • 最小权限原则:RBAC可以将角色配置成其完成任务所需的最小权限集合;
    • 责任分离原则:可以通过调用相互独立互斥的角色来共同完成敏感的任务,例如要求一个计账员和财务管理员共同参与统一过账操作;
    • 数据抽象原则:可以通过权限的抽象来体现,例如财务操作用借款、存款等抽象权限,而不是使用典型的读、写、执行权限;

    4、RBAC模型分类
    RBAC模型可以分为以下4个模型:RBAC0、RBAC1、RBAC2、RBAC4,其中最简单常用的模型为RBAC0。

    • RBAC0:最简单、最原始的实现方式,也是其他RBAC模型的基础;若用户和角色是多对一的关系,一个用户只充当一种角色,一个角色可以授予多个用户;若用户和角色是多对多的关系,一个用户可以同时充当多个角色,一个角色可以授予多个用户;
    • RBAC1:基于RBAC0模型,引入了角色间的继承关系,即角色上有了上下级的区别;
    • RBAC2:基于RBAC0模型的基础上,进行了角色的访问控制;一个基本限制是角色互斥,互斥角色是指各自权限可以互相制约的两个角色。对于这类角色一个用户在某一次活动中只能被分配其中的一个角色,不能同时获得两个角色的使用权;还有基数约束、先决条件、运行时互斥;
    • RBAC3:RBAC1,RBAC2,两者模型全部累计,称为统一模型;

    二、Spring Security

    shiro和spring security都是安全框架,以spring security为例进行简单使用说明。
    1、新建springboot工程,引入依赖

    		<dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
    
    • 1
    • 2
    • 3
    • 4

    2、访问接口

    	@GetMapping("/hello")
        public String hello() {
            return "hello";
        }
    
    • 1
    • 2
    • 3
    • 4

    3、启动访问
    启动项目,如下
    在这里插入图片描述
    访问http://localhost:8080/hello,弹出如下
    在这里插入图片描述
    输入账号密码:user/控制台打印的字符串,返回如下
    在这里插入图片描述
    这样就完成了对访问的简单权限控制。
    三、Spring Security + JWT实现前后端分离
    实例如下
    1、pom依赖

    		<dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>0.9.1</version>
            </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2、jwt工具类

    @Component
    public class JwtTokenUtil implements Serializable {
    
        @Value("${jwt.header}")
        private String header;
    
        @Value("${jwt.secret}")
        private String secret;
    
        @Value("${jwt.expiration}")
        private Long expiration;
    
        private String generateToken(Map<String, Object> claims) {
            Date expirationDate = new Date(System.currentTimeMillis() + expiration);
            return Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, secret).compact();
        }
    
        private Claims getClaimsFromToken(String token) {
            Claims claims;
            try {
                claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
    
            } catch (Exception e) {
                claims = null;
            }
            return claims;
        }
    
        public String generateToken(UserDetails userDetails) {
            Map<String, Object> claims = new HashMap<>(2);
            claims.put("sub", userDetails.getUsername());
            claims.put("created", new Date());
            return generateToken(claims);
    
        }
    
        public String getUsernameFromToken(String token) {
            String username;
            try {
                Claims claims = getClaimsFromToken(token);
                username = claims.getSubject();
    
            } catch (Exception e) {
                username = null;
            }
            return username;
    
        }
    
        public String getUserRole(String token) {
            return (String) getClaimsFromToken(token).get("role");
        }
    
        public Boolean isTokenExpired(String token) {
            try {
                Claims claims = getClaimsFromToken(token);
                Date expiration = claims.getExpiration();
                return expiration.before(new Date());
            } catch (Exception e) {
                return false;
            }
        }
    
        public String refreshToken(String token) {
            String refreshedToken;
            try {
                Claims claims = getClaimsFromToken(token);
                claims.put("created", new Date());
                refreshedToken = generateToken(claims);
    
            } catch (Exception e) {
                refreshedToken = null;
    
            }
            return refreshedToken;
        }
    
        public Boolean validateToken(String token, UserDetails userDetails) {
            String username = getUsernameFromToken(token);
            return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    
        }
    
        public String getSecret() {
            return secret;
        }
    
        public void setSecret(String secret) {
            this.secret = secret;
        }
    
        public Long getExpiration() {
            return expiration;
        }
    
        public void setExpiration(Long expiration) {
            this.expiration = expiration;
        }
    
        public String getHeader() {
            return header;
        }
    
        public void setHeader(String header) {
            this.header = header;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108

    3、jwt过滤器

    @Component
    public class JwtAuthenticationFilter extends OncePerRequestFilter {
    
        @Resource
        private JwtUserDetailsServiceImpl userDetailsService;
    
        @Resource
        private JwtTokenUtil jwtTokenUtil;
    
        @Override
        protected void doFilterInternal(HttpServletRequest httpServletRequest,
                                        HttpServletResponse httpServletResponse,
                                        FilterChain filterChain) throws ServletException, IOException {
            String token = httpServletRequest.getHeader(jwtTokenUtil.getHeader());
            if (token != null && StringUtils.hasLength(token)) {
                String username = jwtTokenUtil.getUsernameFromToken(token);
                if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                    UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                    if (jwtTokenUtil.validateToken(token, userDetails)) {
                        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
                        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
                        SecurityContextHolder.getContext().setAuthentication(authentication);
                    }
                }
            }
            filterChain.doFilter(httpServletRequest, httpServletResponse);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    4、重写userDetailsService的实现类

    @Service
    public class JwtUserDetailsServiceImpl implements UserDetailsService {
    
        @Resource
        private PasswordEncoder passwordEncoder;
    
        @Override
        public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
            //直接写死数据信息,可以在这里获取数据库的信息并进行验证
    //        UserDetails user = User.withUsername("caocao")
    //                .password(passwordEncoder.encode("123"))
    //                .authorities("admin")
    //                .build();
    
            // 从数据库根据name查询获得
            User user = new User("caocao", passwordEncoder.encode("123"),
                    AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
            if (user == null) {
                throw new UsernameNotFoundException(String.format("'%s'.这个用户不存在", name));
            }
            return user;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    5、配置类

    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Resource
        private JwtUserDetailsServiceImpl userDetailsService;
    
        @Resource
        private JwtAccessDeniedHandler jwtAccessDeniedHandler;
    
        @Resource
        private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    
        @Resource
        private JwtAuthenticationFilter jwtAuthenticationFilter;
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
        @Override
        public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
            authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable();
            // 禁用session机制
            http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    
            http.authorizeRequests()
                    // 指定某些接口不需要通过验证即可访问。像登陆、注册接口肯定是不需要认证的
                    .antMatchers("/login").permitAll()
                    .anyRequest().authenticated();
    
            // 禁用缓存
            http.headers().cacheControl();
    
            // 使用自己定义的拦截机制验证请求是否正确,拦截jwt
            http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
            // 添加自定义未授权和未登陆结果返回
            // 用户访问没有授权资源
            http.exceptionHandling().accessDeniedHandler(jwtAccessDeniedHandler);
            // 用户访问资源没有携带正确的token
            http.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    6、登录接口

    	@PostMapping("/login")
        public String login(@RequestBody Map<String,String> params) {
            //生成token,返回给客户端
            UserDetails userDetails = userDetailsService.loadUserByUsername(params.get("userName"));
            if (!passwordEncoder.matches(params.get("password"),userDetails.getPassword())) {
                return "用户名或密码不正确";
            }
            String token = jwtTokenUtil.generateToken(userDetails);
            return token;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    7、测试
    直接访问hello接口,由于未登录返回
    在这里插入图片描述
    输入用户名/密码,密码错误时返回
    在这里插入图片描述
    输入正确时返回
    在这里插入图片描述
    携带jwt令牌访问hello接口,返回
    在这里插入图片描述
    以上只是其中一种方式的简单使用,工程化时还需深入理解security的底层原理及各种实现方式,若有错误或不当之处,欢迎指正。

  • 相关阅读:
    VUE3.0+Antdv+Asp.net WebApi开发学生信息管理系统(三)
    resume不严格加载model、避免某些层维度不一致导致错误
    【C语言】通讯录联系(文件版)
    c语言extern关键字
    C语言系统化精讲(二):C语言初探
    MySQL - Heap 表是什么?
    Error running ‘xxx‘: Command line is too long. Shorten command line for xxxx
    [NOIP2012 提高组] 借教室(二分、差分)
    MySQL查询某个字段含有字母数字的值
    k8s相关命令-命名空间
  • 原文地址:https://blog.csdn.net/leijie0322/article/details/126501666