javascript
Spring Security 玩出花!两种方式 DIY 登录
Spring Security 玩出花!兩種方式 DIY 登錄
一般情況下,我們在使用 Spring Security 的時候,用的是 Spring Security 自帶的登錄方案,配置一下登錄接口,配置一下登錄參數,再配置一下登錄回調就能用了,這種用法可以算是最佳實踐了!
但是!
總會有一些奇奇怪怪得需求,例如想自定義登錄,像 Shiro 那樣自己寫登錄邏輯,如果要實現這一點,該怎么做?今天松哥就來和大家分享一下。
Remi琢磨了一下,想在 Spring Security 中自定義登錄邏輯,我們有兩種思路,不過這兩種思路底層實現其實異曲同工,我們一起來看下。
1. 化腐朽為神奇
前面Remi和大家分享了一個 Spring Security 視頻:
沒見過的奇葩登錄
這個視頻里主要是和大家分享了我們其實可以使用 HttpServletRequest 來完成系統的登錄,這其實是 JavaEE 的規范,這種登錄方式雖然冷門,但是卻很好玩!
然后Remi還和大家分享了一個視頻:
SpringSecurity登錄數據獲取最后一講
這個視頻其實是在講 Spring Security 對 HttpServletRequest 登錄邏輯的實現,或句話說,HttpServletRequest 中提供的那幾個和登錄相關的 API,Spring Security 都按照自己的實現方式對其進行了重寫。
有了這兩個儲備知識后,第一個 DIY Spring Security 登錄的方案呼之欲出。
1.1 實踐
我們來看看具體操作。
首先我們來創建一個 Spring Boot 工程,引入 Web 和 Security 兩個依賴,如下:
方便起見,我們在 application.properties 中配置一下默認的用戶名密碼:
spring.security.user.name=javaboy
spring.security.user.password=123
接下來我們提供一個 SecurityConfig,為登錄接口放行:
登錄接口就是 /login ,一會我們自定義的登錄邏輯就寫在這個里邊,我們來看下:
@RestController public class LoginController {@PostMapping("/login")public String login(String username, String password, HttpServletRequest req) {try {req.login(username, password);return "success";} catch (ServletException e) {e.printStackTrace();}return "failed";} }直接調用 HttpServletRequest#login 方法,傳入用戶名和密碼完成登錄操作。
最后我們再提供一個測試接口,如下:
@RestController public class HelloController {@GetMapping("/hello")public String hello() {return "hello security!";} }just this!
啟動項目,我們首先訪問 /hello 接口,會訪問失敗,接下來我們訪問 /login 接口執行登錄操作,如下:
登錄成功之后,再去訪問 /hello 接口,此時就可以訪問成功了。
是不是很 Easy?登錄成功后,以后的授權等操作都還是原來的寫法不變。
1.2 原理分析
這里我也是稍微說兩句。
我們在 LoginController#login 方法中所獲取到的 HttpServletRequest 實例其實是 HttpServlet3RequestFactory 中的一個內部類 Servlet3SecurityContextHolderAwareRequestWrapper 的對象,在這個類中,重寫了 HttpServletRequest 的 login 以及 authenticate 等方法,我們先來看看 login 方法,如下:
@Override public void login(String username, String password) throws ServletException {if (isAuthenticated()) {throw new ServletException("Cannot perform login for '" + username + "' already authenticated as '"+ getRemoteUser() + "'");}AuthenticationManager authManager = HttpServlet3RequestFactory.this.authenticationManager;if (authManager == null) {HttpServlet3RequestFactory.this.logger.debug("authenticationManager is null, so allowing original HttpServletRequest to handle login");super.login(username, password);return;}Authentication authentication = getAuthentication(authManager, username, password);SecurityContextHolder.getContext().setAuthentication(authentication); }可以看到:
如果用戶已經認證了,就拋出異常。
獲取到一個 AuthenticationManager 對象。
調用 getAuthentication 方法完成登錄,在該方法中,會根據用戶名密碼構建 UsernamePasswordAuthenticationToken 對象,然后調用 Authentication#authenticate 方法完成登錄,具體代碼如下:
該方法返回的是一個認證后的 Authentication 對象。
最后,將認證后的 Authentication 對象存入 SecurityContextHolder 中,這里的具體邏輯我就不啰嗦了,我在公眾號【江南一點雨】之前的視頻中已經講過多次了。
這就是 login 方法的執行邏輯。
Servlet3SecurityContextHolderAwareRequestWrapper 類也重寫了 HttpServletRequest#authenticate 方法,這個也是做認證的方法:
@Override public boolean authenticate(HttpServletResponse response) throws IOException, ServletException {AuthenticationEntryPoint entryPoint = HttpServlet3RequestFactory.this.authenticationEntryPoint;if (entryPoint == null) {HttpServlet3RequestFactory.this.logger.debug("authenticationEntryPoint is null, so allowing original HttpServletRequest to handle authenticate");return super.authenticate(response);}if (isAuthenticated()) {return true;}entryPoint.commence(this, response,new AuthenticationCredentialsNotFoundException("User is not Authenticated"));return false; }可以看到,這個方法用來判斷用戶是否已經完成認證操作,返回 true 表示用戶已經完成認證,返回 false 表示用戶尚未完成認證工作。
2. 源碼的力量
看了上面的原理分析,大家應該也明白了第二種方案了,就是不使用 HttpServletRequest#login 方法,我們直接調用 AuthenticationManager 進行登錄驗證。
一起來看下。
首先我們修改配置類如下:
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/login","/login2").permitAll().anyRequest().authenticated().and().csrf().disable();}@Override@Beanpublic AuthenticationManager authenticationManagerBean() throws Exception {DaoAuthenticationProvider provider = new DaoAuthenticationProvider();InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();manager.createUser(User.withUsername("javaboy").password("{noop}123").roles("admin").build());provider.setUserDetailsService(manager);return new ProviderManager(provider);} }/login2
ss
接下來提供一個登錄接口:
在登錄接口中,傳入用戶名密碼等參數,然后將用戶名密碼等參數封裝成一個 UsernamePasswordAuthenticationToken 對象,最后調用 AuthenticationManager#authenticate 方法進行驗證,驗證成功后會返回一個認證后的 Authentication 對象,再手動把該 Authentication 對象存入 SecurityContextHolder 中。
配置完成后,重啟項目,進行登錄測試即可。
第二種方案和第一種方案異曲同工,第二種實際上就是把第一種的底層拉出來自己重新實現, 僅此而已 。
3. 小結
好啦,今天就和大家介紹了兩種 Spring Security DIY 登錄的方案,這些方案可能工作中并不常用,但是對于大家理解 Spring Security 原理還是大有裨益的,感興趣的小伙伴可以敲一下試試哦~
文章來源:https://www.tuicool.com/articles/2ErUBvv
總結
以上是生活随笔為你收集整理的Spring Security 玩出花!两种方式 DIY 登录的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怎么通俗的理解Netty呢?
- 下一篇: 这几种Java异常处理方法,你会吗?