SpringBoot2 集成SpringSecurity對資源訪問認證

在日常開發中,難免會遇到權限相關的需求,關於權限處理這一塊,用的比較多的安全框架分別是Shiro和Spring全家桶中的
Spring-Security,之前已經使用過Shiro,對它也有一定的瞭解,下面是對Spring-Security學習做的記錄。

概述

Spring Security 是一個能夠爲基於 Spring 的企業應用系統提供聲明式的安全訪問控制解決方案的安全框架。它提供了一組可以在 Spring 應用上下文中配置的 Bean,充分利用了Spring IoC,DI(控制反轉Inversion of Control ,DI:Dependency Injection 依賴注入)和 AOP(面向切面編程)功能,爲應用系統提供聲明式的安全訪問控制功能,減少了爲企業系統安全控制編寫大量重複代碼的工作。

路由資源訪問認證

先用一個比較簡單的基於角色訪問權限的小例子入門

導入相關jar包,SpringBoot-Utils模塊是我常用的一些公共類

    compile project(':SpringBoot-Utils')
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-security', version: '2.0.5.RELEASE'
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-thymeleaf', version: '2.0.5.RELEASE'
    compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity4:3.0.2.RELEASE'

配置Security

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired
    private AccessDeniedHandler accessDeniedHandler;

    @Bean
    @Override
    protected UserDetailsService userDetailsService() {
        //直接建兩個用戶存在內存中,生產環境可以從數據庫中讀取,對應管理器JdbcUserDetailsManager
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        // 創建兩個用戶
        //通過密碼的前綴區分編碼方式,推薦,這種加密方式很好的利用了委託者模式,使得程序可以使用多種加密方式,並且會自動
        //根據前綴找到對應的密碼編譯器處理。
        manager.createUser(User.withUsername("guest").password("{bcrypt}" +
                new BCryptPasswordEncoder().encode("123456")).roles("USER").build());
        manager.createUser(User.withUsername("root").password("{sha256}" +
                new StandardPasswordEncoder().encode("666666"))
                .roles("ADMIN", "USER").build());
        return manager;
    }


    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 支持多種編碼,通過密碼的前綴區分編碼方式,推薦
     *
     * @return the password encoder
     */
    @Bean
    PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // @formatter:on
        http.authorizeRequests()
                .antMatchers("/css/**", "/js/**", "/fonts/**").permitAll()  // 允許訪問資源
                .antMatchers("/", "/home", "/about", "/login").permitAll() //允許訪問這三個路由
                .antMatchers("/admin/**").hasAnyRole("ADMIN")   // 滿足該條件下的路由需要ROLE_ADMIN的角色
                .antMatchers("/user/**").hasAnyRole("USER")     // 滿足該條件下的路由需要ROLE_USER的角色
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
                .logout()
                .permitAll()
                .and()
                .exceptionHandling().accessDeniedHandler(accessDeniedHandler).and()
                .csrf().disable();
        // @formatter:off
    }
}

上面的代碼主要配置了兩個用戶,分別是root和guest,並分別設置了角色,接着配置靜態資源、登錄頁、首頁允許訪問,
部分路由指定特定角色可以訪問,並配置權限拒絕訪問異常處理器和跨越無效。

權限拒絕訪問處理器,配置跳轉到特定頁面

@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    private static Logger logger = LoggerFactory.getLogger(CustomAccessDeniedHandler.class);

    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        Authentication auth
                = SecurityContextHolder.getContext().getAuthentication();

        if (auth != null) {
            logger.info("User '" + auth.getName()
                    + "' attempted to access the protected URL: "
                    + httpServletRequest.getRequestURI());
        }

        httpServletResponse.sendRedirect(httpServletRequest.getContextPath() + "/403");
    }
}

下面訪問,/admin,會自動跳轉到/login登錄頁,使用root賬號登錄,在訪問/admin,會發現訪問到了。

接着在註銷,使用guest的賬號登錄,會發現自動跳轉到了403頁面,也就是訪問被拒絕了。

原因是guest只配置Role爲USER,而訪問/admin需要有ADMIN的角色。
到這裏,簡單的通過角色限制資源訪問以實現了,後面如果真是有需要要用到,可自行改造。
比如賬號的管理可以從內存移至數據庫中等等。

填坑記錄

最後,在記錄幾處採坑的地方:

  • Security的配置中,有個坑,需要自己配置passwordEncoder,否則,
    登錄會報java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id “null”。
    簡單的瞭解下PasswordEncoder:
    根據官方的定義,Spring Security的PasswordEncoder接口用於執行密碼的單向轉換,以便安全地存儲密碼。
    而DelegatingPasswordEncoder和NoOpPasswordEncoder都是PasswordEncoder接口的實現類,簡單來說就是現在數據庫存儲的密碼基本都是經過編碼的,而決定如何編碼以及判斷未編碼的字符序列和編碼後的字符串是否匹配就是PassswordEncoder的責任.
    隨着安全要求的提高,NoOpPasswordEncoder已經不被推薦了,如果你未自行加密,那麼他存儲的將是明文,並且當你因爲加密算法安全性不夠,需要更改時,將會變得非常棘手.
    而DelegatingPasswordEncoder,他是一個委派密碼編譯器,會根據不同的加密算法前綴標識委派給不同的密碼編譯器處理,
    使得程序可以同時存在好幾種加密算法,所以這裏推薦配置使用DelegatingPasswordEncoder,這裏僅僅是提及一下,若想深入瞭解PassEncoder可在自行Google。
    /**
     * 支持多種編碼,通過密碼的前綴區分編碼方式,推薦
     * 對應的密碼需加上加密算法標識,如:{bcrypt}、{MD5}..........
     * @return the password encoder
     */
    @Bean
    PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
  • 模板頁面標籤不生效,sec:authentication,原因可能如下:
    thymeleaf-extras-springsecurity4 Jar包未導入,或者版本與Security版本不匹配
    官方描述如下:
This is a thymeleaf extras module, not a part of the Thymeleaf core (and as such following its own versioning schema), but fully supported by the Thymeleaf team.
This repository contains 3 projects:
thymeleaf-extras-springsecurity3 for integration with Spring Security 3.x
thymeleaf-extras-springsecurity4 for integration with Spring Security 4.x
thymeleaf-extras-springsecurity5 for integration with Spring Security 5.x
Current versions:
Version 3.0.4.RELEASE - for Thymeleaf 3.0 (requires Thymeleaf 3.0.10+)
Version 2.1.3.RELEASE - for Thymeleaf 2.1 (requires Thymeleaf 2.1.2+)

按官方描述,我用的是Spring Security5.X的包,應該使用thymeleaf-extras-springsecurity5的包,但它確不生效。
無奈之下嘗試使用thymeleaf-extras-springsecurity4的包,便可以了,這裏建議5.0.X的還是用
thymeleaf-extras-springsecurity4的包,畢竟親測生效了。

項目源碼地址:
https://github.com/liaozihong/SpringBoot-Learning/tree/master/SpringBoot-SpringSecurity

參考鏈接:
https://docs.spring.io/spring-security/site/docs/5.2.0.BUILD-SNAPSHOT/reference/htmlsingle/
https://blog.csdn.net/alinyua/article/details/80219500
https://juejin.im/entry/5a45fe8e6fb9a045132b0658
https://segmentfault.com/a/1190000015191298

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章