SpringSecurity5 (1) ——初步認識


一、Spring Security 簡介

所有的業務系統都需要鑒權、授權的步驟,通過鑒權,授權提高系統的安全性,只有合法的用戶才能對系統進行操作,外部系統通過鑒權后才能調用本系統的接口等。鑒權、授權的實現有很多種,常見的有apache shiro 以及今天我們介紹的Spring Security,它們都屬於安全框架,幫助業務系統實現鑒權、授權的功能,讓我們有更多的經歷實現業務功能。

Spring Security 核心是一組過濾器鏈,通過過濾器來驗證用戶是否登錄、是否有權限訪問后台接口,我們也可以通過自定義過濾器實現不同方式的登錄,比如通過手機號+驗證碼的方式。

二、簡單使用Spring Security

本篇文章的demo是基於SpringBoot 2.2.5+tkmybatis+themlefy+mysql開發,security的版本是5.2.2.RELEASE;

(一)搭建項目

項目是標准的maven項目結構,具體的目錄如下圖所示,新建啟動類SpringSecurityApplication及測試類HelloController,只有一個簡單的測試方法返回字符串"hello,world"

@SpringBootApplication
@EnableAsync
@ComponentScan(value = "com.tl.spring.security")
public class SpringSecurityApplication  extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(SpringSecurityApplication.class);
    }

    @Override
    public void onStartup(ServletContext servletContext)
            throws ServletException {
        servletContext.getSessionCookieConfig().setName("SESSIONID");
    }
    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(SpringSecurityApplication.class);
        springApplication.addListeners(new ApplicationPidFileWriter());
        springApplication.run(args);
    }
}

(二)引入依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.5.RELEASE</version>
    </parent>
    <groupId>com.tl</groupId>
    <artifactId>spring-security</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <url>http://www.xxx.com</url>
        <dependencies>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                </dependency>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-test</artifactId>
                </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
        </dependencies>
</project>

(三)使用默認的配置

使用spring默認配置,不新建配置文件application.yml

(四)運行程序

運行程序,在控制台上回打印出來,此次生成的密碼,如下圖所示:

通過瀏覽器訪問127.0.0.1:8080/hello跳轉到登錄頁面(security5.2版本默認好像不再使用http basic 認證),如下圖所示,輸入默認用戶名user及控制台打印的密碼,登錄成功后,可正常訪問后台接口,並返回字符串hello,world

(五)使用內存用戶登錄

新建配置類AuthConfiguration繼承WebSecurityConfigurerAdapter重寫configure方法

@Configuration
@EnableWebSecurity
public class AuthConfiguration   extends WebSecurityConfigurerAdapter {

	
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/hello/admin").hasAnyRole("ROOT").anyRequest().permitAll()
                .antMatchers("/hello").hasRole("USER").anyRequest().permitAll()
                .and()
                .csrf().disable().
                formLogin().and().httpBasic().disable()
                .sessionManagement().disable()
                .cors()
                .and()
                .logout();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        // 設置攔截忽略文件夾,可以對靜態資源放行包括css,js等
        web.ignoring().antMatchers("/static/**");
    }

	
   /**
     * 新建兩個用戶root 和user 分別擁有"ROLE_ROOT", "ROLE_USER" 和"ROLE_USER" 角色
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .passwordEncoder(getPasswordEncoder())
                .withUser("root")
                .password(getPasswordEncoder().encode("root@123456"))
                .roles("ROLE_ROOT", "ROLE_USER")
                .and()
                .withUser("user")
                .password(getPasswordEncoder().encode("user@123456"))
                .roles("ROLE_USER");
    }

    
    /**
     * 加密方式  security 5.0以后必須要求使用加密方式對明文密碼進行加密
     * @return
     */
    @Bean
    private BCryptPasswordEncoder  getPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

或者使用重寫userDetailsService()方法新建內存用戶

@Override
    @Bean
    protected UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
       manager.createUser(User.withUsername("root").password(getPasswordEncoder().encode("root@123456")).roles("ROOT").build());
        manager.createUser(User.withUsername("user").password(getPasswordEncoder().encode("user@123456")).roles("USER").build());
        return manager;
    }

使用user/user@123456登錄后,可正常訪問/hello接口,但是訪問/hello/admin接口時前台報403無權訪問錯誤,說明我們的配置已經生效。

(六)使用數據庫用戶登錄

在實際應用開發中,我們不會使用以上方式(把用戶信息放到內存中),用戶信息應該存儲在DB中,通過查詢DB獲取用戶信息,Security提供有相應的接口,因我們自己的業務系統一般使用自己設計的權限模型,所以經常使用的方案是實現UserDetailService接口中的loadUserByUsername方法,根據用戶名返回UserDetails對象。在使用自定義的登錄邏輯實現登錄之前,我們先看下security登錄驗證的整體流程。

1 .登錄驗證流程

(1) .用戶通過前台頁面發起登錄請求后,請求會被UsernamePasswordAuthenticationFilter攔截,執行attemptAuthentication方法,構建token對象,具體構建的token對象的過程比較簡單,可自行查看源碼。token生成后並交由AuthenticationManager的實現類ProviderManagerauthenticate方法來處理


(2) 用戶名密碼登錄流程所使用DaoAuthenticationProvider繼承自AbstractUserDetailsAuthenticationProvider,並實現了抽象方法retrieveUser,獲取userDetailService獲取用戶信息后,對用戶進行認證。

(3).認證成功后調用createSuccessAuthentication()方法,並返回認證信息,在此方法中 ,它重新 new 了一個 UsernamePasswordAuthenticationToken,因為到這里認證已經通過了,所以將 authorities 注入進去,並設置 authenticated 為 true,即已通過認證。


(4)到此為止認證信息會返回到UsernamePasswordAuthenticationFilter 中,在 UsernamePasswordAuthenticationFilter 的父類 AbstractAuthenticationProcessingFilterdoFilter() 中,會根據認證的成功或者失敗調用相應的 handler,此handler可通過

2. 自定義類

根據登錄流程,需要自定義類實現UserDetailsService接口,重寫loadUserByUsername實現從自己的數據庫查詢用戶信息邏輯

@Component
public class SecurityUserDetailsService implements UserDetailsService {

	private Logger log = LoggerFactory.getLogger(SecurityUserDetailsService.class);

	@Autowired
	private UserMapper userMapper ;

	/**
	 * 根據用戶名稱查詢用戶信息,並返回UserDetails對象
	 * @param username
	 * @return
	 * @throws UsernameNotFoundException
	 */
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //自定義實現查找用戶的方式,這里不是重點,不再貼具體實現代碼
		User user = userMapper.getUserByName(username);
		if (user == null) {
			log.error("Can not find user by name: {}", username);
			throw new UsernameNotFoundException("Can not find user by name:"+username);
		}
			//返回User 對象
		return org.springframework.security.core.userdetails.User
				.withUsername(username)
				.password(user.getPassword())
				.roles("USER","ROOT").accountExpired(false)
				.accountLocked(false)
				.credentialsExpired(false)
				.disabled(false)
				.build();
	}
}

3. 修改配置

修改上面AuthConfiguration 的配置類,配置自定義的UserDetailsService類及密碼加密方式

   @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(securityUserDetailsService).
                passwordEncoder(getPasswordEncoder());
    }

4. 自定義加密

通過實現PasswordEncoder接口中的兩個方法,實現自定義的加密方式

	//加密
	String encode(CharSequence rawPassword);
	
	//判斷密碼是否匹配
	boolean matches(CharSequence rawPassword, String encodedPassword);


免責聲明!

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



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