spring security之@Secured角色權限

前言

寫完後臺接口之後,剩下的是加入權限控制。最簡單是關於spring security加入的@Secured註解,註解里加入容許訪問的角色便可。spring

@Secured("ROLE_VIEWER")
public String getUsername() {
    SecurityContext securityContext = SecurityContextHolder.getContext();
    return securityContext.getAuthentication().getName();
}

可是他裏邊的角色是如何與我定義的用戶角色對應的呢,又是對應的哪一個字段呢,我也沒找到解釋,想着寫完之後去研究一下。先寫了一個嘗試一下,在獲取clazz分頁數據接口上加入只能管理員訪問,而後登錄一個學生用戶,用url跳轉到clazz模塊,發現起做用了。
image.png
而後就將大部分接口都加入了@Secured註解。再啓動項目就發現就不對勁了,他是起做用了,可是對全部角色用戶都攔截了。一開始想到寫法不太正確,可是又不知道里頭填的角色名稱跟什麼相對應。這能去研究他的原理。數組

過程

去網上找了不少資料,對於這方面的描述都只停留在使用層面。都是說你只要加入@Secured("ADMIN")註解,就只有你角色是ADMIN的用戶才能訪問。可是怎麼讓用戶角色是ADMIN,並無詳細的介紹。
網上找不到資料,我問了學長關於原來項目的使用方法。學長告訴我是因爲實現UserDetailsService接口的loadUserByUsername方法裏去添加獲取到的user的角色。this

public UserDetails loadUserByUsername(String username) throws 
UsernameNotFoundException {
    logger.debug("根據用戶名查詢用戶");
    User user = this.userRepository.findByUsername(username);

    if (user == null) {
        logger.error("用戶名不存在");
        throw new UsernameNotFoundException("用戶名不存在");
    }

    logger.debug("獲取用戶受權菜單");
    Set<Menu> menus = new HashSet<>();
    for (Role role : user.getRoles()) {
        menus.addAll(role.getMenus());
    }

    logger.debug("初始化受權列表");
    List<SimpleGrantedAuthority> authorities = new ArrayList<>();

    logger.debug("根據菜單進行 API 受權");
    for (Menu menu : menus) {
        authorities.add(new SimpleGrantedAuthority(menu.getRoleName()));
    }

    logger.debug("構造用戶");
    return new YzUserDetail(user, username, user.getPassword(), authorities);
}

而後與@Secured註解裏咱們定義的角色名稱相對應。
我一看個人項目中loadUserByUsername方法向UserDetail方法傳入的authorities參數是一個new ArrayList<>()空數組。我就試着變成傳入角色數組,再去實驗,仍是將全部角色攔截了。並無解決問題。
而後我在loadUserByUsername方法上打斷點,發現看看是否執行,發現只有登陸的時候執行了,可是是獲取到用戶角色了的。image.png
我就想多是其餘地方實現的傳入角色。我去找哪還用了UserDetail。發如今咱們自定義的spring security過濾器中也運用了,url

if (userOptional.isPresent()) {
    // token有效,則設置登陸信息
    PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(
            new UserServiceImpl.UserDetail(userOptional.get(), new ArrayList<>()), null, new ArrayList<>());
    SecurityContextHolder.getContext().setAuthentication(authentication);
 }

我在PreAuthenticatedAuthenticationToken中加入角色authorities,發現居然能夠了。spa

if (userOptional.isPresent()) {
    // token有效,則設置登陸信息
    // 設置用戶角色
    List<SimpleGrantedAuthority> authorities = new ArrayList<>();
    for (Role role: userOptional.get().getRoles()) {
       authorities.add(new SimpleGrantedAuthority(role.getValue()));
    }
    PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(
            new UserServiceImpl.UserDetail(userOptional.get(), new ArrayList<>()), null, authorities);
    SecurityContextHolder.getContext().setAuthentication(authentication);
}

那爲何和老項目裏實現的方法不同呢。我去老項目裏搜了一下關於debug

SecurityContextHolder.getContext().setAuthentication()

的實現,發現
// 設置當前登陸用戶,設置其狀態爲:已認證。其它同類過濾器獲取到已認證的狀態後將跳過其自身的認證過程code

UserDetails userDetails = this.authService.loadUserByUsername(authentication.getName());
        SecurityContextHolder.getContext().setAuthentication(
                new SuperPasswordAuthenticationToken(
                        userDetails,
                        authentication.getCredentials(),
                        userDetails.getAuthorities()
                ));

他是在實現裏去傳入了SuperPasswordAuthenticationToken ,在SuperPasswordAuthenticationToken裏傳入了userDetails.getAuthorities()
顯然SuperPasswordAuthenticationTokenPreAuthenticatedAuthenticationToken
繼承同一個父類。
image.png繼承

image.png

最後起做用的是咱們token

SecurityContextHolder.getContext().setAuthentication(authentication);

裏傳入的用戶,其餘任何實現都爲咱們傳入的user相關信息服務。接口

總結

使用一個新東西不能照貓畫貓。裏頭要傳什麼參數要理解其中的原理才行。

相關文章
相關標籤/搜索