Spring security 自定义过滤器实现Json参数传递并兼容表单参数(实例代码)

依赖

 <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>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>

配置安全适配类

基本配置和配置自定义过滤器

package com.study.auth.config.core;

import com.study.auth.config.core.authentication.AccountAuthenticationProvider;
import com.study.auth.config.core.authentication.MailAuthenticationProvider;
import com.study.auth.config.core.authentication.PhoneAuthenticationProvider;
import com.study.auth.config.core.filter.CustomerUsernamePasswordAuthenticationFilter;
import com.study.auth.config.core.handler.CustomerAuthenticationFailureHandler;
import com.study.auth.config.core.handler.CustomerAuthenticationSuccessHandler;
import com.study.auth.config.core.handler.CustomerLogoutSuccessHandler;
import com.study.auth.config.core.observer.CustomerUserDetailsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * @Package: com.study.auth.config
 * @Description: <>
 * @Author: milla
 * @CreateDate: 2020/09/04 11:27
 * @UpdateUser: milla
 * @UpdateDate: 2020/09/04 11:27
 * @UpdateRemark: <>
 * @Version: 1.0
 */
@Slf4j
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  @Autowired
  private AccountAuthenticationProvider provider;
  @Autowired
  private MailAuthenticationProvider mailProvider;
  @Autowired
  private PhoneAuthenticationProvider phoneProvider;
  @Autowired
  private CustomerUserDetailsService userDetailsService;
  @Autowired
  private CustomerAuthenticationSuccessHandler successHandler;
  @Autowired
  private CustomerAuthenticationFailureHandler failureHandler;
  @Autowired
  private CustomerLogoutSuccessHandler logoutSuccessHandler;

  /**
   * 配置拦截器保护请求
   *
   * @param http
   * @throws Exception
   */
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    //配置HTTP基本身份验证//使用自定义过滤器-兼容json和表单登录
    http.addFilterBefore(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
        .httpBasic()
        .and().authorizeRequests()
        //表示访问 /setting 这个接口,需要具备 admin 这个角色
        .antMatchers("/setting").hasRole("admin")
        //表示剩余的其他接口,登录之后就能访问
        .anyRequest()
        .authenticated()
        .and()
        .formLogin()
        //定义登录页面,未登录时,访问一个需要登录之后才能访问的接口,会自动跳转到该页面
        .loginPage("/noToken")
        //登录处理接口-登录时候访问的接口地址
        .loginProcessingUrl("/account/login")
        //定义登录时,表单中用户名的 key,默认为 username
        .usernameParameter("username")
        //定义登录时,表单中用户密码的 key,默认为 password
        .passwordParameter("password")
//        //登录成功的处理器
//        .successHandler(successHandler)
//        //登录失败的处理器
//        .failureHandler(failureHandler)
        //允许所有用户访问
        .permitAll()
        .and()
        .logout()
        .logoutUrl("/logout")
        //登出成功的处理
        .logoutSuccessHandler(logoutSuccessHandler)
        .permitAll();
    //关闭csrf跨域攻击防御
    http.csrf().disable();
  }

  /**
   * 配置权限认证服务
   *
   * @param auth
   * @throws Exception
   */
  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    //权限校验-只要有一个认证通过即认为是通过的(有一个认证通过就跳出认证循环)-适用于多登录方式的系统
//    auth.authenticationProvider(provider);
//    auth.authenticationProvider(mailProvider);
//    auth.authenticationProvider(phoneProvider);
    //直接使用userDetailsService
    auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
  }

  /**
   * 配置Spring Security的Filter链
   *
   * @param web
   * @throws Exception
   */
  @Override
  public void configure(WebSecurity web) throws Exception {
    //忽略拦截的接口
    web.ignoring().antMatchers("/noToken");
  }

  /**
   * 指定验证manager
   *
   * @return
   * @throws Exception
   */
  @Override
  @Bean
  public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
  }

  /**
   * 注册自定义的UsernamePasswordAuthenticationFilter
   *
   * @return
   * @throws Exception
   */
  @Bean
  public AbstractAuthenticationProcessingFilter customAuthenticationFilter() throws Exception {
    AbstractAuthenticationProcessingFilter filter = new CustomerUsernamePasswordAuthenticationFilter();
    filter.setAuthenticationSuccessHandler(successHandler);
    filter.setAuthenticationFailureHandler(failureHandler);
    //过滤器拦截的url要和登录的url一致,否则不生效
    filter.setFilterProcessesUrl("/account/login");

    //这句很关键,重用WebSecurityConfigurerAdapter配置的AuthenticationManager,不然要自己组装AuthenticationManager
    filter.setAuthenticationManager(authenticationManagerBean());
    return filter;
  }
}

自定义过滤器

根据ContentType是否为json进行判断,如果是就从body中读取参数,进行解析,并生成权限实体,进行权限认证

否则直接使用UsernamePasswordAuthenticationFilter中的方法

package com.study.auth.config.core.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.study.auth.config.core.util.AuthenticationStoreUtil;
import com.study.auth.entity.bo.LoginBO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;

/**
 * @Package: com.study.auth.config.core.filter
 * @Description: <>
 * @Author: milla
 * @CreateDate: 2020/09/11 16:04
 * @UpdateUser: milla
 * @UpdateDate: 2020/09/11 16:04
 * @UpdateRemark: <>
 * @Version: 1.0
 */
@Slf4j
public class CustomerUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

  /**
   * 空字符串
   */
  private final String EMPTY = "";

  @Override
  public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

    //如果不是json使用自带的过滤器获取参数
    if (!request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE) && !request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
      String username = this.obtainUsername(request);
      String password = this.obtainPassword(request);
      storeAuthentication(username, password);
      Authentication authentication = super.attemptAuthentication(request, response);
      return authentication;
    }

    //如果是json请求使用取参数逻辑
    ObjectMapper mapper = new ObjectMapper();
    UsernamePasswordAuthenticationToken authRequest = null;
    try (InputStream is = request.getInputStream()) {
      LoginBO account = mapper.readValue(is, LoginBO.class);
      storeAuthentication(account.getUsername(), account.getPassword());
      authRequest = new UsernamePasswordAuthenticationToken(account.getUsername(), account.getPassword());
    } catch (IOException e) {
      log.error("验证失败:{}", e);
      authRequest = new UsernamePasswordAuthenticationToken(EMPTY, EMPTY);
    } finally {
      setDetails(request, authRequest);
      Authentication authenticate = this.getAuthenticationManager().authenticate(authRequest);
      return authenticate;
    }
  }

  /**
   * 保存用户名和密码
   *
   * @param username 帐号/邮箱/手机号
   * @param password 密码/验证码
   */
  private void storeAuthentication(String username, String password) {
    AuthenticationStoreUtil.setUsername(username);
    AuthenticationStoreUtil.setPassword(password);
  }
}

其中会有body中的传参问题,所以使用ThreadLocal传递参数

PS:枚举类具备线程安全性

package com.study.auth.config.core.util;

/**
 * @Package: com.study.auth.config.core.util
 * @Description: <使用枚举可以保证线程安全>
 * @Author: milla
 * @CreateDate: 2020/09/11 17:48
 * @UpdateUser: milla
 * @UpdateDate: 2020/09/11 17:48
 * @UpdateRemark: <>
 * @Version: 1.0
 */
public enum AuthenticationStoreUtil {
  AUTHENTICATION;
  /**
   * 登录认证之后的token
   */
  private final ThreadLocal<String> tokenStore = new ThreadLocal<>();
  /**
   * 需要验证用户名
   */
  private final ThreadLocal<String> usernameStore = new ThreadLocal<>();
  /**
   * 需要验证的密码
   */
  private final ThreadLocal<String> passwordStore = new ThreadLocal<>();

  public static String getUsername() {
    return AUTHENTICATION.usernameStore.get();
  }

  public static void setUsername(String username) {
    AUTHENTICATION.usernameStore.set(username);
  }

  public static String getPassword() {
    return AUTHENTICATION.passwordStore.get();
  }

  public static void setPassword(String password) {
    AUTHENTICATION.passwordStore.set(password);
  }

  public static String getToken() {
    return AUTHENTICATION.tokenStore.get();
  }

  public static void setToken(String token) {
    AUTHENTICATION.tokenStore.set(token);
  }

  public static void clear() {
    AUTHENTICATION.tokenStore.remove();
    AUTHENTICATION.passwordStore.remove();
    AUTHENTICATION.usernameStore.remove();
  }
}

实现UserDetailsService接口

package com.study.auth.config.core.observer;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

/**
 * @Package: com.study.auth.config.core
 * @Description: <自定义用户处理类>
 * @Author: milla
 * @CreateDate: 2020/09/04 13:53
 * @UpdateUser: milla
 * @UpdateDate: 2020/09/04 13:53
 * @UpdateRemark: <>
 * @Version: 1.0
 */
@Slf4j
@Component
public class CustomerUserDetailsService implements UserDetailsService {

  @Autowired
  private PasswordEncoder passwordEncoder;

  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    //测试直接使用固定账户代替
    return User.withUsername("admin").password(passwordEncoder.encode("admin")).roles("admin", "user").build();
  }
}

登录成功类

package com.study.auth.config.core.handler;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Package: com.study.auth.config.core.handler
 * @Description: <登录成功处理类>
 * @Author: milla
 * @CreateDate: 2020/09/08 17:39
 * @UpdateUser: milla
 * @UpdateDate: 2020/09/08 17:39
 * @UpdateRemark: <>
 * @Version: 1.0
 */
@Component
public class CustomerAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
  @Override
  public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
    HttpServletResponseUtil.loginSuccess(response);
  }
}

登录失败

package com.study.auth.config.core.handler;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Package: com.study.auth.config.core.handler
 * @Description: <登录失败操作类>
 * @Author: milla
 * @CreateDate: 2020/09/08 17:42
 * @UpdateUser: milla
 * @UpdateDate: 2020/09/08 17:42
 * @UpdateRemark: <>
 * @Version: 1.0
 */
@Component
public class CustomerAuthenticationFailureHandler implements AuthenticationFailureHandler {
  @Override
  public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
    HttpServletResponseUtil.loginFailure(response, exception);
  }
}

登出成功类

package com.study.auth.config.core.handler;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Package: com.study.auth.config.core.handler
 * @Description: <登出成功>
 * @Author: milla
 * @CreateDate: 2020/09/08 17:44
 * @UpdateUser: milla
 * @UpdateDate: 2020/09/08 17:44
 * @UpdateRemark: <>
 * @Version: 1.0
 */
@Component
public class CustomerLogoutSuccessHandler implements LogoutSuccessHandler {
  @Override
  public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
    HttpServletResponseUtil.logoutSuccess(response);
  }
}

返回值工具类

package com.study.auth.config.core.handler;

import com.alibaba.fastjson.JSON;
import com.study.auth.comm.ResponseData;
import com.study.auth.constant.CommonConstant;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @Package: com.study.auth.config.core.handler
 * @Description: <>
 * @Author: milla
 * @CreateDate: 2020/09/08 17:45
 * @UpdateUser: milla
 * @UpdateDate: 2020/09/08 17:45
 * @UpdateRemark: <>
 * @Version: 1.0
 */
public final class HttpServletResponseUtil {

  public static void loginSuccess(HttpServletResponse resp) throws IOException {
    ResponseData success = ResponseData.success();
    success.setMsg("login success");
    response(resp, success);
  }

  public static void logoutSuccess(HttpServletResponse resp) throws IOException {
    ResponseData success = ResponseData.success();
    success.setMsg("logout success");
    response(resp, success);
  }

  public static void loginFailure(HttpServletResponse resp, AuthenticationException exception) throws IOException {
    ResponseData failure = ResponseData.error(CommonConstant.EX_RUN_TIME_EXCEPTION, exception.getMessage());
    response(resp, failure);
  }

  private static void response(HttpServletResponse resp, ResponseData data) throws IOException {
    //直接输出的时候还是需要使用UTF-8字符集
    resp.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
    PrintWriter out = resp.getWriter();
    out.write(JSON.toJSONString(data));
    out.flush();
  }
}

其他对象见Controller 层返回值的公共包装类-避免每次都包装一次返回-InitializingBean增强

至此,就可以传递Json参数了

到此这篇关于Spring security 自定义过滤器实现Json参数传递并兼容表单参数的文章就介绍到这了,更多相关Spring security 自定义过滤器内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

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

    在使用SpringSecurity中,大伙都知道默认的登录数据是通过key/value的形式来传递的,默认情况下不支持JSON格式的登录数据,如果有这种需求,就需要自己来解决,本文主要和小伙伴来聊聊这个话题. 基本登录方案 在说如何使用JSON登录之前,我们还是先来看看基本的登录吧,本文为了简单,SpringSecurity在使用中就不连接数据库了,直接在内存中配置用户名和密码,具体操作步骤如下: 创建Spring Boot工程 首先创建SpringBoot工程,添加SpringSecurity

  • SpringSecurity学习之自定义过滤器的实现代码

    我们系统中的认证场景通常比较复杂,比如说用户被锁定无法登录,限制登录IP等.而SpringSecuriy最基本的是基于用户与密码的形式进行认证,由此可知它的一套验证规范根本无法满足业务需要,因此扩展势在必行.那么我们可以考虑自己定义filter添加至SpringSecurity的过滤器栈当中,来实现我们自己的验证需要. 本例中,基于前篇的数据库的Student表来模拟一个简单的例子:当Student的jointime在当天之后,那么才允许登录 一.创建自己定义的Filter 我们先在web包下创

  • 详解Spring Security如何配置JSON登录

    spring security用了也有一段时间了,弄过异步和多数据源登录,也看过一点源码,最近弄rest,然后顺便搭oauth2,前端用json来登录,没想到spring security默认居然不能获取request中的json数据,谷歌一波后只在stackoverflow找到一个回答比较靠谱,还是得要重写filter,于是在这里填一波坑. 准备工作 基本的spring security配置就不说了,网上一堆例子,只要弄到普通的表单登录和自定义UserDetailsService就可以.因为需

  • Spring security 自定义过滤器实现Json参数传递并兼容表单参数(实例代码)

    依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId&g

  • Bootbox将后台JSON数据填充Form表单的实例代码

    序言: 刚结束公司的三个月试用期,意味着我即将正式步入社会成为广大从事IT行业的一员.作为一个编程小白,无论从技术层面还是知识层面都是比较薄弱的,想要成为一个优秀的程序员不断的学习与探索是不可避免的.我相信一切的付出与收获是成正比的!Fighting! 这几天在做公司的实际项目的时候,需要实现选中Bootstrap table中的任意一行数据点击编辑按钮弹出一个模态框以表单的形式对该行数据进行编辑.获取表格行的数据是比较方便的,具体可以查找Bootstrap table参考文档,具体地址可以直接

  • spring security自定义认证登录的全过程记录

    spring security使用分类: 如何使用spring security,相信百度过的都知道,总共有四种用法,从简到深为: 1.不用数据库,全部数据写在配置文件,这个也是官方文档里面的demo: 2.使用数据库,根据spring security默认实现代码设计数据库,也就是说数据库已经固定了,这种方法不灵活,而且那个数据库设计得很简陋,实用性差: 3.spring security和Acegi不同,它不能修改默认filter了,但支持插入filter,所以根据这个,我们可以插入自己的f

  • Spring Security CsrfFilter过滤器用法实例

    这篇文章主要介绍了Spring Security CsrfFilter过滤器用法实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 spring security框架提供的默认登录页面,会有一个name属性值为_csrf的隐藏域: 这是框架在用户访问登录页面之前就生成的,保存在内存中,当用户提交表单的时候会跟着一起提交:_csrf_formdata 然后会经过spring security框架resources目录下配置文件spring-sec

  • Spring Security 自定义短信登录认证的实现

    自定义登录filter 上篇文章我们说到,对于用户的登录,security通过定义一个filter拦截login路径来实现的,所以我们要实现自定义登录,需要自己定义一个filter,继承AbstractAuthenticationProcessingFilter,从request中提取到手机号和验证码,然后提交给AuthenticationManager: public class SmsAuthenticationFilter extends AbstractAuthenticationPro

  • Spring Security基于过滤器实现图形验证码功能

    目录 前言 一. 验证码简介 二. 基于过滤器实现图形验证码 1. 实现概述 2. 创建新模块 3. 添加依赖包 4. 创建Producer对象 5. 创建生成验证码的接口 6. 自定义异常 7. 创建拦截验证码的过滤器 8. 编写SecurityConfig 9. 编写测试页面 10. 代码结构 11. 启动项目测试 前言 在前两个章节中,一一哥 带大家学习了Spring Security内部关于认证授权的核心API,以及认证授权的执行流程和底层原理.掌握了这些之后,对于Spring Secu

  • Spring Security自定义认证逻辑实例详解

    目录 前言 分析问题 自定义 Authentication 自定义 Filter 自定义 Provider 自定义认证成功/失败后的 Handler 配置自定义认证的逻辑 测试 总结 前言 这篇文章的内容基于对Spring Security 认证流程的理解,如果你不了解,可以读一下这篇文章:Spring Security 认证流程 . 分析问题 以下是 Spring Security 内置的用户名/密码认证的流程图,我们可以从这里入手: 根据上图,我们可以照猫画虎,自定义一个认证流程,比如手机短

  • Spring Security自定义登录页面认证过程常用配置

    目录 一.自定义登录页面 1.编写登录页面 2.修改配置类 3.编写控制器 二. 认证过程其他常用配置 1.失败跳转 1.1编写页面 1.2修改表单配置 1.3添加控制器方法 1.4设置fail.html不需要认证 2.设置请求账户和密码的参数名 2.1源码简介 2.2修改配置 2.3修改页面 3.自定义登录成功处理器 3.1源码分析 3.2代码实现 4.自定义登录失败处理器 4.1源码分析 4.2代码实现 一.自定义登录页面 虽然Spring Security给我们提供了登录页面,但是对于实际

  • Spring Security 自定义资源服务器实践过程

    目录 前言 最小化配置 安装资源服务器 配置资源服务器 配置客户端 整流程体验 总结 前言 在前面我们使用最小化配置的方式搭建了自己的授权服务器,现在我们依旧用最小化的方式配置自己的资源服务器.资源服务器负责scope的鉴权.authorities的鉴权.基于用户角色的鉴权等. 最小化配置 安装资源服务器 1. 新建一个Spring Boot项目,命名为spring-security-resource-server2.引入pom.xml依赖 <dependency> <groupId&g

  • spring security自定义决策管理器

    首先介绍下Spring的决策管理器,其接口为AccessDecisionManager,抽象类为AbstractAccessDecisionManager.而我们要自定义决策管理器的话一般是继承抽象类而不去直接实现接口. 在Spring中引入了投票器(AccessDecisionVoter)的概念,有无权限访问的最终觉得权是由投票器来决定的,最常见的投票器为RoleVoter,在RoleVoter中定义了权限的前缀,先看下Spring在RoleVoter中是怎么处理授权的. public int

随机推荐