亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

Spring Security 實現(xiàn)用戶授權(quán)

xfee / 840人閱讀

摘要:實現(xiàn)用戶認證本次,我們通過的授權(quán)機制,實現(xiàn)用戶授權(quán)。啟用注解默認的是不進行授權(quán)注解攔截的,添加注解以啟用注解的全局方法攔截。角色該角色對應菜單示例用戶授權(quán)代碼體現(xiàn)授權(quán)思路遍歷當前用戶的菜單,根據(jù)菜單中對應的角色名進行授權(quán)。

引言

上一次,使用Spring SecurityAngular實現(xiàn)了用戶認證。Spring Security and Angular 實現(xiàn)用戶認證

本次,我們通過Spring Security的授權(quán)機制,實現(xiàn)用戶授權(quán)。

實現(xiàn)十分簡單,大家認真聽,都能聽得懂。

實現(xiàn) 權(quán)限設計

前臺實現(xiàn)了菜單的權(quán)限控制,但后臺接口還沒進行保護,只要用戶登錄成功,什么接口都可以調(diào)用。

我們希望實現(xiàn):用戶有什么菜單的權(quán)限,只能訪問后臺對應該菜單的接口。

比如,用戶有計算機組管理的菜單,就可以訪問計算機組相關(guān)的增刪改查接口,但是其他的接口都不允許訪問。

Spring Security的設計

依據(jù)Spring Security的設計,用戶對應角色,角色對應后臺接口。這是沒什么問題的。

示例

某接口添加@Secured注解,內(nèi)部添加權(quán)限表達式。

@GetMapping
@Secured("ROLE_ADMIN")
public List getAll() {
    return hostService.getAll();
}

然后再為用戶創(chuàng)建Spring Security中的角色。

這里我們?yōu)橛脩籼砑?b>ROLE_ADMIN的角色授權(quán),與getAll方法上的@Secured("ROLE_ADMIN")注解中的參數(shù)一致,表示該用戶有權(quán)限訪問該方法,這就是授權(quán)。

private UserDetails createUser(User user) {
    logger.debug("初始化授權(quán)列表");
    List authorities = new ArrayList<>();

    logger.debug("角色授權(quán)");
    authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));

    logger.debug("構(gòu)建用戶");
    return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities);
}
不足

作為一款優(yōu)秀的安全框架而言,Spring Security這樣設計是沒有任何問題的,我們只需要簡簡單單的幾行代碼就能實現(xiàn)接口的授權(quán)管理。

但是卻不符合我們的要求。

我們要求,在我們的系統(tǒng)中,用戶對應多角色。

但是我們的角色是要求可以進行動態(tài)配置的,今天有一個系統(tǒng)管理員的角色,明天可能又加一個教師的角色。

在用戶授權(quán)這方面,是可以實現(xiàn)動態(tài)配置的,因為用戶的權(quán)限列表是一個List,我可以從數(shù)據(jù)庫查當前用戶的角色,然后add進去。

private UserDetails createUser(User user) {
    logger.debug("初始化授權(quán)列表");
    List authorities = new ArrayList<>();

    logger.debug("角色授權(quán)");
    authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));

    logger.debug("構(gòu)建用戶");
    return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities);
}

但是在接口級別,就無法實現(xiàn)動態(tài)配置了。大家想想,注解里,要求的參數(shù)必須是常量,就是我們想動態(tài)配置,也實現(xiàn)不了???

@GetMapping
@Secured("ROLE_ADMIN")
public List getAll() {
    return hostService.getAll();
}

所以,我們總結(jié),因為注解配置的限制,所以在Spring Security中角色是靜態(tài)的。

重新設計

我們的角色是動態(tài)的,而Spring Security中的角色是靜態(tài)的,所以不能將我們的角色直接映射到Spring Security中的角色,要映射也得拿一個我們系統(tǒng)中靜態(tài)的對象與之對應。

角色是動態(tài)的,這個不行了。但是我們的菜單是靜態(tài)的啊。

功能模塊是我們開發(fā)的,菜單就這么固定的幾個,用戶管理、角色管理、系統(tǒng)設置啥的,在我們開發(fā)期間就已經(jīng)固定下來了,我們是不是可以使用菜單結(jié)合Spring Security進行授權(quán)呢?

認真看這張圖,看懂了這張圖,你應該就明白了我的設計思想。

角色是動態(tài)的,我不用它授權(quán),我使用靜態(tài)的菜單進行授權(quán)。

靜態(tài)的菜單對應Spring Security中靜態(tài)的角色,角色再對應后臺接口,如此設計,就實現(xiàn)了我們的設想:用戶擁有哪個菜單的權(quán)限,就只擁有被該菜單調(diào)用的相應接口權(quán)限。

編碼

設計好了,一起來寫代碼吧。

授權(quán)注解選擇

Spring Security中有多種授權(quán)注解,個人經(jīng)過對比之后選擇@Secured注解,因為我覺得這個注解配置項更容易被人理解。

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();
}

直接寫一個角色的字符串數(shù)組傳進去即可。

@Secured("ROLE_ADMIN")                      // 需要擁有`ROLE_ADMIN`角色才可訪問
@Secured({"ROLE_ADMIN", "ROLE_TEACHER"})    // 用戶擁有`ROLE_ADMIN`、`ROLE_TEACHER`二者之一即可訪問

注意:這里的字符串一定是以ROLE_開頭,Spring Security才把它當成角色的配置,否則無效。

啟用@Secured注解

默認的Spring Security是不進行授權(quán)注解攔截的,添加注解@EnableGlobalMethodSecurity以啟用@Secured注解的全局方法攔截。

@EnableGlobalMethodSecurity(securedEnabled = true)         // 啟用全局方法安全,采用@Secured方式
菜單角色映射

在菜單中新建一個字段securityRoleName來聲明我們的系統(tǒng)菜單對應著哪個Spring Security角色。

// 該菜單在Spring Security環(huán)境下的角色名稱
@Column(nullable = false)
private String securityRoleName;

建一個類,用于存放所有Spring Security角色的配置信息,供全局調(diào)用。

這里不能用枚舉,@Secured注解中要求必須是String數(shù)組,如果是枚舉,需要通過YunzhiSecurityRoleEnum.ROLE_MAIN.name()格式獲取字符串信息,但很遺憾,注解中要求必須是常量。

還記得上次自定義HTTP狀態(tài)碼的時候,吃了枚舉類無法擴展的虧,以后再也不用枚舉了。就算用枚舉,也會設計一個接口,枚舉實現(xiàn)該接口,不用枚舉聲明方法的參數(shù)類型,而使用接口聲明,方便擴展。

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();
}
用戶授權(quán)

代碼體現(xiàn)授權(quán)思路:遍歷當前用戶的菜單,根據(jù)菜單中對應的Security角色名進行授權(quán)。

private UserDetails createUser(User user) {
    logger.debug("獲取用戶的所有授權(quán)菜單");
    Set menus = webAppMenuService.getAllAuthMenuByUser(user);

    logger.debug("初始化授權(quán)列表");
    List authorities = new ArrayList<>();

    logger.debug("遍歷授權(quán)菜單,進行角色授權(quán)");
    for (WebAppMenu menu : menus) {
        authorities.add(new SimpleGrantedAuthority(menu.getSecurityRoleName()));
    }

    logger.debug("構(gòu)建用戶");
    return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities);
}

注:這里遇到了Hibernate惰性加載引起的錯誤,啟用事務防止Hibernate關(guān)閉Session,深層原理目前還在研究。

單元測試

單元測試很簡單,供寫相同功能的人參考。

@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("構(gòu)造角色");
    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("構(gòu)造用戶");
    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("無授權(quán)用戶訪問host,斷言403");
    this.mockMvc.perform(MockMvcRequestBuilders.get(HOST_URL)
            .header(TOKEN_KEY, token))
            .andExpect(status().isForbidden());

    logger.debug("用戶授權(quán)Host菜單");
    user.getRoleList().clear();
    user.getRoleList().add(roleHost);
    userRepository.save(user);

    logger.debug("重新登錄, 重新授權(quán)");
    token = this.loginWithUsernameAndPassword(username, password);

    logger.debug("授權(quán)Host用戶訪問,斷言200");
    this.mockMvc.perform(MockMvcRequestBuilders.get(HOST_URL)
            .header(TOKEN_KEY, token))
            .andExpect(status().isOk());

    logger.debug("用戶授權(quán)Group菜單");
    user.getRoleList().clear();
    user.getRoleList().add(roleGroup);
    userRepository.save(user);

    logger.debug("重新登錄, 重新授權(quán)");
    token = this.loginWithUsernameAndPassword(username, password);

    logger.debug("授權(quán)Group用戶訪問,斷言200");
    this.mockMvc.perform(MockMvcRequestBuilders.get(HOST_URL)
            .header(TOKEN_KEY, token))
            .andExpect(status().isOk());

    logger.debug("用戶授權(quán)Setting菜單");
    user.getRoleList().clear();
    user.getRoleList().add(roleSetting);
    userRepository.save(user);

    logger.debug("重新登錄, 重新授權(quán)");
    token = this.loginWithUsernameAndPassword(username, password);

    logger.debug("授權(quán)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");
}
總結(jié)
感謝開源社區(qū),感謝Spring Security。

五行代碼(不算注釋),一個注解。就解決了一直以來困擾我們的權(quán)限問題。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/11476.html

相關(guān)文章

  • Spring Security

    摘要:框架具有輕便,開源的優(yōu)點,所以本譯見構(gòu)建用戶管理微服務五使用令牌和來實現(xiàn)身份驗證往期譯見系列文章在賬號分享中持續(xù)連載,敬請查看在往期譯見系列的文章中,我們已經(jīng)建立了業(yè)務邏輯數(shù)據(jù)訪問層和前端控制器但是忽略了對身份進行驗證。 重拾后端之Spring Boot(四):使用JWT和Spring Security保護REST API 重拾后端之Spring Boot(一):REST API的搭建...

    keelii 評論0 收藏0
  • 源碼-Spring Security Oauth2

    摘要:給定一個作為方法參數(shù)傳遞的域?qū)ο髮嵗_保類要綁定合適的權(quán)限。對或或檢查與驗證。檢查是否在客戶端的權(quán)限范圍內(nèi)。匹配默認的前綴字符串是的,如果匹配到則授權(quán),如授權(quán)范圍。實現(xiàn)類輪詢所有配置的每個配置,并且如果全部是肯定才能授予訪問權(quán)限。 03.01-源碼-Spring Security Oauth2 @(技術(shù)-架構(gòu))[源碼, 權(quán)限, Security, Oauth2] Oauth2 是一個...

    AWang 評論0 收藏0

發(fā)表評論

0條評論

xfee

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<