天天看點

權限管理之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(權限,非角色)即可實作權限控制。

繼續閱讀