本篇目录:
一、默认情况
二、自定义用户认证
三、自定义用户登录页面
四、自定义登录成功、失败处理
五、图形验证码
六、记住我功能
七、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; @Override public 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