• 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

       

      

      

      

      

      

      

      

  • 相关阅读:
    java 整合redis缓存 SSM 后台框架 rest接口 shiro druid maven bootstrap html5
    《将博客搬至CSDN》
    前后端分离-定义响应格式化数据
    微服务-Springboot+Redis缓存管理接口代码实现
    java语法
    java后台树形结构展示---懒加载
    后端处理前端传过来的日期的两种方式
    汉字转拼音工具类
    Mybatis的小技巧
    调用高德API,通过输入的地址,如省份、市、区获取经纬度 ,通过输入的经纬度,获取区域详情
  • 原文地址:https://www.cnblogs.com/javasl/p/13193847.html
Copyright © 2020-2023  润新知