SpringSecurity登录使用JSON格式数据

2年前 (2022) 程序员胖胖胖虎阿
623 0 0

在使用SpringSecurity中,大伙都知道默认的登录数据是通过key/value的形式来传递的,默认情况下不支持JSON格式的登录数据,如果有这种需求,就需要自己来解决,本文主要和小伙伴来聊聊这个话题。

基本登录方案

在说如何使用JSON登录之前,我们还是先来看看基本的登录吧,本文为了简单,SpringSecurity在使用中就不连接数据库了,直接在内存中配置用户名和密码,具体操作步骤如下:

1.创建Spring Boot工程

首先创建SpringBoot工程,添加SpringSecurity依赖,如下:

  
  
  
  1. <dependency>

  2.    <groupId>org.springframework.boot</groupId>

  3.    <artifactId>spring-boot-starter-security</artifactId>

  4. </dependency>

  5. <dependency>

  6.    <groupId>org.springframework.boot</groupId>

  7.    <artifactId>spring-boot-starter-web</artifactId>

  8. </dependency>

2.添加Security配置

创建SecurityConfig,完成SpringSecurity的配置,如下:

  
  
  
  1. @Configuration

  2. public class SecurityConfig extends WebSecurityConfigurerAdapter {

  3.    @Bean

  4.    PasswordEncoder passwordEncoder() {

  5.        return new BCryptPasswordEncoder();

  6.    }

  7.    @Override

  8.    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

  9.        auth.inMemoryAuthentication().withUser("zhangsan").password("$2a$10$2O4EwLrrFPEboTfDOtC0F.RpUMk.3q3KvBHRx7XXKUMLBGjOOBs8q").roles("user");

  10.    }


  11.    @Override

  12.    public void configure(WebSecurity web) throws Exception {

  13.    }


  14.    @Override

  15.    protected void configure(HttpSecurity http) throws Exception {

  16.        http.authorizeRequests()

  17.                .anyRequest().authenticated()

  18.                .and()

  19.                .formLogin()

  20.                .loginProcessingUrl("/doLogin")

  21.                .successHandler(new AuthenticationSuccessHandler() {

  22.                    @Override

  23.                    public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {

  24.                        RespBean ok = RespBean.ok("登录成功!",authentication.getPrincipal());

  25.                        resp.setContentType("application/json;charset=utf-8");

  26.                        PrintWriter out = resp.getWriter();

  27.                        out.write(new ObjectMapper().writeValueAsString(ok));

  28.                        out.flush();

  29.                        out.close();

  30.                    }

  31.                })

  32.                .failureHandler(new AuthenticationFailureHandler() {

  33.                    @Override

  34.                    public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {

  35.                        RespBean error = RespBean.error("登录失败");

  36.                        resp.setContentType("application/json;charset=utf-8");

  37.                        PrintWriter out = resp.getWriter();

  38.                        out.write(new ObjectMapper().writeValueAsString(error));

  39.                        out.flush();

  40.                        out.close();

  41.                    }

  42.                })

  43.                .loginPage("/login")

  44.                .permitAll()

  45.                .and()

  46.                .logout()

  47.                .logoutUrl("/logout")

  48.                .logoutSuccessHandler(new LogoutSuccessHandler() {

  49.                    @Override

  50.                    public void onLogoutSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {

  51.                        RespBean ok = RespBean.ok("注销成功!");

  52.                        resp.setContentType("application/json;charset=utf-8");

  53.                        PrintWriter out = resp.getWriter();

  54.                        out.write(new ObjectMapper().writeValueAsString(ok));

  55.                        out.flush();

  56.                        out.close();

  57.                    }

  58.                })

  59.                .permitAll()

  60.                .and()

  61.                .csrf()

  62.                .disable()

  63.                .exceptionHandling()

  64.                .accessDeniedHandler(new AccessDeniedHandler() {

  65.                    @Override

  66.                    public void handle(HttpServletRequest req, HttpServletResponse resp, AccessDeniedException e) throws IOException, ServletException {

  67.                        RespBean error = RespBean.error("权限不足,访问失败");

  68.                        resp.setStatus(403);

  69.                        resp.setContentType("application/json;charset=utf-8");

  70.                        PrintWriter out = resp.getWriter();

  71.                        out.write(new ObjectMapper().writeValueAsString(error));

  72.                        out.flush();

  73.                        out.close();

  74.                    }

  75.                });


  76.    }

  77. }

这里的配置虽然有点长,但是很基础,配置含义也比较清晰,首先提供BCryptPasswordEncoder作为PasswordEncoder,可以实现对密码的自动加密加盐,非常方便,然后提供了一个名为 zhangsan的用户,密码是 123,角色是 user,最后配置登录逻辑,所有的请求都需要登录后才能访问,登录接口是 /doLogin,用户名的key是username,密码的key是password,同时配置登录成功、登录失败以及注销成功、权限不足时都给用户返回JSON提示,另外,这里虽然配置了登录页面为 /login,实际上这不是一个页面,而是一段JSON,在LoginController中提供该接口,如下:

  
  
  
  1. @RestController

  2. @ResponseBody

  3. public class LoginController {

  4.    @GetMapping("/login")

  5.    public RespBean login() {

  6.        return RespBean.error("尚未登录,请登录");

  7.    }

  8.    @GetMapping("/hello")

  9.    public String hello() {

  10.        return "hello";

  11.    }

  12. }

这里 /login只是一个JSON提示,而不是页面, /hello则是一个测试接口。

OK,做完上述步骤就可以开始测试了,运行SpringBoot项目,访问 /hello接口,结果如下:

SpringSecurity登录使用JSON格式数据

此时先调用登录接口进行登录,如下:

SpringSecurity登录使用JSON格式数据

登录成功后,再去访问 /hello接口就可以成功访问了。

使用JSON登录

上面演示的是一种原始的登录方案,如果想将用户名密码通过JSON的方式进行传递,则需要自定义相关过滤器,通过分析源码我们发现,默认的用户名密码提取在UsernamePasswordAuthenticationFilter过滤器中,部分源码如下:

  
  
  
  1. public class UsernamePasswordAuthenticationFilter extends

  2.        AbstractAuthenticationProcessingFilter {

  3.    public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";

  4.    public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";


  5.    private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;

  6.    private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;

  7.    private boolean postOnly = true;

  8.    public UsernamePasswordAuthenticationFilter() {

  9.        super(new AntPathRequestMatcher("/login", "POST"));

  10.    }


  11.    public Authentication attemptAuthentication(HttpServletRequest request,

  12.            HttpServletResponse response) throws AuthenticationException {

  13.        if (postOnly && !request.getMethod().equals("POST")) {

  14.            throw new AuthenticationServiceException(

  15.                    "Authentication method not supported: " + request.getMethod());

  16.        }


  17.        String username = obtainUsername(request);

  18.        String password = obtainPassword(request);


  19.        if (username == null) {

  20.            username = "";

  21.        }


  22.        if (password == null) {

  23.            password = "";

  24.        }


  25.        username = username.trim();


  26.        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(

  27.                username, password);


  28.        // Allow subclasses to set the "details" property

  29.        setDetails(request, authRequest);


  30.        return this.getAuthenticationManager().authenticate(authRequest);

  31.    }


  32.    protected String obtainPassword(HttpServletRequest request) {

  33.        return request.getParameter(passwordParameter);

  34.    }


  35.    protected String obtainUsername(HttpServletRequest request) {

  36.        return request.getParameter(usernameParameter);

  37.    }

  38.    //...

  39.    //...

  40. }

从这里可以看到,默认的用户名/密码提取就是通过request中的getParameter来提取的,如果想使用JSON传递用户名密码,只需要将这个过滤器替换掉即可,自定义过滤器如下:

  
  
  
  1. public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

  2.    @Override

  3.    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

  4.        if (request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)

  5.                || request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {

  6.            ObjectMapper mapper = new ObjectMapper();

  7.            UsernamePasswordAuthenticationToken authRequest = null;

  8.            try (InputStream is = request.getInputStream()) {

  9.                Map<String,String> authenticationBean = mapper.readValue(is, Map.class);

  10.                authRequest = new UsernamePasswordAuthenticationToken(

  11.                        authenticationBean.get("username"), authenticationBean.get("password"));

  12.            } catch (IOException e) {

  13.                e.printStackTrace();

  14.                authRequest = new UsernamePasswordAuthenticationToken(

  15.                        "", "");

  16.            } finally {

  17.                setDetails(request, authRequest);

  18.                return this.getAuthenticationManager().authenticate(authRequest);

  19.            }

  20.        }

  21.        else {

  22.            return super.attemptAuthentication(request, response);

  23.        }

  24.    }

  25. }

这里只是将用户名/密码的获取方案重新修正下,改为了从JSON中获取用户名密码,然后在SecurityConfig中作出如下修改:

  
  
  
  1. @Override

  2. protected void configure(HttpSecurity http) throws Exception {

  3.    http.authorizeRequests().anyRequest().authenticated()

  4.            .and()

  5.            .formLogin()

  6.            .and().csrf().disable();

  7.    http.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

  8. }

  9. @Bean

  10. CustomAuthenticationFilter customAuthenticationFilter() throws Exception {

  11.    CustomAuthenticationFilter filter = new CustomAuthenticationFilter();

  12.    filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {

  13.        @Override

  14.        public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {

  15.            resp.setContentType("application/json;charset=utf-8");

  16.            PrintWriter out = resp.getWriter();

  17.            RespBean respBean = RespBean.ok("登录成功!");

  18.            out.write(new ObjectMapper().writeValueAsString(respBean));

  19.            out.flush();

  20.            out.close();

  21.        }

  22.    });

  23.    filter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {

  24.        @Override

  25.        public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {

  26.            resp.setContentType("application/json;charset=utf-8");

  27.            PrintWriter out = resp.getWriter();

  28.            RespBean respBean = RespBean.error("登录失败!");

  29.            out.write(new ObjectMapper().writeValueAsString(respBean));

  30.            out.flush();

  31.            out.close();

  32.        }

  33.    });

  34.    filter.setAuthenticationManager(authenticationManagerBean());

  35.    return filter;

  36. }

将自定义的CustomAuthenticationFilter类加入进来即可,接下来就可以使用JSON进行登录了,如下:

SpringSecurity登录使用JSON格式数据

好了,本文就先介绍到这里,有问题欢迎留言讨论。


往期精彩回顾

2019新年福利,新书免费送!
Docker教程
Redis教程
SpringCloud教程
Git教程
MongoDB教程
SpringBoot+Vue前后端分离开源项目-微人事
SpringBoot+Vue前后端分离开源项目-V部落

SpringSecurity登录使用JSON格式数据

本文分享自微信公众号 - 江南一点雨(a_javaboy)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

版权声明:程序员胖胖胖虎阿 发表于 2022年9月23日 上午7:00。
转载请注明:SpringSecurity登录使用JSON格式数据 | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...