RBAC(Role-Based Access Control)是一种基于角色的访问控制模型,用于管理系统中用户的权限和访问控制。它将用户、角色和权限之间的关系进行了明确的定义,以实现灵活的权限管理和控制。
1、模型概念:
用户(User):系统中的实际操作者,拥有唯一标识符。
角色(Role):权限的集合,可以被赋予给用户,一个用户可以拥有一个或多个角色。
权限(Permission):系统中的具体操作权限,定义了用户可以执行的操作,可以是功能菜单的接口路径URL,也可以是其他细粒度的操作权限。
2、操作概念:
授权(Authorization):将权限赋予用户或角色的过程,即为用户分配相应的角色或权限。
认证(Authentication):验证用户的身份和权限,确保用户具有访问系统资源的合法权限。
RBAC模型的基本原则是:权限授权应该基于角色,而不是直接授权给特定用户。通过角色的中介,将权限与用户进行解耦,实现了权限的集中管理和灵活分配。
在实际应用中,RBAC模型可以结合权限管理框架和安全框架来实现。例如,使用Spring Security框架可以方便地实现RBAC模型的权限控制,通过定义角色、权限和用户的关系,并结合注解和配置来实现权限的校验和访问控制。
总结来说,RBAC模型是一种灵活和可扩展的权限管理模型,通过角色的授权机制实现了权限的集中管理和灵活分配,为系统提供了高效、安全和可维护的访问控制机制。
当用户非常多的情况,可以设计用户组的概念,用户组代替原先用户的概念,这样能起到批量操作用户角色的作用;
权限组的概念也差不多,是权限过多的情况,一个一个赋权操作太繁琐,可以用权限组批量操作,非常方便。
结合前面我写的两篇帖子,这里整理了一个demo,能够实现一个简单的无侵入的登录及权限控制方案。这里用到springSecruity、jwt、redis、mysql、threadLocal等技术
数据源自己配,就是要提前有redis和mysql
server:
port: 8083
spring:
application:
name: player
redis:
host: xxxxxxx
port: xxxxxxx
datasource:
url: jdbc:mysql://xxxxxxx:xxxx/xxxxxx
username: root
password: Aa@123456
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: none
mybatis:
mapper-locations: classpath:mappers/*.xml
type-aliases-package: com.loong.nba.player.pojo
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-redis
org.springframework.boot
spring-boot-starter-security
io.jsonwebtoken
jjwt-api
0.11.2
io.jsonwebtoken
jjwt-impl
0.11.2
runtime
io.jsonwebtoken
jjwt-jackson
0.11.2
runtime
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.2.0
mysql
mysql-connector-java
8.0.31
package com.loong.nba.player.util;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* @author jilong
* @date 2023/5/18
*/
@Component
public class JwtUtil {
private final Key secretKey;
private final long expirationTime;
@Autowired
private StringRedisTemplate redisTemplate;
public JwtUtil() {
this.secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
this.expirationTime = 120000;
}
/**
* 生成jwt令牌
*
* @param username 用户名
* @return token
*/
public String generateToken(String username) {
Date now = new Date();
Date expirationDate = new Date(System.currentTimeMillis() + expirationTime);
String token = Jwts.builder()
.setSubject(username)
.setIssuedAt(now)
.setExpiration(expirationDate)
.signWith(secretKey, SignatureAlgorithm.HS256)
.compact();
redisTemplate.opsForValue().set(username, token, expirationTime, TimeUnit.MILLISECONDS);
return token;
}
/**
* 解析令牌
*
* @param token token
* @return 内容
*/
public String getUsernameFormToken(String token) {
return extractClaims(token).getBody().getSubject();
}
public boolean validateToken(String token) {
try {
Jws claims = extractClaims(token);
return !claims.getBody().getExpiration().before(new Date());
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
private Jws extractClaims(String token) {
JwtParser parser = Jwts.parserBuilder().setSigningKey(secretKey).build();
return parser.parseClaimsJws(token);
}
}
package com.loong.nba.player.util;
import com.loong.nba.player.pojo.LoginUserBO;
public class SessionUtils {
static ThreadLocal loginUser = new ThreadLocal<>();
public static LoginUserBO getLoginUser() {
return loginUser.get();
}
public static void setLoginUser(LoginUserBO loginUserBO) {
loginUser.set(loginUserBO);
}
}
package com.loong.nba.player.pojo;
public class UserPO {
private Integer id;
private String username;
private String password;
private String salt;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
}
package com.loong.nba.player.pojo;
import java.util.List;
public class LoginUserBO {
private String username;
private Integer userId;
private String password;
private List roleList;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public List getRoleList() {
return roleList;
}
public void setRoleList(List roleList) {
this.roleList = roleList;
}
}
package com.loong.nba.player.pojo;
public class Role {
private Integer id;
private String roleName;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
}
package com.loong.nba.player.pojo;
/**
* @author jilong
* @date 2023/5/19
*/
public class UserDO {
private String name;
private String password;
public UserDO() {
}
public UserDO(String name, String password) {
this.name = name;
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
package com.loong.nba.player.config;
import com.loong.nba.player.filter.JwtAuthenticationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* @author jilong
* @date 2023/5/18
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/login","/user").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
package com.loong.nba.player.dao;
import com.loong.nba.player.pojo.Role;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface RoleMapper {
List findByUserId(Integer id);
}
package com.loong.nba.player.dao;
import com.loong.nba.player.pojo.UserPO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface UserMapper {
UserPO findById(Integer id);
UserPO findByName(@Param("name") String name);
Integer addUser(@Param("userPO") UserPO userPO);
}
package com.loong.nba.player.service;
import com.loong.nba.player.pojo.UserDO;
import com.loong.nba.player.pojo.UserPO;
public interface UserService {
/**
* 添加用户
* @return 用户id
*/
UserPO addUser(UserDO userDO);
boolean comparePassword(UserDO userDO);
}
package com.loong.nba.player.service;
import com.loong.nba.player.dao.UserMapper;
import com.loong.nba.player.pojo.UserDO;
import com.loong.nba.player.pojo.UserPO;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
/**
* @author jilong
* @date 2023/5/19
*/
@Service
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
public UserServiceImpl(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Override
public UserPO addUser(UserDO userDO) {
// 定义盐的长度(字节数)
int saltLength = 6;
// 创建一个安全的随机数生成器
SecureRandom secureRandom = new SecureRandom();
// 生成盐
byte[] salt = new byte[saltLength];
secureRandom.nextBytes(salt);
// 将盐转换为字符串或字节数组
String saltString = encodeSalt(salt);
// 计算密码摘要
String password = encryptPassword(userDO.getPassword(), saltString);
UserPO userPO = new UserPO();
userPO.setUsername(userDO.getName());
userPO.setPassword(password);
userPO.setSalt(saltString);
userMapper.addUser(userPO);
System.out.println("Salt (Base64 string): " + saltString);
return userPO;
}
String encodeSalt(byte[] salt) {
return Base64.getEncoder().encodeToString(salt);
}
byte[] decodeSaltString(String saltString) {
return Base64.getDecoder().decode(saltString);
}
String encryptPassword(String password, String salt) {
String plaintext = password + salt;
try {
// 创建SHA-256算法的MessageDigest实例
MessageDigest digest = MessageDigest.getInstance("SHA-256");
// 计算中文字符串的摘要
byte[] hash = digest.digest(plaintext.getBytes(StandardCharsets.UTF_8));
// 将摘要字节数组转换为十六进制字符串表示
String digestHex = bytesToHex(hash);
System.out.println("SHA-256摘要:" + digestHex);
return digestHex;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
// 将摘要字节数组转换为十六进制字符串表示
private static String bytesToHex(byte[] bytes) {
StringBuilder hexStringBuilder = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexStringBuilder.append('0');
}
hexStringBuilder.append(hex);
}
return hexStringBuilder.toString();
}
@Override
public boolean comparePassword(UserDO userDO) {
UserPO user = userMapper.findByName(userDO.getName());
String salt = user.getSalt();
String password = encryptPassword(userDO.getPassword(), salt);
return password.equals(user.getPassword());
}
}
package com.loong.nba.player.filter;
import com.loong.nba.player.dao.RoleMapper;
import com.loong.nba.player.dao.UserMapper;
import com.loong.nba.player.pojo.LoginUserBO;
import com.loong.nba.player.pojo.Role;
import com.loong.nba.player.pojo.UserPO;
import com.loong.nba.player.util.JwtUtil;
import com.loong.nba.player.util.SessionUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
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;
import java.util.Collections;
import java.util.List;
/**
* @author jilong
* @date 2023/5/18
*/
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final String tokenHeader = "Authorization";
private final String tokenPrefix = "Bearer ";
private final JwtUtil jwtUtil;
private final StringRedisTemplate redisTemplate;
private final UserMapper userMapper;
private final RoleMapper roleMapper;
public JwtAuthenticationFilter(JwtUtil jwtUtil, StringRedisTemplate redisTemplate, UserMapper userMapper, RoleMapper roleMapper) {
this.jwtUtil = jwtUtil;
this.redisTemplate = redisTemplate;
this.userMapper = userMapper;
this.roleMapper = roleMapper;
}
@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String header = request.getHeader(tokenHeader);
if (!ObjectUtils.isEmpty(header) && header.startsWith(tokenPrefix)) {
String token = header.replace(tokenPrefix, "");
if (!ObjectUtils.isEmpty(token) && jwtUtil.validateToken(token)) {
String username = jwtUtil.getUsernameFormToken(token);
UserPO userPO = userMapper.findByName(username);
List roleList = roleMapper.findByUserId(userPO.getId());
LoginUserBO loginUserBO = new LoginUserBO();
loginUserBO.setUserId(userPO.getId());
loginUserBO.setUsername(username);
loginUserBO.setPassword(userPO.getPassword());
loginUserBO.setRoleList(roleList);
if (redisTemplate.opsForValue().get(username) != null) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
username, null, Collections.emptyList());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
SessionUtils.setLoginUser(loginUserBO);
}
}
}
chain.doFilter(request, response);
}
}
package com.loong.nba.player.controller;
import com.loong.nba.player.pojo.UserDO;
import com.loong.nba.player.pojo.UserPO;
import com.loong.nba.player.service.UserServiceImpl;
import com.loong.nba.player.util.JwtUtil;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
/**
* @author jilong
* @date 2023/5/15
*/
@RestController
public class LoginController {
private final JwtUtil jwtUtil;
private final UserServiceImpl userService;
public LoginController(JwtUtil jwtUtil, UserServiceImpl userService) {
this.jwtUtil = jwtUtil;
this.userService = userService;
}
@PostMapping("/login")
public ResponseEntity postLogin(@RequestBody UserDO userDO, HttpServletResponse response) {
if (!userService.comparePassword(userDO)) {
return ResponseEntity.ok(new UserDO());
}
String token = jwtUtil.generateToken(userDO.getName());
response.addHeader("Authorization", "Bearer " + token);
return ResponseEntity.ok(userDO);
}
@PostMapping("/user")
public UserPO addUser(@RequestBody UserDO userDO) {
return userService.addUser(userDO);
}
@GetMapping("/test")
@PreAuthorize("@permission.hasRole("+"'管理员'"+")")
public String test() {
return "hello test";
}
}
INSERT INTO user (username, user_password, salt)
VALUES (#{userPO.username}, #{userPO.password}, #{userPO.salt})