赞
踩
权限系统,在应用开发中是一种不可缺少的系统,主要进行权限的控制和管理,典型的权限设计方案有RBAC,常见的权限框架有shiro和security,下面对相关内容进行简单的聊聊。
一、RBAC
1、RBAC模型
RBAC模型(Role-Based Access Control:基于角色的访问控制),通过角色关联用户,角色关联权限,这种间接的方式赋予用户的权限,如下
2、RBAC组成
在RBAC模型中,有3个基础组成部分,分别是用户、角色和权限。RBAC通过定义角色的权限,并对用户授予某个角色从而控制用户的权限,实现了用户和权限的逻辑分离,方便了权限的管理。
如管理员和普通用户被授予不同的权限,普通用户只能去修改和查看用户,而不能创建创建用户和冻结用户,而管理员由于被授 予所有权限,所以可以做所有操作。
3、RBAC安全原则
RBAC支持三个著名的安全原则:最小权限原则、责任分离原则和数据抽象原则,如下
4、RBAC模型分类
RBAC模型可以分为以下4个模型:RBAC0、RBAC1、RBAC2、RBAC4,其中最简单常用的模型为RBAC0。
二、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的底层原理及各种实现方式,若有错误或不当之处,欢迎指正。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。