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;
    
    @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

   

  

  

  

  

  

  

  

原文地址:https://www.cnblogs.com/javasl/p/13193847.html