当前位置:   article > 正文

权限系统漫谈_权限互斥

权限互斥

权限系统,在应用开发中是一种不可缺少的系统,主要进行权限的控制和管理,典型的权限设计方案有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的底层原理及各种实现方式,若有错误或不当之处,欢迎指正。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/352203
推荐阅读
相关标签
  

闽ICP备14008679号