javascript
SpringBoot整合Spring Security——登录管理
文章目錄
- 一、自定義認證成功、失敗處理
- 1.1 CustomAuthenticationSuccessHandle
- 1.2 CustomAuthenticationFailureHandler
- 1.3 修改 WebSecurityConfig
- 1.4 運行程序
- 二、Session 超時
- 三、限制最大登錄數
- 四、踢出用戶
- 五、退出登錄
- 六、Session 共享
- 6.1 導入依賴
- 6.3 運行程序
一、自定義認證成功、失敗處理
有些時候我們想要在認證成功后做一些業務處理,例如添加積分;有些時候我們想要在認證失敗后也做一些業務處理,例如記錄日志。
在之前的文章中,關于認證成功、失敗后的處理都是如下配置的:
http.authorizeRequests()// 如果有允許匿名的url,填在下面 // .antMatchers().permitAll().anyRequest().authenticated().and()// 設置登陸頁.formLogin().loginPage("/login").failureUrl("/login/error").defaultSuccessUrl("/").permitAll()...;即 failureUrl() 指定認證失敗后Url,defaultSuccessUrl() 指定認證成功后Url。我們可以通過設置 successHandler() 和 failureHandler() 來實現自定義認證成功、失敗處理。
PS:當我們設置了這兩個后,需要去除 failureUrl() 和 defaultSuccessUrl() 的設置,否則無法生效。這兩套配置同時只能存在一套
1.1 CustomAuthenticationSuccessHandle
自定義 CustomAuthenticationSuccessHandler 類來實現 AuthenticationSuccessHandler 接口,用來處理認證成功后邏輯:
@Component public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {private Logger logger = LoggerFactory.getLogger(getClass());@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {logger.info("登錄成功,{}", authentication);response.sendRedirect("/");} }onAuthenticationSuccess() 方法的第三個參數 Authentication 為認證后該用戶的認證信息,這里打印日志后,重定向到了首頁。
1.2 CustomAuthenticationFailureHandler
自定義 CustomAuthenticationFailureHandler 類來實現 AuthenticationFailureHandler 接口,用來處理認證失敗后邏輯:
@Component public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {private Logger logger = LoggerFactory.getLogger(getClass());@Autowiredprivate ObjectMapper objectMapper;@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {logger.info("登錄失敗,{}");response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());response.setContentType("application/json;charset=UTF-8");response.getWriter().write(objectMapper.writeValueAsString(e.getMessage()));} }onAuthenticationFailure()方法的第三個參數 exception 為認證失敗所產生的異常,這里也是簡單的返回到前臺。
1.3 修改 WebSecurityConfig
@Autowiredprivate CustomAuthenticationFailureHandler customAuthenticationFailureHandler;@Autowiredprivate CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler; @Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()// 如果有允許匿名的url,填在下面//.antMatchers().permitAll().anyRequest().authenticated().and()// 設置登陸頁.formLogin().loginPage("/login").successHandler(customAuthenticationSuccessHandler).failureHandler(customAuthenticationFailureHandler) // .failureUrl("/login/error") // .defaultSuccessUrl("/").permitAll();http.csrf().disable();}1.首先將 customAuthenticationSuccessHandler 和 customAuthenticationFailureHandler注入進來
2.配置 successHandler() 和 failureHandler()
3.注釋 failureUrl() 和 defaultSuccessUrl()
1.4 運行程序
運行程序,當我們成功登陸后,發現日志信息被打印出來,頁面被重定向到了首頁:
當我們認證失敗后,發現日志中“登陸失敗”被打印出來,頁面展示了認證失敗的異常消息:
二、Session 超時
當用戶登錄后,我們可以設置 session 的超時時間,當達到超時時間后,自動將用戶退出登錄。
Session 超時的配置是 SpringBoot 原生支持的,我們只需要在 application.properties 配置文件中配置:
# session 過期時間,單位:秒 server.servlet.session.timeout=60Tip:
從用戶最后一次操作開始計算過期時間。
過期時間最小值為 60 秒,如果你設置的值小于 60 秒,也會被更改為 60 秒。
我們可以在 Spring Security 中配置處理邏輯,在 session 過期退出時調用。修改 WebSecurityConfig 的 configure() 方法,添加:
.sessionManagement()// 以下二選一//.invalidSessionStrategy()//.invalidSessionUrl();Spring Security 提供了兩種處理配置,一個是 invalidSessionStrategy(),另外一個是 invalidSessionUrl()。
這兩個的區別就是一個是前者是在一個類中進行處理,后者是直接跳轉到一個 Url。簡單起見,我就直接用 invalidSessionUrl()了,跳轉到 /login/invalid,我們需要把該 Url 設置為免授權訪問, 配置如下:
@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()// 如果有允許匿名的url,填在下面.antMatchers("/login/invalid").permitAll().anyRequest().authenticated().and()// 設置登陸頁.formLogin().loginPage("/login").successHandler(customAuthenticationSuccessHandler).failureHandler(customAuthenticationFailureHandler).permitAll().and() // .failureUrl("/login/error") // .defaultSuccessUrl("/").sessionManagement().invalidSessionUrl("/login/invalid");http.csrf().disable();}在 controller 中寫一個接口進行處理:
@RequestMapping("/login/invalid") @ResponseStatus(HttpStatus.UNAUTHORIZED) @ResponseBody public String invalid() {return "Session 已過期,請重新登錄"; }運行程序,登陸成功后等待一分鐘(或者重啟服務器),刷新頁面:
三、限制最大登錄數
接下來實現限制最大登陸數,原理就是限制單個用戶能夠存在的最大 session 數。
在上一節的基礎上,修改 configure() 為:
@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()// 如果有允許匿名的url,填在下面.antMatchers("/login/invalid").permitAll().anyRequest().authenticated().and()// 設置登陸頁.formLogin().loginPage("/login").successHandler(customAuthenticationSuccessHandler).failureHandler(customAuthenticationFailureHandler).permitAll().and() // .failureUrl("/login/error") // .defaultSuccessUrl("/").sessionManagement().invalidSessionUrl("/login/invalid")//指定最大登錄數.maximumSessions(1)//當達到最大值時,是否保留已經登錄的用戶.maxSessionsPreventsLogin(false)//當達到最大值時,舊用戶被踢出后的操作.expiredSessionStrategy(customExpiredSessionStrategy);http.csrf().disable();}增加了下面三行代碼,其中:
maximumSessions(int):指定最大登錄數
maxSessionsPreventsLogin(boolean):是否保留已經登錄的用戶;為true,新用戶無法登錄;為 false,舊用戶被踢出
expiredSessionStrategy(SessionInformationExpiredStrategy):舊用戶被踢出后處理方法
maxSessionsPreventsLogin()可能不太好理解,這里我們先設為 false,效果和 QQ 登錄是一樣的,登陸后之前登錄的賬戶被踢出。
編寫 CustomExpiredSessionStrategy 類,來處理舊用戶登陸失敗的邏輯:
@Component public class CustomExpiredSessionStrategy implements SessionInformationExpiredStrategy {private ObjectMapper objectMapper = new ObjectMapper();@Overridepublic void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {Map<String,Object> map = new HashMap<>();map.put("code",0);map.put("msg","已經另一臺機器登錄,您被迫下線。" + event.getSessionInformation().getLastRequest());String s = objectMapper.writeValueAsString(map);event.getResponse().setContentType("application/json;charset=UTF-8");event.getResponse().getWriter().write(s);} }在 onExpiredSessionDetected() 方法中,處理相關邏輯,我這里只是簡單的返回一句話。
執行程序,打開兩個瀏覽器,登錄同一個賬戶。因為我設置了 maximumSessions(1),也就是單個用戶只能存在一個 session,因此當你刷新先登錄的那個瀏覽器時,被提示踢出了。
下面我們來測試下 maxSessionsPreventsLogin(true) 時的情況,我們發現第一個瀏覽器登錄后,第二個瀏覽器無法登錄:
四、踢出用戶
下面來看下如何主動踢出一個用戶。
首先需要在容器中注入名為 SessionRegistry 的 Bean,這里我就簡單的寫在 WebSecurityConfig 中:
@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()// 如果有允許匿名的url,填在下面.antMatchers("/login/invalid").permitAll().anyRequest().authenticated().and()// 設置登陸頁.formLogin().loginPage("/login").successHandler(customAuthenticationSuccessHandler).failureHandler(customAuthenticationFailureHandler).permitAll().and() // .failureUrl("/login/error") // .defaultSuccessUrl("/").sessionManagement().invalidSessionUrl("/login/invalid")//指定最大登錄數.maximumSessions(1)//當達到最大值時,是否保留已經登錄的用戶.maxSessionsPreventsLogin(false)//當達到最大值時,舊用戶被踢出后的操作.expiredSessionStrategy(customExpiredSessionStrategy).sessionRegistry(sessionRegistry());http.csrf().disable();} @Bean public SessionRegistry sessionRegistry() {return new SessionRegistryImpl(); }編寫一個接口用于測試踢出用戶:
@GetMapping("/click")@ResponseBodypublic String removeUserSessionByUsername(@RequestParam String username) {int count = 0;List<Object> users = sessionRegistry.getAllPrincipals();for (Object user:users) {if (user instanceof User) {String principalName = ((User) user).getUsername();if (principalName.equals(username)) {List<SessionInformation> allSessions = sessionRegistry.getAllSessions(user, false);if (allSessions != null && allSessions.size() > 0) {for (SessionInformation sessionInformation:allSessions) {sessionInformation.expireNow();count++;}}}}}return "操作成功,清理session共" + count + "個";}運行程序,分別使用 admin 和 zhao賬戶登錄,admin 訪問 /kick?username=zhao 來踢出用戶 zhao,zhao 刷新頁面,發現被踢出。
五、退出登錄
補充一下退出登錄的內容,在之前,我們直接在 WebSecurityConfig 的 configure() 方法中,配置了:
http.logout();
這就是 Spring Security 的默認退出配置,Spring Security 在退出時候做了這樣幾件事:
Spring Security 默認的退出 Url 是 /logout,我們可以修改默認的退出 Url,例如修改為 /signout:
WebSecurityConfig配置如下:
六、Session 共享
在最后補充下關于 Session 共享的知識點,一般情況下,一個程序為了保證穩定至少要部署兩個,構成集群。那么就牽扯到了 Session 共享的問題,不然用戶在 8080 登錄成功后,后續訪問了 8060 服務器,結果又提示沒有登錄。
這里就簡單實現下 Session 共享,采用 Redis 來存儲。
6.1 導入依賴
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId> </dependency>在 application.xml 中新增配置指定 redis 地址以及 session 的存儲方式
spring.redis.host=localhost spring.redis.port=6379spring.session.store-type=redis然后為主類添加 @EnableRedisHttpSession 注解。
@EnableRedisHttpSession @SpringBootApplication public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);} }6.3 運行程序
分別啟動兩個端口8080,8086
先訪問 localhost:8080,登錄成功后,再訪問 localhost:8060,發現無需登錄。
然后我們進入 Redis 查看下 key:
最后再測試下之前配置的 session 設置是否還有效,使用其他瀏覽器登陸,登陸成功后發現原瀏覽器用戶的確被踢出。
總結
以上是生活随笔為你收集整理的SpringBoot整合Spring Security——登录管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: springboo整合security—
- 下一篇: gradle idea java ssm