本篇作為SpringBoot2.1版本的個人開發框架 子章節,請先閱讀SpringBoot2.1版本的個人開發框架再次閱讀本篇文章
參考:
- Spring Security 從入門到進階系列教程 作者: SpringForAll
- Spring Security的詳細配置
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也給我們預設了。
我們在控制檯中可以看到以下內容: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配置
參考:
-
spring security之httpSecurity使用例項,雖然這個是15年作者釋出的,但是還是有參考價值的,新版本跟舊版本不一樣時還可以加深我們對新版本的認識。
在上面其實什麼也沒有動,只是最簡單的引入了依賴而已,啟動專案以後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初步的有了一個認識,但是我們知道對於網站來說,使用者的資訊都是存在資料庫中的,不可能向我們這樣寫死在程式碼中的,實現從資料庫中查詢實現登陸我將在下一篇筆記中記錄,一篇文章太多了,看不進去。