<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
多個資料來源是指在同一個系統中,使用者資料來自不同的表,在認證時,如果第一張表沒有查詢到使用者,那就去第二張表中査詢,依次類推。
看了前面的分析,要實現這個需求就很容易了,認證要經過AuthenticationProvider,每一 個 AuthenticationProvider 中都設定了一個 UserDetailsService,而不同的 UserDetailsService 則可以代表不同的資料來源,所以我們只需要手動設定多個AuthenticationProvider,併為不同的 AuthenticationProvider 提供不同的 UserDetailsService 即可。
為了方便起見,這裡通過 InMemoryUserDetailsManager 來提供 UserDetailsService 範例, 在實際開發中,只需要將UserDetailsService換成自定義的即可,具體設定如下:
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean @Primary UserDetailsService us1(){ return new InMemoryUserDetailsManager(User.builder().username("testuser1").password("{noop}123") .roles("admin").build()); } @Bean UserDetailsService us2(){ return new InMemoryUserDetailsManager(User.builder().username("testuser2").password("{noop}123") .roles("user").build()); } @Override public AuthenticationManager authenticationManagerBean() throws Exception { DaoAuthenticationProvider dao1 = new DaoAuthenticationProvider(); dao1.setUserDetailsService(us1()); DaoAuthenticationProvider dao2 = new DaoAuthenticationProvider(); dao2.setUserDetailsService(us2()); ProviderManager manager = new ProviderManager(dao1, dao2); return manager; } @Override protected void configure(HttpSecurity http) throws Exception { //省略 } }
首先定義了兩個UserDetailsService範例,不同範例中儲存了不同的使用者;然後重寫 authenticationManagerBean 方法,在該方法中,定義了兩個 DaoAuthenticationProvider 範例並分別設定了不同的UserDetailsService ;最後構建ProviderManager範例並傳入兩個 DaoAuthenticationProvider,當系統進行身份認證操作時,就會遍歷ProviderManager中不同的 DaoAuthenticationProvider,進而呼叫到不同的資料來源。
登入驗證碼也是專案中一個常見的需求,但是Spring Security對此並未提供自動化設定方案,需要開發者自行定義,一般來說,有兩種實現登入驗證碼的思路:
通過自定義過濾器來實現登入驗證碼,這種方案我們會在後面的過濾器中介紹, 這裡先來看如何通過自定義認證邏輯實現新增登入驗證碼功能。
生成驗證碼,可以自定義一個生成工具類,也可以使用一些現成的開源庫來實現,這裡 採用開源庫kaptcha,首先引入kaptcha依賴,程式碼如下:
<dependency> <groupId>com.github.penggle</groupId> <artifactId>kaptcha</artifactId> <version>2.3.2</version> </dependency>
然後對kaptcha進行設定:
package com.intehel.demo.config; import com.google.code.kaptcha.Producer; import com.google.code.kaptcha.impl.DefaultKaptcha; import com.google.code.kaptcha.util.Config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.Properties; @Configuration public class KaptchaConfig { @Bean Producer kaptcha(){ Properties properties = new Properties(); properties.setProperty("kaptcha.image.width", "150"); properties.setProperty("kaptcha.image.height", "50"); properties.setProperty("kaptcha.textproducer.char.string", "0123456789"); properties.setProperty("kaptcha.textproducer.char.length", "4"); Config config = new Config(properties); DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); defaultKaptcha.setConfig(config); return defaultKaptcha; } }
設定一個Producer範例,主要設定一下生成的圖片驗證碼的寬度、長度、生成字元、驗證碼的長度等資訊,設定完成後,我們就可以在Controller中定義一個驗證碼介面了。
package com.intehel.demo.controller; import com.google.code.kaptcha.Producer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.imageio.ImageIO; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.awt.image.BufferedImage; import java.io.IOException; @RestController public class LoginController { @Autowired Producer producer; @RequestMapping("/vc.jpg") public void getVerifyCode(HttpServletResponse resp, HttpSession session){ resp.setContentType("image/jpeg"); String text = producer.createText(); session.setAttribute("kaptcha",text); BufferedImage image = producer.createImage(text); try(ServletOutputStream out = resp.getOutputStream()) { ImageIO.write(image,"jpg",out); } catch (IOException e) { e.printStackTrace(); } } }
在這個驗證碼介面中,我們主要做了兩件事:
接下來修改登入表單,加入驗證碼,程式碼如下:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>登入</title> <link href="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css"> <script src="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> </head> <style> #login .container #login-row #login-column #login-box { border: 1px solid #9c9c9c; background-color: #EAEAEA; } </style> <body> <div id="login"> <div class="container"> <div id="login-row" class="row justify-content-center align-items-center"> <div id="login-column" class="col-md-6"> <div id="login-box" class="col-md-12"> <form id="login-form" class="form" action="/doLogin" method="post"> <h3 class="text-center text-info">登入</h3> <!--/*@thymesVar id="SPRING_SECURITY_LAST_EXCEPTION" type="com"*/--> <div th:text="${SPRING_SECURITY_LAST_EXCEPTION}"></div> <div class="form-group"> <label for="username" class="text-info">使用者名稱:</label><br> <input type="text" name="uname" id="username" class="form-control"> </div> <div class="form-group"> <label for="password" class="text-info">密碼:</label><br> <input type="text" name="passwd" id="password" class="form-control"> </div> <div class="form-group"> <label for="kaptcha" class="text-info">驗證碼:</label><br> <input type="text" name="kaptcha" id="kaptcha" class="form-control"> <img src="/vc.jpg" alt=""> </div> <div class="form-group"> <input type="submit" name="submit" class="btn btn-info btn-md" value="登入"> </div> </form> </div> </div> </div> </div> </div> </body> </html>
登入表單中增加一個驗證碼輸入框,驗證碼的圖片地址就是我們在Controller中定義的驗證碼介面地址。
接下來就是驗證碼的校驗了。經過前面的介紹,讀者已經瞭解到,身份認證實際上就是在AuthenticationProvider.authenticate方法中完成的。所以,驗證碼的校驗,我們可以在該方法執行之前進行,需要設定如下類:
package com.intehel.demo.provider; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; public class KaptchaAuthenticationProvider extends DaoAuthenticationProvider{ @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { HttpServletRequest req = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest(); String kaptcha = req.getParameter("kaptcha"); String serssionKaptcha = (String) req.getSession().getAttribute("kaptcha"); if (kaptcha != null && serssionKaptcha != null && kaptcha.equalsIgnoreCase(serssionKaptcha)){ return super.authenticate(authentication); } throw new AuthenticationServiceException("驗證碼輸入錯誤"); } }
這裡重寫authenticate方法,在authenticate方法中,從RequestContextHolder中獲取當前請求,進而獲取到驗證碼引數和儲存在HttpSession中的驗證碼文字進行比較,比較通過則繼續執行父類別的authenticate方法,比較不通過,就丟擲異常。
你可能會想到通過重寫 DaoAuthenticationProvider類的 additionalAuthenticationChecks 方法來完成驗證碼的校驗,這個從技術上來說是沒有問題的,但是這會讓驗證碼失去存在的意義,因為當additionalAuthenticationChecks方法被呼叫時,資料庫查詢已經做了,僅僅剩下密碼沒有校驗,此時,通過驗證碼來攔截惡意登入的功能就已經失效了。
最後,在 SecurityConfig 中設定 AuthenticationManager,程式碼如下:
package com.intehel.demo.config; import com.fasterxml.jackson.databind.ObjectMapper; import com.intehel.demo.Service.MyUserDetailsService; import com.intehel.demo.handler.MyAuthenticationFailureHandler; import com.intehel.demo.provider.KaptchaAuthenticationProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.ProviderManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.OrRequestMatcher; import java.util.HashMap; import java.util.Map; @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired MyUserDetailsService myUserDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/vc.jpg").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/mylogin.html") .loginProcessingUrl("/doLogin") .defaultSuccessUrl("/index.html") .failureHandler(new MyAuthenticationFailureHandler()) .usernameParameter("uname") .passwordParameter("passwd") .permitAll() .and() .logout() .logoutRequestMatcher(new OrRequestMatcher(new AntPathRequestMatcher("/logout1","GET"), new AntPathRequestMatcher("/logout2","POST"))) .invalidateHttpSession(true) .clearAuthentication(true) .defaultLogoutSuccessHandlerFor((req,resp,auth)->{ resp.setContentType("application/json;charset=UTF-8"); Map<String,Object> result = new HashMap<String,Object>(); result.put("status",200); result.put("msg","使用logout1登出成功!"); ObjectMapper om = new ObjectMapper(); String s = om.writeValueAsString(result); resp.getWriter().write(s); },new AntPathRequestMatcher("/logout1","GET")) .defaultLogoutSuccessHandlerFor((req,resp,auth)->{ resp.setContentType("application/json;charset=UTF-8"); Map<String,Object> result = new HashMap<String,Object>(); result.put("status",200); result.put("msg","使用logout2登出成功!"); ObjectMapper om = new ObjectMapper(); String s = om.writeValueAsString(result); resp.getWriter().write(s); },new AntPathRequestMatcher("/logout1","GET")) .and() .csrf().disable(); } @Bean AuthenticationProvider kaptchaAuthenticationProvider(){ KaptchaAuthenticationProvider provider = new KaptchaAuthenticationProvider(); provider.setUserDetailsService(myUserDetailsService); return provider; } @Override public AuthenticationManager authenticationManagerBean() throws Exception { ProviderManager manager = new ProviderManager(kaptchaAuthenticationProvider()); return manager; } }
這裡設定分三步:首先設定UserDetailsService提供資料來源;然後提供一個 AuthenticationProvider 範例並設定 UserDetailsService;最後重寫 authenticationManagerBean 方 法,提供一個自己的PioviderManager並使用自定義的AuthenticationProvider範例。
另外需要注意,在configure(HttpSecurity)方法中給驗證碼介面設定放行,permitAll表示這個介面不需要登入就可以存取。
設定完成後,啟動專案,瀏覽器中輸入任意地址都會跳轉到登入頁面,如圖3-5所示。
圖 3-5
此時,輸入使用者名稱、密碼以及驗證碼就可以成功登入。如果驗證碼輸入錯誤,則登入頁面會自動展示錯誤資訊,如下:
到此這篇關於Spring Security設定多個資料來源並新增登入驗證碼的文章就介紹到這了,更多相關Spring Security登入驗證碼內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45