Spring Security 사용자 권한 부여 실현

머리말
지난번 에는 사용 Spring SecurityAngular 이 사용자 인증 을 실현 했다.Spring Security 및 Angular 사용자 인증 실현
이번에 저 희 는 Spring Security 의 권한 수여 체 제 를 통 해 사용자 의 권한 수 여 를 실현 합 니 다.
실현 은 매우 간단 해서 모두 가 진지 하 게 들 으 면 알 아들 을 수 있다.
이루어지다
권한 설계
프론트 데스크 톱 은 메뉴 의 권한 통 제 를 실 현 했 지만 백 스테이지 인 터 페 이 스 는 아직 보호 되 지 않 았 습 니 다. 사용자 가 로그 인 에 성공 하면 모든 인 터 페 이 스 를 호출 할 수 있 습 니 다.
저 희 는 사용자 가 어떤 메뉴 의 권한 을 가지 고 있 는 지, 배경 에서 해당 메뉴 의 인터페이스 에 만 접근 할 수 있 기 를 바 랍 니 다.
예 를 들 어 사용 자 는 컴퓨터 팀 이 관리 하 는 메뉴 가 있 으 면 컴퓨터 팀 과 관련 된 추가 삭제 검사 인 터 페 이 스 를 방문 할 수 있 지만 다른 인 터 페 이 스 는 접근 할 수 없다.Spring Security 의 디자인Spring Security 의 디자인 에 따라 사용 자 는 역할 에 대응 하고 역할 은 배경 인터페이스 에 대응 합 니 다.이것 은 아무런 문제 가 없다.
예시
한 인터페이스 에 주 해 를 추가 하고 내부 에 권한 표현 식 을 추가 합 니 다.
@GetMapping
@Secured("ROLE_ADMIN")
public List getAll() {
    return hostService.getAll();
}

그리고 사용자 에 게 @Secured 의 역할 을 만 듭 니 다.
여기 서 저 희 는 사용자 에 게 Spring Security 의 역할 권한 수 여 를 추가 합 니 다. ROLE_ADMIN 방법 상의 getAll 주석 중의 매개 변수 와 일치 하여 이 사용자 가 이 방법 에 접근 할 수 있 는 권한 이 있다 는 것 을 나타 냅 니 다. 이것 이 바로 권한 수여 입 니 다.
private UserDetails createUser(User user) {
    logger.debug("       ");
    List authorities = new ArrayList<>();

    logger.debug("    ");
    authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));

    logger.debug("    ");
    return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities);
}

모자라다
우수한 안전 프레임 워 크 로 서 @Secured("ROLE_ADMIN") 이렇게 디자인 하 는 것 은 아무런 문제 가 없다. 우 리 는 간단 한 몇 줄 코드 만 있 으 면 인터페이스의 권한 수여 관 리 를 실현 할 수 있다.
하지만 우리 의 요구 에 부합 되 지 않 는 다.
Google 은 시스템 에서 사용자 가 다 중 역할 에 대응 할 것 을 요구 합 니 다.
그러나 우리 의 역할 은 동적 설정 을 할 수 있 도록 요구 하 는 것 이다. 오늘 은 시스템 관리자 의 역할 이 있 고 내일 은 교사 의 역할 을 추가 할 수 있다.
사용자 권한 수여 에 있어 서 동적 설정 을 실현 할 수 있 습 니 다. 사용자 의 권한 목록 은 Spring Security 이기 때문에 저 는 데이터 베이스 에서 현재 사용자 의 역할 을 찾 은 다음 List 들 어 갈 수 있 습 니 다.
private UserDetails createUser(User user) {
    logger.debug("       ");
    List authorities = new ArrayList<>();

    logger.debug("    ");
    authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));

    logger.debug("    ");
    return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities);
}

그러나 인터페이스 단계 에 서 는 동적 설정 을 실현 할 수 없다.여러분 생각해 보 세 요. 주해 에서 요구 하 는 매개 변 수 는 반드시 상수 여야 합 니 다. 우리 가 동적 으로 설정 하고 싶 어도 실현 할 수 없 는 것 입 니까?
@GetMapping
@Secured("ROLE_ADMIN")
public List getAll() {
    return hostService.getAll();
}

그래서 우 리 는 설정 의 제한 을 설명 하기 때문에 add 에서 역할 은 정적 이다.
재 설계
우리 의 역할 은 동태 적 인 것 이 고 Spring Security 중의 역할 은 정태 적 인 것 이기 때문에 우리 의 역할 을 Spring Security 중의 역할 에 직접 투사 할 수 없고 매 핑 하려 면 우리 시스템 에서 정태 적 인 대상 과 대응 해 야 한다.
캐릭터 가 동적 이 야. 이 건 안 돼.근 데 우리 메뉴 는 정적 이 잖 아.
기능 모듈 은 우리 가 개발 한 것 입 니 다. 메뉴 는 이렇게 고정 되 어 있 습 니 다. 사용자 관리, 역할 관리, 시스템 설정 등 이 있 습 니 다. 우리 가 개발 하 는 동안 이미 고정 되 었 습 니 다. 우 리 는 메뉴 결합 Spring Security 을 사용 하여 권한 을 부여 할 수 있 습 니까?
이 그림 을 진지 하 게 보고 이 그림 을 이해 하면 너 는 나의 디자인 사상 을 알 게 될 것 이다.
역할 은 동적 입 니 다. 저 는 권한 을 부여 하지 않 고 정적 메뉴 로 권한 을 부여 합 니 다.
정적 메뉴 대응 Spring Security 에서 정적 역할, 캐릭터 가 백 엔 드 인터페이스 에 대응 합 니 다. 이렇게 디자인 하면 사용자 가 어떤 메뉴 의 권한 을 가지 고 있 는 지, 이 메뉴 에 호출 된 해당 인터페이스 권한 만 가지 고 있다 는 구상 을 실현 합 니 다.
부호화
디자인 이 다 되 었 으 니 함께 코드 를 쓰 자.
권한 부여 주석 선택Spring Security 에는 여러 가지 권한 수여 주해 가 있 는데 개인 이 대 비 를 거 친 후에 Spring Security 주 해 를 선택 합 니 다. 왜냐하면 저 는 이 주해 설정 항목 이 사람들 에 게 이해 되 기 쉽다 고 생각 하기 때 문 입 니 다.
public @interface Secured {
    /**
     * Returns the list of security configuration attributes (e.g. ROLE_USER, ROLE_ADMIN).
     *
     * @return String[] The secure method attributes
     */
    public String[]value();
}

캐릭터 를 직접 쓰 는 문자열 배열 을 보 내 면 됩 니 다.
@Secured("ROLE_ADMIN")                      //     `ROLE_ADMIN`      
@Secured({"ROLE_ADMIN", "ROLE_TEACHER"})    //     `ROLE_ADMIN`、`ROLE_TEACHER`        

메모: 이 문자열 은 @Secured 로 시작 해 야 합 니 다. ROLE_ 캐릭터 설정 으로 간주 합 니 다. 그렇지 않 으 면 유효 하지 않 습 니 다.
주석 사용 하기 Spring Security기본 값 @Secured 은 권한 부여 주 해 를 차단 하지 않 고 주 해 를 추가 합 니 다 Spring Security 주 해 를 사용 하 는 전역 방법 으로 차단 합 니 다.
@EnableGlobalMethodSecurity(securedEnabled = true)         //         ,  @Secured  

메뉴 캐릭터 맵
메뉴 에 새 필드 @EnableGlobalMethodSecurity 를 만들어 서 시스템 메뉴 가 어떤 역할 에 대응 하 는 지 설명 합 니 다.
//     Spring Security        
@Column(nullable = false)
private String securityRoleName;

모든 @Secured 캐릭터 의 설정 정 보 를 저장 하고 전역 호출 에 사용 할 클래스 를 만 듭 니 다.
여 기 는 매 거 진 것 을 사용 할 수 없습니다. securityRoleName 주석 에서 요구 하 는 것 은 Spring Security 배열 이 어야 합 니 다. 매 거 진 것 이 라면 Spring Security 형식 으로 문자열 정 보 를 얻어 야 합 니 다. 그러나 유감스럽게도 주석 에서 요구 하 는 것 은 상수 여야 합 니 다.
지난번 사용자 정의 @Secured 상태 코드 를 사용 할 때 확장 할 수 없 는 손 해 를 입 었 던 것 을 기억 합 니 다. 이후 에는 더 이상 매 거 할 필요 가 없습니다.매 거 진 인 터 페 이 스 를 사용 하 더 라 도 이 인 터 페 이 스 를 매 거 진 실현 할 것 입 니 다. 매 거 진 성명 방법의 매개 변수 유형 을 사용 하지 않 고 인터페이스 성명 을 사용 하여 확장 이 편리 합 니 다.
package club.yunzhi.huasoft.security;

/**
 * @author zhangxishuo on 2019-03-02
 * Yunzhi Security   
 *        
 */
public class YunzhiSecurityRole {

    public static final String ROLE_MAIN = "ROLE_MAIN";

    public static final String ROLE_HOST = "ROLE_HOST";

    public static final String ROLE_GROUP = "ROLE_GROUP";

    public static final String ROLE_USER = "ROLE_USER";

    public static final String ROLE_ROLE = "ROLE_ROLE";

    public static final String ROLE_SETTING = "ROLE_SETTING";

}

예시
@GetMapping
@Secured({YunzhiSecurityRole.ROLE_HOST, YunzhiSecurityRole.ROLE_GROUP})
public List getAll() {
    return hostService.getAll();
}

사용자 권한 부여
코드 는 권한 수여 방향 을 나타 낸다. 현재 사용자 의 메뉴 를 옮 겨 다 니 며 메뉴 에 대응 하 는 String 캐릭터 이름 에 따라 권한 을 부여 한다.
private UserDetails createUser(User user) {
    logger.debug("           ");
    Set menus = webAppMenuService.getAllAuthMenuByUser(user);

    logger.debug("       ");
    List authorities = new ArrayList<>();

    logger.debug("      ,      ");
    for (WebAppMenu menu : menus) {
        authorities.add(new SimpleGrantedAuthority(menu.getSecurityRoleName()));
    }

    logger.debug("    ");
    return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities);
}

주: 여기 서 YunzhiSecurityRoleEnum.ROLE_MAIN.name() 타성 로드 로 인 한 오류 가 발생 했 습 니 다. 트 랜 잭 션 방지 HTTP 를 사용 하여 닫 기 Security 를 방지 하고 심층 원 리 는 아직 연구 중 입 니 다.
유닛 테스트
유닛 테스트 는 매우 간단 하여 같은 기능 을 쓰 는 사람 이 참고 하도록 제공 합 니 다.
@Test
public void authTest() throws Exception {
    logger.debug("      ");
    WebAppMenu hostMenu = webAppMenuRepository.findByRoute("/host");
    WebAppMenu groupMenu = webAppMenuRepository.findByRoute("/group");
    WebAppMenu settingMenu = webAppMenuRepository.findByRoute("/setting");

    logger.debug("    ");
    List roleList = new ArrayList<>();

    Role roleHost = new Role();
    roleHost.setWebAppMenuList(Collections.singletonList(hostMenu));
    roleList.add(roleHost);

    Role roleGroup = new Role();
    roleGroup.setWebAppMenuList(Collections.singletonList(groupMenu));
    roleList.add(roleGroup);

    Role roleSetting = new Role();
    roleSetting.setWebAppMenuList(Collections.singletonList(settingMenu));
    roleList.add(roleSetting);

    logger.debug("    ");
    roleRepository.saveAll(roleList);

    logger.debug("    ");
    User user = userService.getOneUnSavedUser();

    logger.debug("        ");
    String username = user.getUsername();
    String password = user.getPassword();

    logger.debug("    ");
    userRepository.save(user);

    logger.debug("    ");
    String token = this.loginWithUsernameAndPassword(username, password);

    logger.debug("       host,  403");
    this.mockMvc.perform(MockMvcRequestBuilders.get(HOST_URL)
            .header(TOKEN_KEY, token))
            .andExpect(status().isForbidden());

    logger.debug("    Host  ");
    user.getRoleList().clear();
    user.getRoleList().add(roleHost);
    userRepository.save(user);

    logger.debug("    ,     ");
    token = this.loginWithUsernameAndPassword(username, password);

    logger.debug("  Host    ,  200");
    this.mockMvc.perform(MockMvcRequestBuilders.get(HOST_URL)
            .header(TOKEN_KEY, token))
            .andExpect(status().isOk());

    logger.debug("    Group  ");
    user.getRoleList().clear();
    user.getRoleList().add(roleGroup);
    userRepository.save(user);

    logger.debug("    ,     ");
    token = this.loginWithUsernameAndPassword(username, password);

    logger.debug("  Group    ,  200");
    this.mockMvc.perform(MockMvcRequestBuilders.get(HOST_URL)
            .header(TOKEN_KEY, token))
            .andExpect(status().isOk());

    logger.debug("    Setting  ");
    user.getRoleList().clear();
    user.getRoleList().add(roleSetting);
    userRepository.save(user);

    logger.debug("    ,     ");
    token = this.loginWithUsernameAndPassword(username, password);

    logger.debug("  Setting    ,  403");
    this.mockMvc.perform(MockMvcRequestBuilders.get(HOST_URL)
            .header(TOKEN_KEY, token))
            .andExpect(status().isForbidden());
}

private String loginWithUsernameAndPassword(String username, String password) throws Exception {
    logger.debug("    ");
    byte[] encodedBytes = Base64.encodeBase64((username + ":" + password).getBytes());
    MvcResult mvcResult = this.mockMvc.perform(MockMvcRequestBuilders.get(LOGIN_URL)
            .header("Authorization", "Basic " + new String(encodedBytes)))
            .andExpect(status().isOk())
            .andReturn();

    logger.debug("       token");
    String json = mvcResult.getResponse().getContentAsString();
    JSONObject jsonObject = JSON.parseObject(json);
    return jsonObject.getString("token");
}

총결산
개원 커 뮤 니 티 감사합니다.Hibernate
다섯 줄 코드 (주석 제외), 주석 하나.그동안 우 리 를 괴 롭 혔 던 권한 문 제 를 해결 했다.

좋은 웹페이지 즐겨찾기