Spring Security是Spring為解決應用安全所提供的一個全面的安全性解決方案。基於Spring AOP和Servlet過濾器,啟動時在Spring上下文中注入了一組安全應用的Bean,並在應用開發中提供了宣告式的安全訪問控制功能,使開發者可以在請求級和方法級上處理使用者身份認證與鑑權,大大減少了應用開發安全處理時編寫程式碼的工作量。

接下來通過程式碼來實踐一番,首先在pom中新增security的依賴:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

新增依賴後啟動服務,可以看到控制檯列印如下資訊:

當Spring Security啟動時,如果沒有指定使用者服務則會建立一個預設的使用者,登入名稱為user,5392bb9d-0673-4a9f-ab1a-c07522c84c5f是登入口令。

這時如果我們通過postman去請求,會返回如下結果:

按照預設的認證方式傳遞使用者名稱和密碼即可成功請求獲取返回值,如下圖所示:

如果通過瀏覽器訪問,則會自動跳轉到預設的登入頁面:

通過上述測試,我們對Spring Security能實現的效果有了一個直觀的感受。但是在實際應用場景中,認證的方式是需要開發人員自己來實現的,那麼如何讓Spring Security使用我們所定義的使用者名稱和登入口令呢?

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean
@Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
} @Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
} @Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
authManagerBuilder.inMemoryAuthentication().withUser("eddy").password("123456").roles("user");
}
}

在上面的程式碼實現中繼承了WebSecurityConfigurerAdapter並使用其所提供的預設配置,通過覆寫configure()方法,以記憶體的方式增加應用使用者資訊的定義。在增加的使用者中我們設定了使用者的登入名、登入口令及相應的使用者許可權(角色)。

這時我們重新啟動服務,由於已經指定了認證規則,在控制檯也不會列印隨機生成的口令了。這時我們通過postman再去請求,發現服務端丟擲瞭如下異常:

java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
at org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:244) ~[spring-security-core-5.1.13.RELEASE.jar:5.1.13.RELEASE]
at org.springframework.security.crypto.password.DelegatingPasswordEncoder.matches(DelegatingPasswordEncoder.java:198) ~[spring-security-core-5.1.13.RELEASE.jar:5.1.13.RELEASE]
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$LazyPasswordEncoder.matches(WebSecurityConfigurerAdapter.java:605) ~[spring-security-config-5.1.13.RELEASE.jar:5.1.13.RELEASE]
at org.springframework.security.authentication.dao.DaoAuthenticationProvider.additionalAuthenticationChecks(DaoAuthenticationProvider.java:90) ~[spring-security-core-5.1.13.RELEASE.jar:5.1.13.RELEASE]
at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:166) ~[spring-security-core-5.1.13.RELEASE.jar:5.1.13.RELEASE]
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:175) ~[spring-security-core-5.1.13.RELEASE.jar:5.1.13.RELEASE]
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:200) ~[spring-security-core-5.1.13.RELEASE.jar:5.1.13.RELEASE]
at org.springframework.security.web.authentication.www.BasicAuthenticationFilter.doFilterInternal(BasicAuthenticationFilter.java:180) ~[spring-security-web-5.1.13.RELEASE.jar:5.1.13.RELEASE]

經過查閱資料,瞭解到Spring security 5.0中新增了多種加密方式,使得當進行驗證時Spring Security將傳輸的資料看作是進行了加密後的資料,在匹配之後發現找不到正確識別序列,就認為id是null,因此要將前端傳過來的密碼進行某種方式加密。

修改後的程式碼如下:
@Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
authManagerBuilder.inMemoryAuthentication()
.passwordEncoder(new BCryptPasswordEncoder())
.withUser("eddy")
.password(new BCryptPasswordEncoder().encode("123456"))
.roles("user");
}

再次測試,成功返回!

 
參考資料:
《Spring Cloud微服務架構開發實戰》
https://www.jianshu.com/p/796c10c3381a