springboot集成security分为前后端分离模式和前后端不分离模式,下面对目前流行的前后端分离模式进行分析。
目前登录有两个方向,传统的通过session来记录用户认证信息的方式是一种有状态登录,而通过JWT的方式则代表了一种无状态登录。
有状态登录,是指服务端记录每次会话的客户端信息,从而识别客户端身份,根据客户端身份进行请求的处理。如tomcat中的session,用户登录后,用户信息保存在服务端的session中,并给用户一个cookie值,记录对应的session,然后下次请求,用户携带cookie值进行请求(浏览器自动完成),服务端识别对应session进行相应处理。
无状态登录,是指服务端对外提供restful风格的接口,返回访问令牌。如用户发送账号密码到服务端进行登录,认证通过后,服务端将用户信息加密且编码成一个token返回给用户,以后用户每次都携带token进行请求,服务端对token进行验证处理。
使用session的优势在于方便,“第一次约会”即是基于session的方式。但是无法应对前端的多元化趋势,包括Android、ios、公众号、小程序等,因为没有cookie。这个时候像JWT这样的无状态登录就大有用武之地了。
不过话说回来,若是前后端分离模式的场景只是网页+服务端,则没必要上无状态登录,基于session来做就可以,有很方便。
本文对基于session的有状态登录+前后端分离模式进行分析。我们知道spring security默认是表单登录,及传统的前后端不分离模式,所以需要对security进行前后端分离模式的配置。
SecurityConfig配置类

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    @Bean
    protected UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("caocao").password(passwordEncoder().encode("123")).roles("admin").build());
        manager.createUser(User.withUsername("liubei").password(passwordEncoder().encode("123")).roles("user").build());
        manager.createUser(User.withUsername("sunquan").password(passwordEncoder().encode("123")).roles("user").build());
        return manager;
    }

    @Bean
    protected PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .successHandler(((request, response, authentication) -> {
                    Object principal = authentication.getPrincipal();
                    response.setContentType("application/json;charset=utf-8");
                    PrintWriter printWriter = response.getWriter();
                    printWriter.write(new ObjectMapper().writeValueAsString(principal));
                    printWriter.flush();
                    printWriter.close();
                }))
                .failureHandler(((request, response, exception) -> {
                    response.setContentType("application/json;charset=utf-8");
                    PrintWriter printWriter = response.getWriter();
                    String exceptionMessage = exception.getMessage();
                    if (exception instanceof LockedException) {
                        exceptionMessage = "账户被锁定,请联系管理员!";
                    } else if (exception instanceof CredentialsExpiredException) {
                        exceptionMessage = "密码过期,请联系管理员!";
                    } else if (exception instanceof AccountExpiredException) {
                        exceptionMessage = "账户过期,请联系管理员!";
                    } else if (exception instanceof DisabledException) {
                        exceptionMessage = "账户被禁用,请联系管理员!";
                    } else if (exception instanceof BadCredentialsException) {
                        exceptionMessage = "用户名或者密码输入错误,请重新输入!";
                    }
                    printWriter.write(exceptionMessage);
                    printWriter.flush();
                    printWriter.close();
                }))
                .and()
                .logout()
                .logoutSuccessHandler(((request, response, authentication) -> {
                    response.setContentType("application/json;charset=utf-8");
                    PrintWriter printWriter = response.getWriter();
                    printWriter.write("注销成功");
                    printWriter.flush();
                    printWriter.close();
                }))
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(((request, response, authException) -> {
                    response.setContentType("application/json;charset=utf-8");
                    PrintWriter printWriter = response.getWriter();
                    printWriter.write("尚未登录,请先登录");
                    printWriter.flush();
                    printWriter.close();
                }))
                .and()
                .csrf()
                .disable();
    }
}

在前后端分离模式下,所有的交互都是通过JSON进行通信,无论是登录成功还是失败,或者是未登录及异常。

successHandler,登录成功,服务端就返回一段登录成功的json给前端,前端收到json后,该跳转跳转,该展示展示,由前端自行决定。

failureHandler,登录失败,服务端就返回一段登录失败的json给前端,前端还是自行处理。

logoutSuccessHandler,注销成功场景的处理方式同上。

exceptionHandling,异常场景的处理方式同上。

重写启动项目,通过postman进行请求,相应的返回如下

springboot前后端不分离如何做到快速分离 springboot前后端分离登录_java


springboot前后端不分离如何做到快速分离 springboot前后端分离登录_前后端分离_02


springboot前后端不分离如何做到快速分离 springboot前后端分离登录_json_03


springboot前后端不分离如何做到快速分离 springboot前后端分离登录_服务端_04