SpringSecurity授權詳解


  項目開發離不開認證授權,簡單來說,認證解決你是誰的問題,授權解決你能干什么的問題。下面講講SpringSecurity的授權。

  一、授權基本知識

  1、授權因項目而異

  一些業務系統,如電商網站,只需區分是否登錄,或者是普通用戶還是VIP用戶等基本角色,它們的權限基本不會改變,這種情況可以直接在代碼中寫死。還有一些內管系統,如運營人員管理系統 ,角色眾多,權限復雜,權限規則隨着公司和業務的發展不斷變化,這種情況必須配置權限。

  2、什么是授權

  授權不是說在頁面上隱藏某個連接或者按鈕就完事兒,而是要判斷當前用戶有沒有訪問該連接的權限。授權模型中要明確兩個要素:系統配置信息和用戶權限信息。系統配置信息中記錄了url連接和每一個連接需要的權限,比如www.a.com/user需要A權限;用戶權限信息則記錄了某用戶具有的權限,比如用戶張三具有A、B、C權限。當一個用戶發了一個連接請求,系統會拿這兩份信息比對,如果這個請求需要A權限,發請求的這個用戶也有A權限,那么就可以訪問。

  二、SpringSecurity中的授權

  1、授權之是否需要登錄

  有一些鏈接需要登錄才能訪問,也有一些鏈接無需登錄就能訪問,怎么允許鏈接不登錄就能訪問呢?SpringSecurity配置如下:

@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
            .loginPage("/authentication/require")
            .loginProcessingUrl("/authentication/form")
        http.authorizeRequests()
            .antMatchers( "/login.html",
                    "/authentication/require",
                    "/code/image",
                    "/session/invalid",
                    "/logout.html"
                    ).permitAll()//不需要身份認證
            .anyRequest()
            .authenticated();
        http.csrf().disable();
    }
}

  2、授權之區分簡單角色

  鏈接"/user"需要"ADMIN"權限才能訪問,配置如下:

@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
            .loginPage("/authentication/require")
            .loginProcessingUrl("/authentication/form")
        http.authorizeRequests()
            .antMatchers(
                    "/login.html",
                    "/authentication/require",
                    "/code/image",
                    "/session/invalid",
                    "/logout.html"
                    ).permitAll()
            .antMatchers("/user").hasRole("ADMIN")
            .anyRequest()
            .authenticated();
        http.csrf().disable();
    }
}
@Component
public class MyUserDetailsService implements UserDetailsService{

    @Autowired
    private PasswordEncoder passwordEncoder;
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return new User(username, passwordEncoder.encode("123456"), 
            AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
}

  說明:

  a)如上配置,瀏覽器訪問http://localhost/user,報403無權訪問;訪問http://localhost/user/1,可以獲取到數據。

  b)將MyUserDetailsService中“admin”改為“ROLE_ADMIN”,http://localhost/user或者http://localhost/user/1都可以獲取到數據。

  c)注意,hasRole("ADMIN")對應的是“ROLE_ADMIN”,大小寫敏感。必須有“ROLE_”。

  d)antMatchers中支持通配符,如下配置,則http://localhost/user/或者http://localhost/user/1都無權訪問。

  

  

  3、授權之權限表達式

  上面講了permitAll和hasRole,SpringSecurity還有一些其他的表達式如下:

  

  說明:

  a)anonymous是匿名,即不登錄時可以訪問。

  b).antMatchers("/user").access("hasRole('ADMIN') and hasIpAddress('192.168.1.0/24')") ;ADMIN權限和IP地址同時滿足。

  c)判斷hasRole中的權限時,在設置權限時添加前綴ROLE_,其它表達式時不需要添加。

  4、將授權提取到AuthorizeConfigProvider中統一管理

  從上面的示例中看到授權代碼寫在WebSecurityConfigurerAdapter中,耦合度高,下面解決這個問題

  1)AuthorizeConfigProvider.java

public interface AuthorizeConfigProvider {
    void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config);
}

  2)MyAuthorizeConfigProvider.java,配置通用url

@Component
public class MyAuthorizeConfigProvider implements AuthorizeConfigProvider {

    @Override
    public void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) {
        config.antMatchers(
                "/login.html",
                "/authentication/require",
                "/code/image",
                "/session/invalid",
                "/logout.html"
        ).permitAll();
    }
}

  3)DempAuthorizeConfigProvider.java,配置自定義url

@Component
public class DempAuthorizeConfigProvider implements AuthorizeConfigProvider {

    @Override
    public void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) {
        config.antMatchers("/user",
                           "/demo.html"
                ).hasRole("ADMIN");
    }
}

  4)AuthorizeConfigManager.java

public interface AuthorizeConfigManager {
    void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config);
}

  5)MyAuthorizeConfigManager.java

@Component
public class MyAuthorizeConfigManager implements AuthorizeConfigManager {

    //Spring啟動的時候,所有AuthorizeConfigProvider的接口的實現都(自動)放在AuthorizeConfigProviders中
    @Autowired
    private Set<AuthorizeConfigProvider> AuthorizeConfigProviders;
    
    @Override
    public void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) {
        for(AuthorizeConfigProvider authorizeConfigProvider:AuthorizeConfigProviders) {
            authorizeConfigProvider.config(config);
        }
        config.anyRequest().authenticated();//所有請求需要身份認證
    }
}

  上面的代碼實現了權限代碼的解耦,現在看一下WebSecurityConfigurerAdapter中的配置:

@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
    ... ...
    @Autowired private AuthorizeConfigManager authorizeConfigManager;
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ... ...
        /**http.authorizeRequests()
            .antMatchers(
                    "/login.html",
                    "/authentication/require",
                    "/code/image",
                    "/session/invalid",
                    "/logout.html"
                    ).permitAll()//該路徑不需要身份認證
            .antMatchers("/user").hasRole("ADMIN")
            //.antMatchers(HttpMethod.GET,"/user/*").hasRole("ADMIN")//GET請求需要這個權限
            //.antMatchers("/user").access("hasRole('ADMIN') and hasIpAddress('192.168.1.0/24')")  
            .anyRequest()
            .authenticated();*/
        http.csrf().disable();//先禁止掉跨站請求偽造防護功能
        authorizeConfigManager.config(http.authorizeRequests());//將授權代碼提取到AuthorizeConfigManager中
    }
}

  測試,訪問http://localhost/userhttp://localhost/demo.html報403,上述配置生效。

  5、通用RBAC(Role-Based Access Control)數據模型

  上面的授權都是靜態的,即都是寫死在代碼中的,遇到復雜的權限管理,都是配置在數據庫中的,一般由五張表組成,即RBAC。

  1)RBAC數據模型的五張表

  用戶表,存儲用戶信息,由業務人員維護;

  角色表,存儲角色信息,由業務人員維護 ;

  資源表,存儲資源信息(菜單、按鈕及其URL),由開發人員維護;

  用戶-角色關系表,存儲用戶和角色的對應關系,多對多,由業務人員維護;

  角色-資源關系表,存儲角色和資源的對應關系 ,多對多,由業務人員維護;

  2)代碼實現

  RbacService.java

public interface RbacService {
    boolean hasPermission(HttpServletRequest request,Authentication authentication);
}

  RbacServiceImpl.java

@Component("rbacService")
public class RbacServiceImpl implements RbacService{
private AntPathMatcher antPathMatcher = new AntPathMatcher(); @Override public boolean hasPermission(HttpServletRequest request, Authentication authentication) { System.out.println("---------進入hasPermission方法:url"+request.getRequestURI()); Object principal = authentication.getPrincipal(); boolean hasPermission = false; if(principal instanceof UserDetails) { String username = ((UserDetails)principal).getUsername(); //根據username,讀取用戶所擁有權限的所有url Set<String> urls = new HashSet<>(); urls.add("/user"); urls.add("/index.html"); for(String url:urls) { if(antPathMatcher.match(url, request.getRequestURI())) { hasPermission = true; break; } } } return hasPermission; } }

  修改DempAuthorizeConfigProvider.java

@Component
@Order(Integer.MAX_VALUE)//指定順序,最后讀取
public class DempAuthorizeConfigProvider implements AuthorizeConfigProvider {
@Override
public void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) { /**config.antMatchers("/user", "/demo.html" ).hasRole("ADMIN"); */ config.anyRequest().access("@rbacService.hasPermission(request,authentication)"); //anyRequest放到最后讀,注釋掉MyAuthorizeConfigManager中的anyRequest,否則會覆蓋此處的anyRequest } }

  修改MyAuthorizeConfigProvider.java

@Component
@Order(Integer.MIN_VALUE)//指定順序,先讀取
public class MyAuthorizeConfigProvider implements AuthorizeConfigProvider {

    @Override
    public void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) {
        config.antMatchers(
                "/login.html",
                "/authentication/require",
                "/code/image",
                "/session/invalid",
                "/logout.html"
        ).permitAll();
    }
}

  修改MyAuthorizeConfigManager.java

@Component
public class MyAuthorizeConfigManager implements AuthorizeConfigManager {

    //Spring啟動的時候,所有AuthorizeConfigProvider的接口的實現都(自動)放在AuthorizeConfigProviders中
    @Autowired
    //private Set<AuthorizeConfigProvider> AuthorizeConfigProviders;
    private List<AuthorizeConfigProvider> AuthorizeConfigProviders;//改為有序集合
    
    @Override
    public void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) {
        for(AuthorizeConfigProvider authorizeConfigProvider:AuthorizeConfigProviders) {
            authorizeConfigProvider.config(config);
        }
        //config.anyRequest().authenticated();//所有請求需要身份認證,注銷掉,因為DempAuthorizeConfigProvider中有anyRequest了
    }
}

  說明:

  a)通過設置@Order注解,使通用url先讀取,首選執行permitAll;自定義url后讀取,執行rbacService.hasPermission方法。

  b)DempAuthorizeConfigProvider中指定了anyRequest訪問hasPermission方法,MyAuthorizeConfigManager中去掉最后一句。

  c)MyAuthorizeConfigManager中改為有序集合List,先實現AuthorizeConfigProvider的bean先被讀取,與@Order對應。

  測試:

  http://localhost/login.html可以訪問,輸入用戶名、密碼登錄。

  登錄后訪問http://localhost/user或者http://localhost/index.html,有權限,因為hasPermission方法中設置了。

  登錄后訪問http://localhost/user/1或者http://localhost/demo.html,報403無權限。

 

 

  

  

 

 

 

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM