1. 程式人生 > >許可權管理之Spring Security(一)

許可權管理之Spring Security(一)

本文只是我對security的一些簡單看法,如有錯誤,敬請指正。

從最簡單的demo說起

一個最簡單的springboot+security的demo到底可以簡單到什麼程度?請看以下程式碼:

null

 

如果說maven引入jar包不算程式碼的話,那麼最簡單的啟用security沒有任何程式碼!首先建立一個springboot專案,引入security以及web即可

當我們引入spring-boot-starter-security 之後就已經預設啟用了security,在這之後,只有通過驗證的使用者才可以訪問,在web環境下,security會有一個預設的登入介面和預設的使用者(使用者名稱為user,密碼列印在控制檯)

系統的使用者肯定是由我們自己管理的而不是由security預設的,所以我們需要給security提供使用者支援,這裡就是security的一個核心所在,認證!

security支援多種使用者認證方式,常用的有ldap,jdbc等等。你只需要根據相應的認證方式進行配置就可以使用該認證方式進行工作。以jdbc為例,我們只需要配置查詢使用者的sql,DataSource就可以使security從我們的資料來查詢使用者進行認證了。認證的核心java配置如下

1,新建security包

2,新建SecurityConfig類,繼承WebSecurityConfigurerAdapter類(security的預設配置類),重寫configureGlobal(AuthenticationManagerBuilder auth)方法

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        
    }
}

當我們需要快速使用security進行使用者認證時,只需要在這個方法裡進行配置即可,以記憶體認證為例:

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("user").password("123456").roles("ADMIN");
    }
}

以上程式碼我們在記憶體中定義了一個使用者名稱為user,密碼為123456,角色為ADMIN的使用者,我們現在啟動專案,然後進行登入認證,會發現報錯

java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"

這是因為在新版本中,security必須指定密碼編碼器,通常情況下我們使用的是BCryptPasswordEncoder,

在這裡我們配置為明文密碼NoOpPasswordEncoder,詳見  官方文件

    @Bean
    public static NoOpPasswordEncoder passwordEncoder() {
        return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
    }

再次啟動專案,進行認證會認證成功!

auth中包含許多的認證方式,我們需要用到那種就用那種即可。用這些的好處是快,能夠快速的對應用進行安全保護。但通常情況下,我們不會選擇這些預設的方式,我們需要自己實現自己的認證方式,就需要用到兩個關鍵的物件。1,UserDetailsService,它是security提供的一個介面,唯一的方法是 loadUserByUsername,作用是根據使用者名稱獲取使用者物件;2,UserDetails,它是security的使用者物件介面,security已經預設實現了該介面。但不管怎麼樣,只要認證,都需要在configureGlobal(AuthenticationManagerBuilder auth) 這個方法裡進行配置

當我們需要自定義認證時,首先需要的是實現UserDetailsService介面,程式碼如下:

class MyUserDetailsService implements UserDetailsService {

        private Map<String, User> userRepository = new HashMap<String, User>();

        public MyUserDetailsService() {
            List<SimpleGrantedAuthority> authorities = new ArrayList<>();
            authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
            User user1 = new User("user1", "password1", authorities);
            userRepository.put("user1", user1);
            User user2 = new User("user2", "password2", authorities);
            userRepository.put("user2", user2);
        }

        @Override
        public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
            User user = userRepository.get(s);
            return user;
        }
    }

在上面的程式碼中,我們實現了UserDetailsService介面,重寫了loadUserByUsername方法。在這裡我們構造了兩個使用者以及他們的角色列表。在實際情況在,這裡遠遠不是這樣,我們可以根據自己的需求來獲取使用者資訊。只需要理解一點,這個方法是用來根據使用者名稱獲取使用者資訊的(包含角色列表,賬號是否啟用等等)。

接下來,我們需要在configureGlobal(AuthenticationManagerBuilder auth)這個方法裡設定我們自己的UserDetailsService

@Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(new MyUserDetailsService());
    }

到這裡,我們已經完成了基礎的認證,總之認證的核心方法就是configureGlobal,而自定義認證的核心是實現UserDetailsService介面。具體請看  官方文件​​​​​​

當我們完成認證之後,接下來就是授權,使用者到底能夠訪問那些連結。在上面的認證中,我們說到loadUserByUsername這個方法主要是根據使用者名稱查詢使用者資訊,包括使用者的角色資訊,在授權這裡,我們就需要用到使用者的角色資訊。

首先我們需要開啟security基於註解授權的功能,只需要在配置類上加 “@EnableGlobalMethodSecurity(prePostEnabled = true) ”註解即可

其次,我們來改造一下啟動類,增加幾個放法,程式碼如下:

@SpringBootApplication
@RestController
public class SimpleSecurityApplication {

    public static void main(String[] args) {
        SpringApplication.run(SimpleSecurityApplication.class, args);
    }

    @GetMapping("test")
    public String test(){
        return "test!";
    }

    @GetMapping("role")
    @PreAuthorize("hasRole('USER')")
    public String role(){
        return "role!";
    }

    @GetMapping("admin")
    @PreAuthorize("hasRole('ADMIN')")
    public String admin(){
        return "admin!";
    }
}

可以看到,test方法是都可以訪問的,role需要角色USER才可以訪問,admin需要角色ADMIN才可以訪問,接下來我們啟動專案會得到預期結果。至此,springboot整合security最基礎的demo完成,如果要進行實際使用,我們只需要完善MyUserDetailsService 類即可,同時為每個方法分配角色就可以完成對系統許可權的管理。

在我看來,security強大的地方是在於方法級別的許可權控制,在訪問方法的過程中進行許可權控制,訪問前滿足條件允許訪問,訪問時滿足條件允許訪問,訪問後滿足條件允許訪問之類的控制,就是aop(官方文件)。所以security貌似沒有所謂的許可權這一概念,而是通過role來限制使用者是否可以訪問介面。我們常用的許可權管理模型為RBAC模型,我認為,如果用security來實現的話,我們應該將security中ROLE概念認為是許可權,每個方法一個ROLE。通過在資料庫裡配置使用者擁有的ROLE(許可權,非角色)即可實現許可權控制。