SpringSecurity基礎功能詳解
本篇目錄:
一、預設情況
二、自定義使用者認證
三、自定義使用者登入頁面
四、自定義登入成功、失敗處理
五、圖形驗證碼
六、記住我功能
七、Session管理
八、退出操作
首先說明本文所用的SpringSecurity版本是2.0.4.RELEASE。下面逐個功能介紹。
一、預設情況
1、構建與配置
1)pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
2)application.properties
無需配置
3)UserController.java
@GetMapping("/user") public List<User> query(){ List<User> users = new ArrayList<User>(); users.add(new User("1","張三","123456",new Date())); return users; }
2、啟動與測試
1)啟動程式,控制檯打印出預設密碼:“Using generated security password: 15a189e8-accb-407a-ad81-2283c8b3bdbf”
2)瀏覽器輸入:http://localhost:8080/user,跳轉到表單登入頁面
3)輸入預設使用者名稱user與預設使用者密碼15a189e8-accb-407a-ad81-2283c8b3bdbf,訪問到資料
二、 自定義使用者認證
1、實現UserDetailsService介面
@Component public class MyUserDetailsService implements UserDetailsService{ @Autowired private PasswordEncoder passwordEncoder; @Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return new User(username, passwordEncoder.encode("123456"), AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
2、說明
1)認證時使用者名稱任意,密碼是12345,輸錯密碼,提示壞的憑證。
2)必須加密,實際專案中,將密碼passwordEncoder.encode("123456")進行加密,寫入資料庫。
3)建構函式四個true假如為false依次代表:使用者已失效;使用者帳號已過期;使用者憑證已過期;使用者帳號已被鎖定。
return new User(username, passwordEncoder.encode("123456"),true,true,true,true, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
三、自定義使用者登入頁面
登入頁面/static/login.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>登入</title> </head> <body> <h2>標準登入頁面</h2> <h3>表單登入</h3> <form action="/authentication/form" method="post"> <table> <tr><td>使用者名稱</td><td><input type="text" name="username"></td></tr> <tr><td>密碼</td><td><input type="password" name="password"></td></tr> <tr><td colspan="2"><button type="submit">登入</button></td></tr> </table> </form> </body> </html>
1、loginPage指定登入頁面
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .loginPage("/login.html")//指定登入頁面 .loginProcessingUrl("/authentication/form");//指定登入頁面中表單的url http.authorizeRequests() .antMatchers("/login.html").permitAll()//該路徑不需要身份認證 .anyRequest() .authenticated(); http.csrf().disable();//先禁止掉跨站請求偽造防護功能 } }
2、loginPage指定Controller,自定義判斷
1)WebSecurityConfig.java
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .loginPage("/authentication/require")//指定需要認證時路徑 .loginProcessingUrl("/authentication/form");//指定登入頁面中表單的url http.authorizeRequests() .antMatchers("/login.html").permitAll()//該路徑不需要身份認證 .antMatchers("/authentication/require").permitAll() .anyRequest() .authenticated(); http.csrf().disable();//先禁止掉跨站請求偽造防護功能 } }
2)SecurityController.java
@RestController public class SecurityController { private RequestCache requestCache=new HttpSessionRequestCache(); private RedirectStrategy redirectStrategy=new DefaultRedirectStrategy(); @RequestMapping("/authentication/require") @ResponseStatus(code=HttpStatus.UNAUTHORIZED) public SimpleResponse requireAuthentication(HttpServletRequest request,HttpServletResponse response) throws Exception { SavedRequest savedRequest=requestCache.getRequest(request, response); if(savedRequest!=null) { String targetUrl=savedRequest.getRedirectUrl(); System.out.println("引發跳轉的請求是:"+targetUrl); if(StringUtils.endsWithIgnoreCase(targetUrl, ".html")) { redirectStrategy.sendRedirect(request, response,"/login.html"); } } return new SimpleResponse("訪問的服務需要身份認證,請引導使用者到登入頁"); } }
3)index.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>index</title> </head> <body> <h2>index測試頁面</h2> </body> </html>
測試輸入http://localhost:8080/index.html,跳轉到登入頁,輸入使用者名稱、密碼跳轉到index.html頁面。
測試輸入http://localhost:8080/user,頁面打印出{"content":"訪問的服務需要身份認證,請引導使用者到登入頁"}。
四、自定義登入成功、失敗處理
1、構建與配置
1)pom.xml新增處理json依賴
<dependency> <groupId>org.codehaus.jackson</groupId> <artifactId>jackson-mapper-asl</artifactId> <version>1.9.13</version> </dependency>
2)AuthenticationSuccessHandler.java
@Component("authenticationSuccessHandler") public class AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler{ @Autowired private SecurityProperties securityProperties; private ObjectMapper objectMapper=new ObjectMapper(); @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { System.out.println("登入成功!"); if(LoginResponseType.JSON.equals(securityProperties.getLoginType())) {//如果配置了JSON格式,返回如下資訊 response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(objectMapper.writeValueAsString(authentication)); }else {//否則執行預設的方式,跳轉原請求地址 super.onAuthenticationSuccess(request, response, authentication); } } }
3)AuthenticationFailureHandler.java
@Component("authenticationFailureHandler") public class AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { @Autowired private SecurityProperties securityProperties; private ObjectMapper objectMapper=new ObjectMapper(); @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { System.out.println("登入失敗!"); if(LoginResponseType.JSON.equals(securityProperties.getLoginType())) { response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(objectMapper.writeValueAsString(new SimpleResponse(exception.getMessage()))); }else {//執行預設的方式,跳轉loginPage配置的地址 super.onAuthenticationFailure(request, response, exception); } } }
4)WebSecurityConfig.java
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ @Autowired private AuthenticationSuccessHandler authenticationSuccessHandler; @Autowired private AuthenticationFailureHandler authenticationFailureHandler; @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .loginPage("/authentication/require")//指定需要認證時路徑 .loginProcessingUrl("/authentication/form")//指定登入頁面中表單的url .successHandler(authenticationSuccessHandler)//認證成功後自定義處理邏輯 .failureHandler(authenticationFailureHandler);//認證失敗後自定義處理邏輯 http.authorizeRequests() .antMatchers("/login.html").permitAll()//該路徑不需要身份認證 .antMatchers("/authentication/require").permitAll() .anyRequest() .authenticated(); http.csrf().disable();//先禁止掉跨站請求偽造防護功能 } }
5)application.properties
project.security.loginType=REDIRECT
6)SecurityCoreConfig.java
@Configuration @EnableConfigurationProperties(SecurityProperties.class) public class SecurityCoreConfig { }
7)LoginResponseType.java
public enum LoginResponseType { REDIRECT, JSON }
8)SecurityProperties.java
@ConfigurationProperties(prefix="project.security") public class SecurityProperties { private LoginResponseType loginType=LoginResponseType.JSON;//預設JSON public LoginResponseType getLoginType() { return loginType; } public void setLoginType(LoginResponseType loginType) { this.loginType = loginType; } }
2、測試
1)application.properties中配置REDIRECT,輸入localhost:8080/index.html,控制檯列印如下資訊,並跳轉登入頁
1.1)輸入錯誤密碼,控制檯列印如下資訊,瀏覽器顯示:{"content":"訪問的服務需要身份認證,請引導使用者到登入頁"}
1.2)輸入正確密碼,控制檯列印“登入成功!”,瀏覽器跳轉index.html,顯示:index測試頁面
2)application.properties中配置JSON,輸入localhost:8080/user,控制檯列印如下資訊,
瀏覽器顯示:{"content":"訪問的服務需要身份認證,請引導使用者到登入頁"}
2.1)瀏覽器輸入:localhost:8080/index.html,控制檯列印如下資訊,並跳轉登入頁。
2.3)輸入錯誤密碼,控制檯列印“登入失敗!”,瀏覽器顯示:{"content":"壞的憑證"}
2.4)輸入正確密碼,控制檯列印“登入成功!”,瀏覽器顯示登入資訊:
問題:為什麼會列印兩次:“引發跳轉的請求”?
五、圖形驗證碼
1、構建與配置
1)ImageCode.java
public class ImageCode{ private BufferedImage image; private String code; private LocalDateTime expireTime; public BufferedImage getImage() { return image; } public void setImage(BufferedImage image) { this.image = image; } public ImageCode(BufferedImage image, String code, int expireIn) { this.image = image; this.code = code; this.expireTime = LocalDateTime.now().plusSeconds(expireIn); } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public LocalDateTime getExpireTime() { return expireTime; } public void setExpireTime(LocalDateTime expireTime) { this.expireTime = expireTime; } public boolean isExpried() { return LocalDateTime.now().isAfter(expireTime); } }
2)ValidateCodeController.java
@RestController public class ValidateCodeController { @GetMapping("/code/image") public void createCode(HttpServletRequest request,HttpServletResponse response) throws Exception { ImageCode imageCode = createImageCode(request); request.getSession().setAttribute("imageCodeSession", imageCode); ImageIO.write(imageCode.getImage(), "JPEG", response.getOutputStream()); } private ImageCode createImageCode(HttpServletRequest request) { int width=67; int height=23; BufferedImage image=new BufferedImage(width, height,BufferedImage.TYPE_INT_RGB); Graphics g=image.getGraphics(); Random random=new Random(); g.setColor(getRandColor(200,250)); g.fillRect(0,0, width, height); g.setFont(new Font("TIME NEW ROMAN", Font.ITALIC, 20)); g.setColor(getRandColor(160,200)); for(int i=0;i<155;i++) { int x=random.nextInt(width); int y=random.nextInt(height); int xl=random.nextInt(12); int yl=random.nextInt(12); g.drawLine(x, y, x+xl,y+yl); } String sRand=""; for(int i=0;i<4;i++) { String rand=String.valueOf(random.nextInt(10)); sRand+=rand; g.setColor(new Color(20+random.nextInt(110),20+random.nextInt(110), 20+random.nextInt(110))); g.drawString(rand,13*i+6,16); } g.dispose(); return new ImageCode(image,sRand,60); } private Color getRandColor(int fc, int bc) { Random random=new Random(); if(fc>255) { fc=255; } if(bc>255) { bc=255; } int r=fc+random.nextInt(bc-fc); int g=fc+random.nextInt(bc-fc); int b=fc+random.nextInt(bc-fc); return new Color(r,g,b); } }
3)ValidateCodeException.java
public class ValidateCodeException extends AuthenticationException { private static final long serialVersionUID = 1L; public ValidateCodeException(String msg) { super(msg); } }
4)ValidateCodeFilter.java
@Component public class ValidateCodeFilter extends OncePerRequestFilter{ @Autowired private AuthenticationFailureHandler authenticationFailureHandler; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { if(StringUtils.equals("http://localhost:8080/authentication/form",request.getRequestURL()+"") && StringUtils.equalsIgnoreCase(request.getMethod(),"post")) { try { validate(request); } catch (ValidateCodeException e) { authenticationFailureHandler.onAuthenticationFailure(request, response, e); return; } } filterChain.doFilter(request, response); } private void validate(HttpServletRequest request){ ImageCode codeInSession = (ImageCode)request.getSession().getAttribute("imageCodeSession"); String codeInRequest = request.getParameter("imageCode"); if (StringUtils.isBlank(codeInRequest)) { throw new ValidateCodeException("驗證碼的值不能為空"); } if (codeInSession == null) { throw new ValidateCodeException("驗證碼不存在"); } if (codeInSession.isExpried()) { request.getSession().removeAttribute("imageCodeSession"); throw new ValidateCodeException("驗證碼已過期"); } if (!StringUtils.equals(codeInSession.getCode(), codeInRequest)) { throw new ValidateCodeException("驗證碼不匹配"); } request.getSession().removeAttribute("imageCodeSession"); } }
5)WebSecurityConfig.java
@EnableWebSecurity public class WebSecurityConfig2 extends WebSecurityConfigurerAdapter{ @Autowired private AuthenticationSuccessHandler authenticationSuccessHandler; @Autowired private AuthenticationFailureHandler authenticationFailureHandler; @Autowired private ValidateCodeFilter validateCodeFilter; @Override protected void configure(HttpSecurity http) throws Exception { http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);//認證前新增驗證碼過濾器 http.formLogin() .loginPage("/authentication/require")//指定需要認證時路徑 .loginProcessingUrl("/authentication/form")//指定登入頁面中表單的url .successHandler(authenticationSuccessHandler)//認證成功後自定義處理邏輯 .failureHandler(authenticationFailureHandler);//認證失敗後自定義處理邏輯 http.authorizeRequests() .antMatchers("/login.html").permitAll()//該路徑不需要身份認證 .antMatchers("/authentication/require").permitAll() .antMatchers("/code/image").permitAll()//圖片驗證碼 .anyRequest() .authenticated(); http.csrf().disable();//先禁止掉跨站請求偽造防護功能 } }
6)static/login.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>登入</title> </head> <body> <h2>標準登入頁面</h2> <h3>表單登入</h3> <form action="/authentication/form" method="post"> <table> <tr><td>使用者名稱</td><td><input type="text" name="username"></td></tr> <tr><td>密碼</td><td><input type="password" name="password"></td></tr> <tr><td>圖片驗證碼</td><td><input type="text" name="imageCode"><img src="/code/image"/></td></tr> <tr><td colspan="2"><button type="submit">登入</button></td></tr> </table> </form> </body> </html>
2、說明
1)驗證碼處理流程為:生成驗證碼->放在Session中->驗證->清空Session
2)過濾器OncePerRequestFilter,每一次請求只進入一次該過濾器
六、記住我功能
1、構建與配置
1)pom.xml新增以下依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
2)application.properties
project.security.loginType=REDIRECT spring.datasource.driverClassName=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3309/springsecurity spring.datasource.username=root spring.datasource.password=123456
3)login.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登入</title>
</head>
<body>
<h2>標準登入頁面</h2>
<h3>表單登入</h3>
<form action="/authentication/form" method="post">
<table>
<tr><td>使用者名稱</td><td><input type="text" name="username"></td></tr>
<tr><td>密碼</td><td><input type="password" name="password"></td></tr>
<tr><td>圖片驗證碼</td><td><input type="text" name="imageCode"><img src="/code/image"/></td></tr>
<tr><td colspan="2"><input name="remember-me" type="checkbox" value="true"/>記住我</td></tr>
<tr><td colspan="2"><button type="submit">登入</button></td></tr>
</table>
</form>
</body>
</html>
4)WebSecurityConfig.java
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ @Autowired private AuthenticationSuccessHandler authenticationSuccessHandler; @Autowired private AuthenticationFailureHandler authenticationFailureHandler; @Autowired private ValidateCodeFilter validateCodeFilter; @Autowired private DataSource dataSource; @Autowired private UserDetailsService userDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);//認證前新增驗證碼過濾器 http.formLogin() .loginPage("/authentication/require")//指定需要認證時路徑 .loginProcessingUrl("/authentication/form")//指定登入頁面中表單的url .successHandler(authenticationSuccessHandler)//認證成功後自定義處理邏輯 .failureHandler(authenticationFailureHandler);//認證失敗後自定義處理邏輯 http.rememberMe()//記住我 .tokenRepository(persistentTokenRepository()) .tokenValiditySeconds(60*60*1)//記住我1小時 .userDetailsService(userDetailsService); http.authorizeRequests() .antMatchers("/login.html").permitAll()//該路徑不需要身份認證 .antMatchers("/authentication/require").permitAll() .antMatchers("/code/image").permitAll() .anyRequest() .authenticated(); http.csrf().disable();//先禁止掉跨站請求偽造防護功能 } @Bean public PersistentTokenRepository persistentTokenRepository() { JdbcTokenRepositoryImpl tokenRepository=new JdbcTokenRepositoryImpl(); tokenRepository.setDataSource(dataSource); tokenRepository.setCreateTableOnStartup(true);//第一次執行開啟,建立資料庫的表,以後不需要,註釋掉 return tokenRepository; } }
2、測試
1)啟動專案,資料庫SpringSecurity中預設建立persistent_logins表,結構如下:
2)訪問http://localhost:8080/index.html,用賬號user登入,persistent_logins表中存了user賬號的資訊
3)重啟專案,再次訪問http://localhost:8080/index.html,無需登入直接進入index.html頁面
4)驗證記住我時間
4.1)設定為1分鐘,清空表persistent_logins,啟動專案,瀏覽器輸入http://localhost:8080/index.html,勾選記住我,登入。停止專案。
4.2)一分鐘後,啟動專案,輸入http://localhost:8080/index.html,發現需要登入,驗證生效。勾選記住我,登入。
4.3)查詢persistent_logins,發現裡面有兩條user資訊,分別是兩次登入時儲存的,如下:
七、Session管理
1、設定超時時間
application.properties,新增以下配置,Session配置為1分鐘(SpringBoot中最小一分鐘)
server.servlet.session.timeout=1m
2、設定超時後跳轉地址
1)WebSecurityConfig.java
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ ... ... @Override protected void configure(HttpSecurity http) throws Exception { ... ... http.sessionManagement() .invalidSessionUrl("/session/invalid")//session超時後的跳轉地址,不會進入loginPage定義的地址中了 ... ... } }
2)SecurityController.java
@RestController public class SecurityController { ... ... @GetMapping("/session/invalid") @ResponseStatus(code=HttpStatus.UNAUTHORIZED) public SimpleResponse sessionInvalid() { System.out.println("session失效"); return new SimpleResponse("session失效"); } }
測試:啟動專案,訪問localhost:8080/index.html,登陸,停止專案後再次啟動,重新整理該地址,瀏覽器出現“session失效”。
3、設定單機登陸
WebSecurityConfig.java
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ ... ... @Override protected void configure(HttpSecurity http) throws Exception { ... ... http.sessionManagement() .invalidSessionUrl("/session/invalid")//session超時後的跳轉地址,不會進入loginPage定義的地址中了 .maximumSessions(1);//最大session數量,1代表只能一個登入,後面的會把前面的踢掉 ... ... } }
測試:Chrome瀏覽器訪問localhost:8080/index.html,用sl登陸;換360瀏覽器訪問該地址,再次用sl登陸,重新整理Chrome,如下:
4、Session達到最大數後,阻止後面的登入
WebSecurityConfig.java
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ ... ... http.sessionManagement() .invalidSessionUrl("/session/invalid")//session超時後的跳轉地址,不會進入loginPage定義的地址中了 .maximumSessions(1)//最大session數量,1代表只能一個登入,後面的會把前面的踢掉 .maxSessionsPreventsLogin(true);//session數量達到了後,阻止後面的登入 ... ... } }
測試:用兩個瀏覽器先後登入,第二個登入後頁面顯示:{"content":"訪問的服務需要身份認證,請引導使用者到登入頁"}
5、Session被踢掉後的處理
1)WebSecurityConfig.java
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ ... ... @Override protected void configure(HttpSecurity http) throws Exception { ... ... http.sessionManagement() .invalidSessionUrl("/session/invalid")//session超時後的跳轉地址,不會進入loginPage定義的地址中了 .maximumSessions(1)//最大session數量,1代表只能一個登入,後面的會把前面的踢掉 //.maxSessionsPreventsLogin(true)//session數量達到了後,阻止後面的登入 .expiredSessionStrategy(new MyexpiredSessionStrategy());//踢掉先登入的session,先登入的再請求後端進入該類的方法 ... ... } }
2)MyexpiredSessionStrategy.java
public class MyexpiredSessionStrategy implements SessionInformationExpiredStrategy{ @Override public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException { event.getResponse().setContentType("application/json;charset=UTF-8"); event.getResponse().getWriter().write("併發登入"); } }
測試 :先用Chrome瀏覽器登入,在用360登入,然後重新整理Chrome,頁面出現“併發登入” 。
說明:不能與maxSessionsPreventsLogin同時設定,否則不會生效,會執行阻止後面的登入的邏輯。
八、退出操作
1、預設退出操作
1)執行退出操作做的事:使當前Session失效;清除與當前使用者相關的remember-me記錄;清除當前的SecurityContext;重定向到登入頁。
2)新增退出的超級連結:<a href="/logout">退出</a>,點選就能退出。
2、自定義退出連線
1)WebSecurityConfig.java
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ ... ... @Override protected void configure(HttpSecurity http) throws Exception { ... ... http.logout() .logoutUrl("/signOut")//指定退出的連線,預設/logout ... ... } }
2)新增退出的超級連結:<a href="/signOut">退出</a>,點選就能退出。
3、自定義退出後跳轉的url
WebSecurityConfig.java
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ ... ... @Override protected void configure(HttpSecurity http) throws Exception { ... ... http.sessionManagement() //.invalidSessionUrl("/session/invalid")//session超時後的跳轉地址,不會進入loginPage定義的地址中了 .maximumSessions(1)//最大session數量,1代表只能一個登入,後面的會把前面的踢掉 //.maxSessionsPreventsLogin(true)//session數量達到了後,阻止後面的登入 .expiredSessionStrategy(new MyexpiredSessionStrategy());//踢掉先登入的session,先登入的再請求後端進入該類的方法 http.logout() .logoutUrl("/signOut")//指定退出的連線,預設/logout .logoutSuccessUrl("/logout.html");//自動退出後跳轉的url,預設跳到登入的url上 http.authorizeRequests() .antMatchers( "/login.html", "/authentication/require", "/code/image", "/session/invalid", "/logout.html" ).permitAll()//該路徑不需要身份認證 .anyRequest() .authenticated(); http.csrf().disable();//先禁止掉跨站請求偽造防護功能 } }
說明:必須去掉 invalidSessionUrl 配置項,否則點選退出後,會跳轉到 invalidSessionUrl指定的連線。
4、自定義退出後跳轉處理
1)WebSecurityConfig.java
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ ... ... @Autowired private MyLogoutSuccessHandler logoutSuccessHandler; @Override protected void configure(HttpSecurity http) throws Exception { ... ... http.sessionManagement() .invalidSessionUrl("/session/invalid")//session超時後的跳轉地址,不會進入loginPage定義的地址中了 .maximumSessions(1)//最大session數量,1代表只能一個登入,後面的會把前面的踢掉 //.maxSessionsPreventsLogin(true)//session數量達到了後,阻止後面的登入 .expiredSessionStrategy(new MyexpiredSessionStrategy());//踢掉先登入的session,先登入的再請求後端進入該類的方法 http.logout() .logoutUrl("/signOut")//指定退出的連線,預設/logout //.logoutSuccessUrl("/logout.html")//自動退出後跳轉的url,預設跳到登入的url上 .logoutSuccessHandler(logoutSuccessHandler)//退出成功後,自定義的操作,不能與logoutSuccessUrl同時存在 .deleteCookies("JSESSIONID"); ... ... } }
2)MyLogoutSuccessHandler.java
@Component public class MyLogoutSuccessHandler implements LogoutSuccessHandler { @Autowired private SecurityProperties securityProperties; private ObjectMapper objectMapper=new ObjectMapper(); @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { System.out.println("onLogoutSuccess:退出成功!"); LoginResponseType loginType = securityProperties.getLoginType(); if(LoginResponseType.JSON.equals(loginType)) { response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(objectMapper.writeValueAsString(new SimpleResponse("退出成功!"))); }else { response.sendRedirect("/logout.html"); } } }
project.security.loginType配置為REDIRECT,退出後跳轉到 logout.html頁面;配置為JSON,頁面顯示出:{"content":"退出成功!"}。
logoutSuccessHandler與logoutSuccessUrl同時配置,logoutSuccessUrl會失效。
優先順序:logoutSuccessHandler >invalidSessionUrl >logoutSuccessUrl