SpringBoot2.1版本的個人應用開發框架 - 使用SpringSecurity管理我們的訪問許可權1

人生長恨水發表於2019-04-11

本篇作為SpringBoot2.1版本的個人開發框架 子章節,請先閱讀SpringBoot2.1版本的個人開發框架再次閱讀本篇文章

專案地址:SpringBoot2.1版本的個人應用開發框架

參考:

SpringSecurity和jjwt簡介

SpringSecurity 是專門針對基於Spring專案的安全框架,充分利用了AOP和Filter來實現安全功能。它提供全面的安全性解決方案,同時在 Web 請求級和方法呼叫級處理身份確認和授權。他提供了強大的企業安全服務,如:認證授權機制、Web資源訪問控制、業務方法呼叫訪問控制、領域物件訪問控制Access Control List(ACL)、單點登入(SSO),Web 應用的安全性包括使用者認證(Authentication)和使用者授權(Authorization)等等。

核心功能:認證(你是誰)、授權(你能幹什麼)、攻擊防護(防止偽造身份)。

而jwt全稱叫做JSON WEB TOKEN,網上對於jwt的概念還是很多的,而我的理解就是把使用者的資訊封裝成一串加密的字串,而jwt的格式例如:A.B.C,有三個部分組成,中間有 . 相隔,每一節都是base 64編碼的。

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ.ipevRNuRP6HflG8cFKnmUPtypruRC4fb1DWtoLL62SY
複製程式碼

A的部分叫做Header,必須指定用於簽署JWT的演算法。 B的部分叫做body,本節包含了JWT編碼的所有宣告,例如可以把我們使用者的資訊編進去。 最後一部分是signature,它的組成是前兩部分,它通過在標頭檔案中指定的演算法通過header和body的組合來計算。 更詳細的介紹,可以參考:jjwt的github文件

簡單配置Spring Security

建立

配置Security之前我們先對ywh-starter-security模組進行劃分,以下包的命名和功能完全可以自己規定。

  • controller包:登陸介面以及其他系統介面
  • dao包:查詢系統使用者的介面
  • entity包:實體類
  • fiter包:攔截器
  • service和impl包:service層和實現類
  • utils包:工具包
  • config包:配置類
  • resources/security-mybatis-mappers:系統類的mapper.xml檔案

修改

建立完以後,我們需要對專案進行以下小的修改,讓專案識別我們security下的東西

  • 在core模組下啟動類上的@MapperScan註解修改如下
@MapperScan(basePackages = "com.ywh.**.dao")
複製程式碼
  • 在core模組中的application-dev.yml檔案中設定mapper-locations屬性,多路徑以逗號相隔
mapper-locations: classpath*:/mybatis-mappers/*,classpath*:/security-mybatis-mappers/*
複製程式碼
  • 在Security模組的pom.xml中引入springsecurity的依賴如下:
<!--security的依賴-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
	<version>2.1.2.RELEASE</version>
</dependency>
複製程式碼

開始

當我們引入依賴後,什麼也不做我們就可以使用到Security給我們帶來的登陸功能,啟動專案後訪問我們的任意介面會被跳轉到login頁面,賬戶和密碼Security也給我們預設了。

springSecurity登入介面

我們在控制檯中可以看到以下內容:Using generated security password: 5dd9438d-1c10-44c2-bc45-6864ba4308ae

控制檯資訊

這個是Security給我們自動生成的登陸密碼,賬戶是什麼呢?我們進入到圖片中標紅的類UserDetailsServiceAutoConfiguration可以看到以下內容:

@Lazy
public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties, ObjectProvider<PasswordEncoder> passwordEncoder) {
    User user = properties.getUser();
    List<String> roles = user.getRoles();
    return new InMemoryUserDetailsManager(new UserDetails[]{org.springframework.security.core.userdetails.User.withUsername(user.getName()).password(this.getOrDeducePassword(user, (PasswordEncoder)passwordEncoder.getIfAvailable())).roles(StringUtils.toStringArray(roles)).build()});
}

private String getOrDeducePassword(User user, PasswordEncoder encoder) {
    String password = user.getPassword();
    if (user.isPasswordGenerated()) {
    	// 這個就是我們在控制檯中看到的那句話的程式碼,是通過user.getPassword()來獲取密碼的
        logger.info(String.format("%n%nUsing generated security password: %s%n", user.getPassword()));
    }

    return encoder == null && !PASSWORD_ALGORITHM_PATTERN.matcher(password).matches() ? "{noop}" + password : password;
}
複製程式碼

很明顯Security是通過User這個類來進行管理預設的使用者資訊的,在內部類User中替我們設定了預設的賬戶“user”,密碼是一個隨機的UUID,我們通過賬戶和密碼登陸以後就可以訪問我的介面了。

	public static class User {
        private String name = "user";
        private String password = UUID.randomUUID().toString();
        private List<String> roles = new ArrayList();
        private boolean passwordGenerated = true;

        public User() {
        }

        。。。此處省略了以下程式碼
    }
複製程式碼

如果我們想要自己設定登陸的賬號和密碼,可以在application.yml檔案中新增以下內容就可以了。

spring:
  security:
    user:
      name: admin
      password: admin
複製程式碼

定製Spring Security配置

參考:

在上面其實什麼也沒有動,只是最簡單的引入了依賴而已,啟動專案以後Security自動為我們配置了認證安全機制,很顯然這對於我們來說是不夠的,所以我們需要對security進行自己定製化配置。

除了在我們的yml檔案中配置預設的登陸賬戶和密碼以外,我們還可以通過繼承WebSecurityConfigurerAdapter 類來實現,這是核心類之一。

在這個類中有三個configure方法

方法 描述
configure(AuthenticationManagerBuilder auth) 使用者資訊的配置
configure(WebSecurity web) 配置Spring Security的Filter鏈
configure(HttpSecurity http) 配置如何通過攔截器保護我們的請求,哪些能通過哪些不能通過

我們需要通過重寫configure(AuthenticationManagerBuilder auth)來配置我們的使用者,以下方式在security5.0以前這麼寫是沒有問題的,security預設使用的是NoOpPasswordEncoder編碼,就是沒有加密的方式,純文字編碼,你輸入root那麼密碼就是明文顯示的root了,現在NoOpPasswordEncoder已經被廢棄了,因為不安全。

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
	@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication()
                .withUser("root")
                .password("root")
                .roles("user");
    }
}
複製程式碼

按照上面方式配置使用者的話,控制檯會報錯**java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"**大概意思就是說我們沒有設定加密的方式。

而NoOpPasswordEncoder已經廢棄了,所以我們來實現一個類似的內部類做測試使用即可,僅僅做測試時使用,重新啟動專案時我們會在控制檯看見依舊輸出password,但是已經不生效了,使用自己設定的root登陸即可,如果想設定多個使用者的話,通過.and()連線後再次設定就好了。

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
	@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication()
                .withUser("root")
                .password("root")
                .roles("user")
                .and()
                .passwordEncoder(CharEncoder.getINSTANCE());
    }

	//以下方式就是設計模式中的單例模式的餓漢式,正好練習一下。
	public static class CharEncoder implements PasswordEncoder {

        public  static CharEncoder INSTANCE = new CharEncoder();

        public static CharEncoder getINSTANCE(){
            return INSTANCE;
        }

        @Override
        public String encode(CharSequence rawPassword) {
            return rawPassword.toString();
        }

        @Override
        public boolean matches(CharSequence rawPassword, String encodedPassword) {
            return rawPassword.toString().equals(encodedPassword);
        }
    }
}
複製程式碼

以上配置好以後security對我們所有的請求都進行了攔截,我們靜態檔案和其他不需要認證的都被攔截顯然不是我們所希望的,所以我們要對某些請求放開,這個就需要我們重寫configure(HttpSecurity http)這個方法了,首先我們看一下預設的方法。

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
	protected void configure(HttpSecurity http) throws Exception {
		((HttpSecurity)((HttpSecurity)((AuthorizedUrl)http.authorizeRequests()
		.anyRequest()).authenticated()
		.and())
		.formLogin()
		.and())
		.httpBasic();
	}
}
複製程式碼
  • anyRequest:對於任何的請求都進行攔截認證
  • permitAll() 方法會對指定的路徑放行
  • authenticated() 方法會對指定的路徑進行攔截認證,如果未登陸則跳轉到登陸頁面

而我們想要對某些介面對外開放,比如ExampleController下的securityTest1介面放行和對是post型別的securityTest2放行,其他介面進行攔截。

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
/**
     * 配置如何通過攔截器保護我們的請求,哪些能通過哪些不能通過,允許對特定的http請求基於安全考慮進行配置
     * @param httpSecurity http
     * @throws Exception 異常
     */
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .authorizeRequests()
                .antMatchers("/example/securityTest1").permitAll()
                .antMatchers(HttpMethod.POST,"/example/securityTest2").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin();
    }
}
複製程式碼

當我們定義了一個securityTest2介面是GET請求的話,就不會放行,只有是POST型別的securityTest2才會被放行通過,這就好像是你拿著別人的身份證去辦銀行卡,銀行當然不可能給你放行。

獲取登陸的使用者

當我們登陸以後,我們想要獲取一下登陸使用者的資訊,在Spring Security中,使用者資訊儲存在SecurityContextHolder中,我們可以通過以下程式碼獲取使用者的資訊。

@GetMapping("securityTest2")
public Result securityTest2(){
    Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    if(principal instanceof UserDetails){
        String name =  ((UserDetails) principal).getUsername();
        return Result.successJson("登陸的使用者是:" + name);
    }else {
        String name = principal.toString();
        return Result.successJson("登陸的使用者是:" + name);
    }
}
複製程式碼

在這裡插入圖片描述

在我們上面的配置過後我們對security初步的有了一個認識,但是我們知道對於網站來說,使用者的資訊都是存在資料庫中的,不可能向我們這樣寫死在程式碼中的,實現從資料庫中查詢實現登陸我將在下一篇筆記中記錄,一篇文章太多了,看不進去。

相關文章