검토
앞에서 스프링 시큐리티가 로그인한 사용자의 권한을 인가할 수 있다고 소개했는데, 어떤 리소스에 접근할 수 있고 어떤 리소스에 접근할 수 없는지 알 수 없다면 리소스 접근 권한 설정 방법을 모르면 이전 글로 돌아가면 된다. "단계별 스프링 보안 학습 7장 , 사용자 테이블과 권한 테이블을 기반으로 권한을 구성하는 방법은? 더 많이 배울수록 더 쉬워집니다 ." 이전에 소개한 것이 구성 파일에서 직접 인터페이스를 구성하는 것임을 눈치채셨는지 모르겠습니다. 이것은 여전히 많은 실제 비즈니스 시나리오에 충분하지 않습니다. 예를 들어 , 일부 기업에는 백그라운드 관리가 있을 것입니다. 기업 관리자는 백그라운드에서 첫 페이지의 메뉴를 구성하고, 어떤 메뉴에 권한을 부여하고, 어떤 사용자 ID에 권한이 있는지 이러한 메뉴에 액세스할 수 있습니다. 그렇지 않으면 액세스할 수 없습니다. 그들을. 관리자가 프런트엔드 메뉴 권한을 설정할 때 매번 코드 설정을 변경해야 한다면 비현실적일 수밖에 없는데 동적으로 설정할 수 있는 방법은 없을까? 물론 있습니다. 그것이 이 글의 주제입니다.
이 기사를 읽기 전에 이 기사를 이해하는 데 도움이 되도록 이전 기사를 읽으십시오.
- 인터뷰에서 스프링 보안에 익숙하지 않다고 말하지 마십시오. 데모는 면접관을 속이려고 합니다.
- 스프링 시큐리티를 배우기 위한 차근차근 2부, 기본 사용자를 수정하는 방법은?
- 단계별 스프링 보안 학습 3부, 로그인 페이지를 사용자 정의하는 방법은 무엇입니까? 로그인 콜백?
- 단계별 스노우 스프링 보안 4부, 로그인 과정은 어떤가요? 로그인 사용자 정보는 어디에 저장되나요?
- 단계별 학습 스프링 보안 5장, 리디렉션 및 서버 점프를 처리하는 방법은 무엇입니까? 로그인은 어떻게 JSON 문자열을 반환합니까?
- 스프링 보안 단계별 학습, 여섯 번째 장, 로그인 확인, mybatis 통합을 위해 데이터베이스에서 사용자를 읽는 방법을 알려줍니다.
- 스프링 시큐리티 7장 단계별로 사용자 테이블과 권한 테이블을 기반으로 권한을 설정하는 방법은? 더 많이 배울수록 더 쉬워집니다
- 스프링 보안 8장 단계별로 비밀번호 암호화를 구성하는 방법은 무엇입니까? 여러 암호화 체계가 지원됩니까?
- 스프링 시큐리티 단계별 학습, 9장, 소스 코드 해석 지원 및 다중 암호화 체계의 샘플 코드
10. 스프링 시큐리티 단계별 학습, 10장 토큰으로 로그인하는 방법은? JWT 데뷔
동적 권한이 필요한 이유는 무엇입니까?
한마디로 : 기업의 비즈니스 요구를 유연하게 충족시키기 위해
예를 들어
대부분의 회사는 소규모에서 대규모로 성장하는 과정에 있습니다.초기에는 회사에 10명밖에 없었고 Xiao Zhang은 HR 부서의 유일한 사람이었습니다.Xiao Zhang은 재무, 채용 등 모든 역할을 담당했습니다. , 프런트 데스크 보기 권한
나중에 회사는 점차 확장되었고 인사 부서에도 Xiao Li와 Xiao Wang 2 명을 추가했습니다.모집, 재무, 프런트 데스크 등 모든 권한, 결석 한 Xiao Li의 작업 항목을 언제든지 확인하거나 운영 할 수 있습니다. 오늘 퇴근한 사람, 출장비 환급 신청한 사람, 환급 금액, 소규모 Wang 채용 및 프론트 데스크 업무를 보거나 운영할 수 있으며, Xiao Li는 재무를 담당하고 있기 때문에 금융 사업만 보고 운영할 수 있지만 Xiao Wang의 사업을 보고 운영할 수 있는 권한을 초과할 수 없습니다.
회사는 계속 확장되고 공개됩니다. 현재 HR 부서에는 1,000명이 있고 많은 지점이 있습니다. 사람들은 매일 가입하고 떠날 수 있으며 권한을 수정해야 합니다. 이전에 소개한 내용을 따르는 경우 URL을 추가하십시오. 구성 파일에서 권한 구성이 죽었고 매번 다시 시작한 후에 적용되므로 의심 할 여지없이 사람들이 충돌합니다
권한을 더 잘 관리하는 방법은 무엇입니까?
데이터베이스 설계
권한을 잘 관리하려면 먼저 데이터베이스를 잘 설계해야 권한 관리가 매우 간단해집니다.
그리고 데이터베이스를 설계하는 방법은 무엇입니까? 가장 중요한 점은 격리입니다. 즉, 테이블은 하나의 비즈니스만 나타내며 다른 비즈니스와 혼합되어서는 안 됩니다.
첫째, 있어야합니다
사용자 테이블 정의
CREATE TABLE `h_user` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(500) NOT NULL COMMENT '密码',
`enabled` tinyint(1) NOT NULL COMMENT '是否启动,0-不启用,1-启用',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT '用户表';
역할 테이블 정의
CREATE TABLE `h_role` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL COMMENT '角色名称',
`code` varchar(50) NOT NULL COMMENT '角色编码',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT '角色表';
역할은 실제로 권한입니다.
메뉴 테이블
CREATE TABLE `h_menu` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL COMMENT '菜单名称',
`url` varchar(200) NOT NULL COMMENT '菜单URL',
`parent_id` int NOT NULL default 0 COMMENT '上级菜单id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT '菜单表';
메뉴 테이블은 어떤 메뉴를 관리할 권한으로 넘겨야 하는지 정의하고, URL은 권한 관리 인터페이스로 넘겨준다.
역할 인원 테이블
CREATE TABLE `h_role_user` (
`role_id` int NOT NULL COMMENT '角色id',
`user_id` int NOT NULL COMMENT '菜单id'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT '角色用户表';
역할 메뉴 테이블
CREATE TABLE `h_role_menu` (
`role_id` int NOT NULL COMMENT '角色id',
`menu_id` int NOT NULL COMMENT '菜单id'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT '角色菜单表';
개인, 역할 및 메뉴 테이블 간의 관계가 분리된 이유는 무엇입니까? 메뉴 테이블을 역할 테이블에 병합하지 않는 이유는 무엇입니까? 역할 테이블에 사용자 ID를 저장하는 것이 좋지 않을까요?
사실 이렇게 하면 정말 안 좋은데 예전에 재무를 담당하던 사용자가 지금은 다른 부서로 옮겨져 지금은 회계와 정산을 담당하고 있다면 사용자의 역할을 삭제해야 하고, 그런 다음 역할에 대한 두 개의 새 레코드를 만들고 역할 이름, 역할 코드, 메뉴 URL을 채우고 사람을 선택하는 등의 작업을 수행합니다.
하지만 이 디자인은 어떨까요?
역할과 메뉴의 관계는 기본적으로 변경되지 않으며, 설정 후에도 기본적으로 변경되지 않습니다. 주요 변화는 사람입니다.오늘은 1,000명의 신규 직원을 채용하거나 500명의 직원을 떠날 수 있지만 역할 사용자 테이블만 유지하면 되므로 더 쉽지 않습니까?
자, 이 테이블에 일부 초기 데이터를 추가해 보겠습니다.
INSERT INTO `h_user` (`username`, `password`, `enabled`) VALUES ('harry', '{bcrypt}$2a$10$gQv1oUFK/LvbV7p4Nk0xE.Gn8H1lYV1hqVJfReWSUYUQBfCkGq2uy', '1');
INSERT INTO `h_user` (`username`, `password`, `enabled`) VALUES ('mike', '{bcrypt}$2a$10$gQv1oUFK/LvbV7p4Nk0xE.Gn8H1lYV1hqVJfReWSUYUQBfCkGq2uy', '1');
INSERT INTO `h_role` (`name`, `code`) VALUES ('超级管理员', 'admin'),('用户管理员','user');
INSERT INTO `h_menu` (`name`, `url`) VALUES ('后台管理', '/admin'),('用户管理','/user/**');
INSERT INTO `h_role_menu` (`role_id`, `menu_id`) VALUES (1, 1),(1,2),(2,2);
INSERT INTO `h_role_user` (`role_id`, `user_id`) VALUES (1, 1),(2,2);
- 두 명의 사용자 harry와 mike가 각각 ID 1과 2로 초기화됩니다.
- 슈퍼 관리자와 사용자 관리자의 두 가지 역할이 초기화되며 여기의 코드는 실제로 권한입니다.
- 백그라운드 관리와 사용자 관리 2가지 메뉴 초기화
- 세 가지 역할 메뉴 레코드가 초기화됨
- User: harry는 최고관리자 역할, Mike: 사용자는 관리자 역할이므로 사용자 harry는 백그라운드 관리 및 사용자 관리 메뉴에 접근할 수 있고, mike는 이것만 접근할 수 있습니다. 메뉴 또는 인터페이스
자, 데이터베이스 설계에 대해 이야기 한 후 구현 방법을 소개하겠습니다.
동적 권한 관리를 구현하는 방법은 무엇입니까?
동적으로 URL 권한 구성 얻기
MenuFilterInvocationSecurityMetadataSource 클래스를 생성하여 FilterInvocationSecurityMetadataSource 인터페이스의 Collection getAttributes(Object object) 메서드를 구현합니다. 이 구현 클래스는 주로 데이터베이스에서 메뉴의 권한을 읽고 권한을 동적으로 로드합니다.
권위의 판단
MenuAccessDecisionManager 클래스를 생성하여 메뉴 또는 인터페이스의 권한을 확인할 수 있는 AccessDecisionManager 인터페이스의 결정(인증 인증, 개체 개체, 컬렉션 컬렉션) 메서드를 구현합니다.
좋아, 프로젝트 구축을 시작하고 기적을 목격하자
빌드 프로젝트
프로젝트 생성: security-mybatis-jwt-perm
이 프로젝트는 이전 기사 " 스프링 보안 단계별 학습, 챕터 10 토큰으로 로그인하는 방법?"을 기반으로 합니다. JWT데뷔 ' 변신 프로젝트
Maven 종속성을 추가합니다.
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.18</version>
<scope>test</scope>
</dependency>
이 종속성은 주로 컬렉션 및 문자열 툴킷과 같은 내부 툴킷을 사용합니다.
pom 프로젝트 이름과 artifactId를 security-mybatis-jwt-perm으로 통일되도록 수정합니다.
새로운 데이터베이스 운영 관련 클래스 및 매퍼
엔터티 클래스 만들기
- 메뉴 클래스 만들기
@Data
public class Menu {
//菜单id
private int id;
//菜单名称
private String name;
//菜单URL
private String url;
//上级菜单id
private int parentId;
}
- Role 클래스를 생성하는 것은 실제로 원래 Authorities 클래스를 Role 클래스로 변경한 다음 몇 개의 필드를 추가하는 것입니다.
public class Role implements GrantedAuthority {
private int id;
private String name;
//权限编码
private String code;
@Override
public String getAuthority() {
return "ROLE_"+code;
}
}
- 사용자 클래스 수정
@Data
public class User implements UserDetails {
private int id;
private String password;
private String username;
private boolean accountNonExpired=true;
private boolean accountNonLocked=true;
private boolean credentialsNonExpired=true;
private boolean enabled;
private List<Role>authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return accountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return accountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
@Override
public boolean isEnabled() {
return enabled;
}
}
mybatis의 mapper.xml 추가
- RoleMapper.xml 추가
<mapper namespace="com.harry.security.mapper.RoleMapper">
<select id="findRolesByUserId" resultType="com.harry.security.entity.Role" parameterType="integer">
select role.id,role.`name`,role.`code` from h_role role
LEFT JOIN h_role_user ru on ru.role_id=role.id
WHERE ru.user_id=#{userId}
</select>
<select id="findRolesByUrl" resultType="com.harry.security.entity.Role" parameterType="string">
select role.id,role.`name`,role.`code` from h_role role
LEFT JOIN h_role_menu rm on rm.role_id=role.id
LEFT JOIN h_menu m on m.id=rm.menu_id
WHERE m.url=#{url};
</select>
</mapper>
여기에 두 가지 쿼리가 제공됩니다.
- findRolesByUserId는 사용자 ID에 따라 사용자가 가진 역할을 쿼리하는 것입니다.
- findRolesByUrl은 메뉴 URL에 따라 메뉴가 속한 역할을 쿼리하는 것입니다.
- MenuMapper.xml 추가
<mapper namespace="com.harry.security.mapper.MenuMapper">
<select id="findMenusByRoleId" resultType="com.harry.security.entity.Menu" parameterType="integer">
SELECT m.id,m.`name`,m.parent_id,m.url from h_menu m
LEFT JOIN h_role_menu rm
on rm.menu_id=m.id
WHERE rm.role_id=#{roleId};
</select>
<select id="findAllMenus" resultType="com.harry.security.entity.Menu">
SELECT m.id,m.`name`,m.parent_id,m.url from h_menu m
</select>
</mapper>
여기에 제공된 두 개의 쿼리 인터페이스도 있습니다.
- findMenusByRoleId 메서드는 들어오는 역할 ID에 따라 역할 아래의 관리 메뉴를 쿼리하는 것입니다.
- findAllMenus 메소드는 모든 메뉴를 조회하는 것입니다.
- UserMapper.xml 수정
<mapper namespace="com.harry.security.mapper.UserMapper">
<resultMap id="BaseUser" type="com.harry.security.entity.User" >
<id property="id" column="id" ></id>
<result property="username" column="username" ></result>
<result property="password" column="password" ></result>
<result property="enabled" column="enabled" ></result>
</resultMap>
<select id="findUserByUsername" resultMap="BaseUser" parameterType="string">
select u.id,u.username,u.`password`,u.enabled from h_user u where u.username=#{username};
</select>
</mapper>
사용자는 사용자 이름을 기반으로 사용자 정보를 쿼리할 수 있는 인터페이스가 하나뿐입니다.
매퍼 인터페이스 추가
- RoleMapper 인터페이스 생성
@Mapper//指定这是一个操作数据库的mapper
public interface RoleMapper {
/**
* 根据用户id查找角色
* @param userId
* @return
*/
List<Role> findRolesByUserId(int userId);
/**
* 根据URL查找角色
* @param url
* @return
*/
List<Role> findRolesByUrl(String url);
}
- 다음을 통해 MenuMapper 인터페이스 생성
@Mapper//指定这是一个操作数据库的mapper
public interface MenuMapper {
/**
* 根据角色id查找菜单
* @param roleId
* @return
*/
List<Menu> findMenusByRoleId(int roleId);
/**
* 查询所有配置菜单
* @return
*/
List<Menu> findAllMenus();
}
- UserMapper 인터페이스가 이미 존재하므로 변경할 필요가 없습니다.
동적 권한 구성
동적으로 URL 권한 구성 얻기
MenuFilterInvocationSecurityMetadataSource 클래스를 만들고 FilterInvocationSecurityMetadataSource 인터페이스를 구현합니다.
public class MenuFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
@Autowired
private MenuMapper menuMapper;
@Autowired
private RoleMapper roleMapper;
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
Set<ConfigAttribute> set = new HashSet<>();
// 获取请求地址
String requestUrl = ((FilterInvocation) object).getRequestUrl();
log.info("requestUrl >> {}", requestUrl);
List<Menu> allMenus = menuMapper.findAllMenus();
if (!CollectionUtils.isEmpty(allMenus)) {
List<String> urlList = allMenus.stream().filter(f->f.getUrl().endsWith("**")?requestUrl.startsWith(f.getUrl().substring(0,f.getUrl().lastIndexOf("/"))):requestUrl.equals(f.getUrl())).map(menu -> menu.getUrl()).collect(Collectors.toList());
for (String url:urlList){
List<Role> roles = roleMapper.findRolesByUrl(url); //当前请求需要的权限
if(!CollectionUtils.isEmpty(roles)){
roles.forEach(role -> {
SecurityConfig securityConfig = new SecurityConfig(role.getAuthority());
set.add(securityConfig);
});
}
}
}
if (ObjectUtils.isEmpty(set)) {
return SecurityConfig.createList("ROLE_LOGIN");
}
return set;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
여기서는 주로 Collection getAttributes(Object object) 메서드를 구현하여 데이터베이스에서 메뉴 권한을 동적으로 로드합니다.
- 먼저 데이터베이스에서 모든 메뉴를 조회한 후 필터링하여 현재 요청을 만족하는 URL을 찾습니다. 매칭 방법에 정확히 일치하는 항목이 있거나 메뉴 구성이 **로 종료되어 퍼지 매칭 경로를 식별합니다. 이전 일치 요구 사항 액세스 제어를 충족하므로
- 그러면 필터링 후 만족하는 메뉴 URL에 따라 자신의 역할을 질의하고 제어가 필요한 메뉴의 역할을 반환함으로써 현재 접속한 요청 URL의 동적 설정이 완료된다.
- 마지막으로, 현재 요청 URL에 해당 역할이 구성되어 있지 않은 경우, 즉 집합 모음이 비어 있으면 기본 역할이 반환되며 주로 현재 요청 URL의 기본 역할을 식별하기 위해 사용자 정의할 수 있습니다. 기본 역할이 지정되지 않은 경우 기본적으로 시스템은 익명의 사용자에게 모든 인터페이스에 대한 액세스 권한을 부여하며 요청은 권한 제어를 위한 권한 판단 구현 클래스에 들어갈 수 없습니다.로그인하지 않아도 모든 액세스가 가능합니다. 불합리한 인터페이스입니다.이 점에 유의해야합니다.
- 다른 두 메서드의 구현은 다음과 같이 작성할 수 있습니다. 걱정하지 마세요.
여기서 주의할 점은 메뉴권한은 매번 데이터베이스 전체를 조회하는 것입니다 데이터가 많으면 성능에 영향을 줄 수 있습니다 여기에서 읽기 캐시를 수정할 수 있지만 추가할 때 캐시 데이터를 업데이트하고 메뉴 수정.
동적 권한 판단
MenuAccessDecisionManager 클래스를 만들어 AccessDecisionManager 인터페이스를 구현합니다.
public class MenuAccessDecisionManager implements AccessDecisionManager {
@Autowired
private RoleMapper roleMapper;
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
// 当前请求需要的权限
log.info("collection:{}", collection);
log.info("principal:{} authorities:{}", authentication.getPrincipal().toString());
Object principal = authentication.getPrincipal();
if(principal instanceof String){
throw new BadCredentialsException("未登录");
}
List<Role> roleList=null;
for (ConfigAttribute configAttribute : collection) {
// 当前请求需要的权限
String needRole = configAttribute.getAttribute();
if ("ROLE_LOGIN".equals(needRole)) {
return;
}
// 当前用户所具有的权限
if(roleList==null){
User loginUser= (User) authentication.getPrincipal();
roleList = roleMapper.findRolesByUserId(loginUser.getId());
}
for (GrantedAuthority grantedAuthority : roleList) {
// 包含其中一个角色即可访问
if (grantedAuthority.getAuthority().equals(needRole)) {
return;
}
}
}
throw new AccessDeniedException("SimpleGrantedAuthority!!");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
여기서 주요 구현 방법은 다음과 같습니다. 결정(인증 인증, 개체 개체, 컬렉션 컬렉션)
- collection은 현재 요청 URL의 권한 모음입니다.
- 먼저 사용자가 로그인했는지 확인합니다. 그렇지 않으면 기본적으로 익명 사용자가 됩니다.authentication.getPrincipal()은 문자열을 가져오고 로그인 후 User 개체를 가져와야 합니다. 로그인, throw 현재 사용자가 로그인하지 않았음을 프런트 엔드에 알리는 예외가 발생합니다. 프런트 엔드는 무엇을 하고 있습니까?
- 둘째, 컬렉션을 반복하고 권한을 제거합니다.
- 기본 권한인 경우 제어 없이 바로 전달
- 데이터베이스에서 현재 로그인한 사용자의 권한을 쿼리합니다.이 목적은 사용자 권한이 변경될 때 최신 사용자 권한 데이터를 적시에 가져오는 것을 방지하여 권한 제어의 적시성을 보장하는 것입니다.
- 메뉴 구성 권한인 경우 현재 로그인한 사용자에게 권한이 있는지 판단하여 권한이 있는 경우 스킵
- 현재 로그인한 사용자에게 권한이 없으면 이 인터페이스에 대한 권한이 없음을 나타내는 예외가 발생하며 액세스가 거부됩니다.
SecurityConfig 구성
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setSecurityMetadataSource(menuFilterInvocationSecurityMetadataSource); //动态获取url权限配置
object.setAccessDecisionManager(menuAccessDecisionManager); //权限判断
return object;
}
})
.and().formLogin()
.successHandler(authenticationSuccessHandler)
.failureHandler(authenticationFailureHandler)
.permitAll()
.and().exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and().logout().logoutSuccessHandler(logoutSuccessHandler)
.and().csrf().disable()
;
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
이것은 주로 연관된 동적 권한 구현 클래스를 구성하기 위한 것입니다.
이렇게 해서 기본적으로 모든 설정을 마쳤고, 나머지 인터페이스 정의는 아직 이전 글 " 스프링 보안 단계별 학습 10장 토큰으로 로그인하는 방법?" 에 있습니다. JWT Shining Debut '는 동일하며 변경된 부분은 위에서 설명한 바 있으며 효과를 확인하기 위해 테스트를 시작합니다.
테스트 시작
프로젝트를 시작하고 mike로 로그인하여 승인된 인터페이스에 액세스하면 정상적으로 액세스할 수 있습니다.
권한이 없는 인터페이스에 대한 액세스는 정상적으로 액세스할 수 없습니다.
이때 회사 직원의 직위 조정을 시뮬레이션합니다.데이터베이스에 역할 사용자 레코드를 추가하는 것은 Mike에게 최고 관리자 권한을 추가하는 것입니다.
INSERT INTO h_role_user
( role_id
, user_id
) VALUES (1, 2);
그런 다음 다시 방문하면 방문이 성공합니다. 즉, 동적 권한 제어를 완료한
다음 위에 추가된 역할 사용자 레코드를 삭제하고 삭제 후 권한이 없으면 harry 사용자로 로그인합니다. 기대효과와 일치하는
사실 제가 쓴 글들은 주로 프론트엔드와 백엔드의 분리에 치우쳐 있다는 것을 알게 되실 겁니다. 프런트엔드 지원입니다. 오시겠습니까? 사용자 데이터, 메뉴, 역할 등의 추가를 완료하기 위해 등록 페이지 상호 작용과 결합된 등록 인터페이스를 추가할 수 있습니다. 이 문서의 내용이 아닙니다. 실제 비즈니스를 기반으로 직접 추가하십시오. 필요
좋아, 됐어, 우리는 동적 권한 제어를 완료했습니다.상장 회사라도 매일 수천 명의 직원이 입사하고 수천 명의 직원이 퇴사하더라도 역할 사용자 테이블 만 유지하면 권한 제어가 완료됩니다.