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
投稿

猜你喜欢

  • SpringBoot参数校验Validator框架详解

    2023-09-22 07:08:40
  • springboot常用注释的讲解

    2023-11-03 02:53:15
  • 浅谈@RequestMapping注解的注意点

    2023-07-11 02:12:39
  • 搭建MyBatis-Plus框架并进行数据库增删改查功能

    2023-11-09 04:33:43
  • 详解mysql插入数据后返回自增ID的七种方法

    2023-07-01 21:32:20
  • Java Vector和ArrayList的异同分析及实例讲解

    2023-12-03 17:27:10
  • Java实现对两个List快速去重并排序操作示例

    2023-05-02 03:40:14
  • Java8通过Function获取字段名的步骤

    2022-07-14 14:06:23
  • 详解JAVA类加载机制(推荐)

    2021-08-10 04:43:10
  • Java经典面试题最全汇总208道(五)

    2023-11-10 07:06:46
  • Spring Bean生命周期之BeanDefinition的合并过程详解

    2023-11-29 02:50:35
  • 详解Spring Boot Profiles 配置和使用

    2021-10-05 22:54:57
  • IDEA设置maven修改settings.xml配置文件无法加载仓库的解决方案

    2023-08-23 22:39:35
  • Java中值类型和引用类型详解

    2023-11-29 00:44:51
  • spring cloud gateway请求跨域问题解决方案

    2021-11-05 11:19:25
  • C语言时间函数之strftime()详解

    2023-06-26 02:42:32
  • 在Framework 4.0中:找出新增的方法与新增的类(一)

    2021-07-07 05:26:07
  • activemq整合springboot使用方法(个人微信小程序用)

    2023-07-08 22:29:55
  • SpringBoot集成MyBatis的分页插件PageHelper实例代码

    2023-09-11 21:54:33
  • Java编程实现判断网上邻居文件是否存在的方法

    2022-12-27 09:49:46
  • asp之家 软件编程 m.aspxhome.com