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

作者:江南一点雨 时间:2021-09-10 21:40:40 

在使用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接口,结果如下:

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

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

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

登录成功后,再去访问/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进行登录了,如下:

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

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

来源:https://segmentfault.com/a/1190000018157525

标签:SpringSecurity,登录,JSON
0
投稿

猜你喜欢

  • 带你一文了解C#中的Expression

    2023-04-20 04:37:57
  • spring boot实战教程之shiro session过期时间详解

    2023-01-05 01:37:47
  • java 如何实现正确的删除集合中的元素

    2022-08-03 17:44:31
  • C#实现读取txt文件生成Word文档

    2022-08-06 19:03:22
  • Jmeter生成UUID作为唯一标识符过程图解

    2022-09-17 03:35:36
  • 最好的8个Java RESTful框架

    2023-02-07 07:49:44
  • Android绘制验证码的实例代码

    2023-10-30 13:57:15
  • springboot如何去除debug日志

    2023-02-14 08:43:37
  • 详解在Spring Boot框架下使用WebSocket实现消息推送

    2023-03-08 01:32:05
  • C# Distinct和重写IEqualityComparer时要知道的二三事

    2023-07-11 15:41:20
  • Unity实现物体左右移动效果

    2021-12-17 15:52:31
  • 轻量级声明式的Http库——Feign的独立使用

    2022-06-05 04:36:19
  • spring中的BeanFactory与FactoryBean的讲解

    2023-03-13 11:18:09
  • Android 内存泄漏的几种可能总结

    2022-02-27 21:43:26
  • c#读写ini配置文件示例

    2023-09-29 16:31:36
  • Unity3D实现物体旋转缩放移动效果

    2023-07-17 22:11:58
  • 详解C++ bitset用法

    2022-10-30 08:57:16
  • Android创建服务之started service详细介绍

    2022-12-09 15:10:29
  • 详解Alibaba Java诊断工具Arthas查看Dubbo动态代理类

    2021-08-04 03:16:28
  • Java多线程 Producer and Consumer设计模式

    2022-08-30 07:13:24
  • asp之家 软件编程 m.aspxhome.com