权限系统漫谈

权限系统,在应用开发中是一种不可缺少的系统,主要进行权限的控制和管理,典型的权限设计方案有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>

2、访问接口

	@GetMapping("/hello")
    public String hello() {
        return "hello";
    }

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>

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;
    }
}

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);
    }
}

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;
    }
}

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);
    }

}

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;
    }

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值