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参数了 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值