SpringSecurity登录使用JSON格式数据的方法

所属分类: 软件编程 / java 阅读数: 38
收藏 0 赞 0 分享

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

基本登录方案

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

创建Spring Boot工程

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

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

添加Security配置

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

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  @Bean
  PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }
  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication().withUser("zhangsan").password("$2a$10$2O4EwLrrFPEboTfDOtC0F.RpUMk.3q3KvBHRx7XXKUMLBGjOOBs8q").roles("user");
  }

  @Override
  public void configure(WebSecurity web) throws Exception {
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .anyRequest().authenticated()
        .and()
        .formLogin()
        .loginProcessingUrl("/doLogin")
        .successHandler(new AuthenticationSuccessHandler() {
          @Override
          public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
            RespBean ok = RespBean.ok("登录成功!",authentication.getPrincipal());
            resp.setContentType("application/json;charset=utf-8");
            PrintWriter out = resp.getWriter();
            out.write(new ObjectMapper().writeValueAsString(ok));
            out.flush();
            out.close();
          }
        })
        .failureHandler(new AuthenticationFailureHandler() {
          @Override
          public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
            RespBean error = RespBean.error("登录失败");
            resp.setContentType("application/json;charset=utf-8");
            PrintWriter out = resp.getWriter();
            out.write(new ObjectMapper().writeValueAsString(error));
            out.flush();
            out.close();
          }
        })
        .loginPage("/login")
        .permitAll()
        .and()
        .logout()
        .logoutUrl("/logout")
        .logoutSuccessHandler(new LogoutSuccessHandler() {
          @Override
          public void onLogoutSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
            RespBean ok = RespBean.ok("注销成功!");
            resp.setContentType("application/json;charset=utf-8");
            PrintWriter out = resp.getWriter();
            out.write(new ObjectMapper().writeValueAsString(ok));
            out.flush();
            out.close();
          }
        })
        .permitAll()
        .and()
        .csrf()
        .disable()
        .exceptionHandling()
        .accessDeniedHandler(new AccessDeniedHandler() {
          @Override
          public void handle(HttpServletRequest req, HttpServletResponse resp, AccessDeniedException e) throws IOException, ServletException {
            RespBean error = RespBean.error("权限不足,访问失败");
            resp.setStatus(403);
            resp.setContentType("application/json;charset=utf-8");
            PrintWriter out = resp.getWriter();
            out.write(new ObjectMapper().writeValueAsString(error));
            out.flush();
            out.close();
          }
        });

  }
}

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

@RestController
@ResponseBody
public class LoginController {
  @GetMapping("/login")
  public RespBean login() {
    return RespBean.error("尚未登录,请登录");
  }
  @GetMapping("/hello")
  public String hello() {
    return "hello";
  }
}

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

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

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

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

使用JSON登录

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

public class UsernamePasswordAuthenticationFilter extends
    AbstractAuthenticationProcessingFilter {
  public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
  public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";

  private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
  private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
  private boolean postOnly = true;
  public UsernamePasswordAuthenticationFilter() {
    super(new AntPathRequestMatcher("/login", "POST"));
  }

  public Authentication attemptAuthentication(HttpServletRequest request,
      HttpServletResponse response) throws AuthenticationException {
    if (postOnly && !request.getMethod().equals("POST")) {
      throw new AuthenticationServiceException(
          "Authentication method not supported: " + request.getMethod());
    }

    String username = obtainUsername(request);
    String password = obtainPassword(request);

    if (username == null) {
      username = "";
    }

    if (password == null) {
      password = "";
    }

    username = username.trim();

    UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
        username, password);

    // Allow subclasses to set the "details" property
    setDetails(request, authRequest);

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

  protected String obtainPassword(HttpServletRequest request) {
    return request.getParameter(passwordParameter);
  }

  protected String obtainUsername(HttpServletRequest request) {
    return request.getParameter(usernameParameter);
  }
  //...
  //...
}

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

public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
  @Override
  public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    if (request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)
        || request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
      ObjectMapper mapper = new ObjectMapper();
      UsernamePasswordAuthenticationToken authRequest = null;
      try (InputStream is = request.getInputStream()) {
        Map<String,String> authenticationBean = mapper.readValue(is, Map.class);
        authRequest = new UsernamePasswordAuthenticationToken(
            authenticationBean.get("username"), authenticationBean.get("password"));
      } catch (IOException e) {
        e.printStackTrace();
        authRequest = new UsernamePasswordAuthenticationToken(
            "", "");
      } finally {
        setDetails(request, authRequest);
        return this.getAuthenticationManager().authenticate(authRequest);
      }
    }
    else {
      return super.attemptAuthentication(request, response);
    }
  }
}

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

@Override
protected void configure(HttpSecurity http) throws Exception {
  http.authorizeRequests().anyRequest().authenticated()
      .and()
      .formLogin()
      .and().csrf().disable();
  http.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
@Bean
CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
  CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
  filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
      resp.setContentType("application/json;charset=utf-8");
      PrintWriter out = resp.getWriter();
      RespBean respBean = RespBean.ok("登录成功!");
      out.write(new ObjectMapper().writeValueAsString(respBean));
      out.flush();
      out.close();
    }
  });
  filter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
    @Override
    public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
      resp.setContentType("application/json;charset=utf-8");
      PrintWriter out = resp.getWriter();
      RespBean respBean = RespBean.error("登录失败!");
      out.write(new ObjectMapper().writeValueAsString(respBean));
      out.flush();
      out.close();
    }
  });
  filter.setAuthenticationManager(authenticationManagerBean());
  return filter;
}

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

好了,本文就先介绍到这里,有问题欢迎留言讨论。 希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

更多精彩内容其他人还在看

java实现背单词程序

这篇文章主要为大家详细介绍了java实现背单词程序,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

java实现单词查询小程序

这篇文章主要为大家详细介绍了java实现单词查询小程序,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

Java程序开发环境配置图文教程

这篇文章主要为大家详细介绍了Java程序开发环境配置图文教程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

详解ssh框架原理及流程

在本文中小编给大家整理的是关于ssh框架原理及流程的相关知识点内容,有此需要的朋友们可以学习下。
收藏 0 赞 0 分享

Java实现弹窗效果的基本操作

这篇文章主要为大家详细介绍了Java实现弹窗效果的基本操作,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

详解springmvc常用5种注解

在本篇里我们给大家总结了关于springmvc常用5种注解相关知识点以及实例代码,需要的朋友们参考下。
收藏 0 赞 0 分享

Java实现弹窗效果的基本操作(2)

这篇文章主要为大家详细介绍了Java实现弹窗效果的基本操作第二篇,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

Spring Boot假死诊断实战记录

这篇文章主要给大家介绍了关于Spring Boot假死诊断的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Spring Boot具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
收藏 0 赞 0 分享

Java计时新姿势StopWatch详解

这篇文章主要介绍了Java计时新姿势StopWatch,最近公司来了个大佬,从他那里学到不少东西,其中一个就是计时的新姿势「StopWatch」,需要的朋友可以参考下
收藏 0 赞 0 分享

java实现点击按钮弹出新窗体功能

这篇文章主要为大家详细介绍了java实现点击按钮弹出新窗体功能,旧窗体不进行操作,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享
查看更多