RBAC 全稱爲基於角色的權限控制,本段將會從什麼是RBAC,模型分類,什麼是權限,用戶組的使用,實例分析等幾個方面闡述RBACweb
RBAC 全稱爲用戶角色權限控制,經過角色關聯用戶,角色關聯權限,這種方式,間階的賦予用戶的權限,以下圖所示
數據庫
對於一般的系統而言,存在多個用戶具備相同的權限,在分配的時候,要爲指定的用戶分配相關的權限,修改的時候也要依次的對這幾個用戶的權限進行修改,有了角色這個權限,在修改權限的時候,只須要對角色進行修改,就能夠實現相關的權限的修改。這樣作增長了效率,減小了權限漏洞的發生。apache
對於RBAC模型來講,分爲如下幾個模型 分別是RBAC0,RBAC1,RBAC2,RBAC3,這四個模型,這段將會依次介紹這四個模型,其中最經常使用的模型有RBAC0.json
RBAC0是最簡單的RBAC模型,這裏麪包含了兩種。後端
用戶和角色是多對一的關係,即一個用戶只充當一種角色,一個角色能夠有多個角色的擔當。
用戶和角色是多對多的關係,即,一個用戶能夠同時充當多個角色,一個角色能夠有多個用戶。
此係統功能單一,人員較少,這裏舉個栗子,張三既是行政,也負責財務,此時張三就有倆個權限,分別是行政權限,和財務權限兩個部分。安全
相對於RBAC0模型來講,增長了子角色,引入了繼承的概念。
session
這裏RBAC2模型,在RBAC0模型的基礎上,增長了一些功能,以及限制app
即,同一個用戶不能擁有兩個互斥的角色,舉個例子,在財務系統中,一個用戶不能擁有會計員和審計這兩種角色。
即,用一個角色,所擁有的成員是固定的,例如對於CEO這種角色,同一個角色,也只能有一個用戶。
即,對於該角色來講,若是想要得到更高的角色,須要先獲取低一級別的角色。舉個栗子,對於副總經理和經理這兩個權限來講,須要先有副總經理權限,才能擁有經理權限,其中副總經理權限是經理權限的先決條件。
即,一個用戶能夠擁有兩個角色,可是這倆個角色不能同時使用,須要切換角色才能進入另一個角色。舉個栗子,對於總經理和專員這兩個角色,系統只能在一段時間,擁有其一個角色,不能同時對這兩種角色進行操做。
即,RBAC1,RBAC2,二者模型所有累計,稱爲統一模型。
權限是資源的集合,這裏的資源指的是軟件中的全部的內容,即,對頁面的操做權限,對頁面的訪問權限,對數據的增刪查改的權限。 舉個栗子。 對於下圖中的系統而言,
擁有,計劃管理,客戶管理,合同管理,出入庫通知單管理,糧食安全追溯,糧食統計查詢,設備管理這幾個頁面,對這幾個頁面的訪問,以及是否可以訪問到菜單,都屬於權限。
對於用戶組來講,是把衆多的用戶劃分爲一組,進行批量授予角色,即,批量授予權限。 舉個栗子,對於部門來講,一個部門擁有一萬多個員工,這些員工都擁有相同的角色,若是沒有用戶組,可能須要一個個的授予相關的角色,在擁有了用戶組之後,只須要,把這些用戶所有劃分爲一組,而後對該組設置授予角色,就等同於對這些用戶授予角色。
優勢: 減小工做量,便於理解,增長多級管理,等。
首先添加依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
而後添加相關的訪問接口
package com.example.demo.web; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/test") public class Test { @RequestMapping("/test") public String test(){ return "test"; } }
最後啓動項目,在日誌中查看相關的密碼
訪問接口,能夠看到相關的登陸界面
輸入用戶名和相關的密碼
用戶名: user 密碼 984cccf2-ba82-468e-a404-7d32123d0f9c
登陸成功
在配置文件中,書寫相關的登陸和密碼
spring: security: user: name: ming password: 123456 roles: admin
在登陸頁面,輸入用戶名和密碼,便可正常登陸
須要自定義類繼承 WebSecurityConfigurerAdapter 代碼以下
package com.example.demo.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter { @Bean PasswordEncoder passwordEncoder(){ return NoOpPasswordEncoder.getInstance(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("admin").password("123").roles("admin"); } }
即,配置的用戶名爲admin,密碼爲123,角色爲admin
這裏對一些方法進行攔截
package com.ming.demo.interceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices; @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { //基於內存的用戶存儲 @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("itguang").password("123456").roles("USER").and() .withUser("admin").password("{noop}" + "123456").roles("ADMIN"); } //請求攔截 @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().permitAll() .and() .formLogin() .permitAll() .and() .logout() .permitAll(); } }
即,這裏完成了對全部的方法訪問的攔截。
這是一個小demo,目的,登陸之後返回jwt生成的token
導入JWT和Security依賴
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>2.3.1.RELEASE</version> </dependency>
建立 一個相關的JavaBean
package com.example.demo; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; public class JwtUser implements UserDetails { private String username; private String password; private Integer state; private Collection<? extends GrantedAuthority> authorities; public JwtUser(){ } public JwtUser(String username, String password, Integer state, Collection<? extends GrantedAuthority> authorities){ this.username = username; this.password = password; this.state = state; this.authorities = authorities; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } @Override public String getPassword() { return this.password; } @Override public String getUsername() { return this.username; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
編寫工具類,用來生成token,以及刷新token,以及驗證token
package com.example.demo; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.security.core.userdetails.UserDetails; import java.io.Serializable; import java.util.Date; import java.util.HashMap; import java.util.Map; public class JwtTokenUtil implements Serializable { private String secret; private Long expiration; private String header; 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 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) { JwtUser user = (JwtUser) userDetails; String username = getUsernameFromToken(token); return (username.equals(user.getUsername()) && !isTokenExpired(token)); } }
編寫Filter 用來檢測JWT
package com.example.demo; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Autowired private UserDetailsService userDetailsService; @Autowired private JwtTokenUtil jwtTokenUtil; @Override protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { String authHeader = httpServletRequest.getHeader(jwtTokenUtil.getHeader()); if (authHeader != null && StringUtils.isNotEmpty(authHeader)) { String username = jwtTokenUtil.getUsernameFromToken(authHeader); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); if (jwtTokenUtil.validateToken(authHeader, userDetails)) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest)); SecurityContextHolder.getContext().setAuthentication(authentication); } } } filterChain.doFilter(httpServletRequest, httpServletResponse); } }
在上方代碼中,編寫userDetailsService,類,實現其驗證過程
package com.example.demo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import javax.management.relation.Role; import java.util.List; @Service public class JwtUserDetailsServiceImpl implements UserDetailsService { @Autowired private UserMapper userMapper; @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { User user = userMapper.selectByUserName(s); if (user == null) { throw new UsernameNotFoundException(String.format("'%s'.這個用戶不存在", s)); } List<SimpleGrantedAuthority> collect = user.getRoles().stream().map(Role::getRolename).map(SimpleGrantedAuthority::new).collect(Collectors.toList()); return new JwtUser(user.getUsername(), user.getPassword(), user.getState(), collect); } }
編寫登陸業務的實現類 其login方法會返回一個JWTUtils 的token
@Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; @Autowired private JwtTokenUtil jwtTokenUtil; public User findByUsername(String username) { User user = userMapper.selectByUserName(username); return user; } public RetResult login(String username, String password) throws AuthenticationException { UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(username, password); final Authentication authentication = authenticationManager.authenticate(upToken); SecurityContextHolder.getContext().setAuthentication(authentication); UserDetails userDetails = userDetailsService.loadUserByUsername(username); return new RetResult(RetCode.SUCCESS.getCode(),jwtTokenUtil.generateToken(userDetails)); } }
@EnableGlobalMethodSecurity(prePostEnabled = true) @EnableWebSecurity public class WebSecurity extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Autowired private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; @Autowired public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { authenticationManagerBuilder.userDetailsService(this.userDetailsService).passwordEncoder(passwordEncoder()); } @Bean(name = BeanIds.AUTHENTICATION_MANAGER) @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and().authorizeRequests() .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() .antMatchers("/auth/**").permitAll() .anyRequest().authenticated() .and().headers().cacheControl(); http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests(); registry.requestMatchers(CorsUtils::isPreFlightRequest).permitAll(); } @Bean public CorsFilter corsFilter() { final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource(); final CorsConfiguration cors = new CorsConfiguration(); cors.setAllowCredentials(true); cors.addAllowedOrigin("*"); cors.addAllowedHeader("*"); cors.addAllowedMethod("*"); urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", cors); return new CorsFilter(urlBasedCorsConfigurationSource); } }
這裏配置SpringSecurity之JSON登陸
這裏須要重寫UsernamePasswordAnthenticationFilter類,以及配置SpringSecurity
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter { @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { //attempt Authentication when Content-Type is json if(request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE) ||request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)){ //use jackson to deserialize json ObjectMapper mapper = new ObjectMapper(); UsernamePasswordAuthenticationToken authRequest = null; try (InputStream is = request.getInputStream()){ AuthenticationBean authenticationBean = mapper.readValue(is,AuthenticationBean.class); authRequest = new UsernamePasswordAuthenticationToken( authenticationBean.getUsername(), authenticationBean.getPassword()); }catch (IOException e) { e.printStackTrace(); authRequest = new UsernamePasswordAuthenticationToken( "", ""); }finally { setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } } //transmit it to UsernamePasswordAuthenticationFilter else { return super.attemptAuthentication(request, response); } } }
@Override protected void configure(HttpSecurity http) throws Exception { http .cors().and() .antMatcher("/**").authorizeRequests() .antMatchers("/", "/login**").permitAll() .anyRequest().authenticated() //這裏必需要寫formLogin(),否則原有的UsernamePasswordAuthenticationFilter不會出現,也就沒法配置咱們從新的UsernamePasswordAuthenticationFilter .and().formLogin().loginPage("/") .and().csrf().disable(); //用重寫的Filter替換掉原有的UsernamePasswordAuthenticationFilter http.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); } //註冊自定義的UsernamePasswordAuthenticationFilter @Bean CustomAuthenticationFilter customAuthenticationFilter() throws Exception { CustomAuthenticationFilter filter = new CustomAuthenticationFilter(); filter.setAuthenticationSuccessHandler(new SuccessHandler()); filter.setAuthenticationFailureHandler(new FailureHandler()); filter.setFilterProcessesUrl("/login/self"); //這句很關鍵,重用WebSecurityConfigurerAdapter配置的AuthenticationManager,否則要本身組裝AuthenticationManager filter.setAuthenticationManager(authenticationManagerBean()); return filter; }
這樣就完成使用json登陸SpringSecurity
須要在Config 類中配置以下內容
/** * 密碼加密 */ @Bean public BCryptPasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); }
即,使用此方法,對密碼進行加密, 在業務層的時候,使用此加密的方法
@Service @Transactional public class UserServiceImpl implements UserService { @Resource private UserRepository userRepository; @Resource private BCryptPasswordEncoder bCryptPasswordEncoder; //注入bcryct加密 @Override public User add(User user) { user.setPassword(bCryptPasswordEncoder.encode(user.getPassword())); //對密碼進行加密 User user2 = userRepository.save(user); return user2; } @Override public ResultInfo login(User user) { ResultInfo resultInfo=new ResultInfo(); User user2 = userRepository.findByName(user.getName()); if (user2==null) { resultInfo.setCode("-1"); resultInfo.setMessage("用戶名不存在"); return resultInfo; } //判斷密碼是否正確 if (!bCryptPasswordEncoder.matches(user.getPassword(),user2.getPassword())) { resultInfo.setCode("-1"); resultInfo.setMessage("密碼不正確"); return resultInfo; } resultInfo.setMessage("登陸成功"); return resultInfo; } }
即,使用BCryptPasswordEncoder 對密碼進行加密,保存數據庫
這裏使用數據庫認證SpringSecurity
這裏設計數據表
@Configurable public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserService userService; // service 層注入 @Bean PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 參數傳入Service,進行驗證 auth.userDetailsService(userService); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/**").hasRole("admin") .anyRequest().authenticated() .and() .formLogin() .loginProcessingUrl("/login").permitAll() .and() .csrf().disable(); } }
這裏着重配置SpringConfig
着重講解了RBAC的權限配置,以及簡單的使用SpringSecurity,以及使用SpringSecurity + JWT 完成先後端的分離,以及配置json登陸,和密碼加密方式,