security 登录改为 post json 登录 多次获取body 问题

原因: security filter 和 自定义 filter 执行顺序问题,框架会优先执行 17个 security filter ,再执行 自定义的 filter

,所以直接多次获取body 是不行的,需要把自定义的 filter 放到 security filter 中

重写登录过滤器

重写 UsernamePasswordAuthenticationFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import com.stark37125.core.util.RequestUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
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;

/**
* 支持json用户名密码身份验证过滤器
*
* @author shuzhuo
*/
@Slf4j
public class JsonUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

public JsonUsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (!RequestUtils.isPost(request)) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}

if (StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) {
UsernamePasswordAuthenticationToken authRequest;
LoginUserParam loginUserParam = LoginUserParamHolder.LOGIN_USER_PARAM_LOCAL.get();
authRequest = new UsernamePasswordAuthenticationToken(loginUserParam.getUsername(), loginUserParam.getPassword());
setDetails(request, authRequest);
LoginUserParamHolder.LOGIN_USER_PARAM_LOCAL.remove();
return this.getAuthenticationManager().authenticate(authRequest);
} else {
// 其他类型的请求依旧走原来的处理方式
return super.attemptAuthentication(request, response);
}
}

}

WebSecurityConfig配置

1
2
3
4
// 验证码
.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
// 兼容 post json 登录
.addFilterBefore(jsonUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)

JsonUsernamePasswordAuthenticationFilter

1
2
3
4
5
6
7
8
@Bean
public JsonUsernamePasswordAuthenticationFilter jsonUsernamePasswordAuthenticationFilter() throws Exception {
JsonUsernamePasswordAuthenticationFilter filter = new JsonUsernamePasswordAuthenticationFilter(authenticationConfiguration.getAuthenticationManager());
filter.setAuthenticationSuccessHandler(successHandler);
filter.setAuthenticationFailureHandler(failHandler);
filter.setFilterProcessesUrl("/system/login");
return filter;
}

方法一 使用 ThreadLocal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import com.stark37125.core.util.RequestUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
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;

/**
* 支持json用户名密码身份验证过滤器
*
* @author shuzhuo
*/
@Slf4j
public class JsonUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

public JsonUsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (!RequestUtils.isPost(request)) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}

if (StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) {
UsernamePasswordAuthenticationToken authRequest;
LoginUserParam loginUserParam = LoginUserParamHolder.LOGIN_USER_PARAM_LOCAL.get();
authRequest = new UsernamePasswordAuthenticationToken(loginUserParam.getUsername(), loginUserParam.getPassword());
setDetails(request, authRequest);
LoginUserParamHolder.LOGIN_USER_PARAM_LOCAL.remove();
return this.getAuthenticationManager().authenticate(authRequest);
} else {
// 其他类型的请求依旧走原来的处理方式
return super.attemptAuthentication(request, response);
}
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import com.alibaba.fastjson2.JSON;
import com.anji.captcha.model.common.ResponseModel;
import com.anji.captcha.model.vo.CaptchaVO;
import com.anji.captcha.service.CaptchaService;
import com.stark37125.core.base.exception.LoginFailException;
import com.stark37125.core.config.security.LoginUserParam;
import com.stark37125.core.config.security.LoginUserParamHolder;
import com.stark37125.core.util.HttpHelper;
import com.stark37125.core.util.RequestUtils;
import com.stark37125.core.util.SpringContextHolder;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.filter.OncePerRequestFilter;

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

/**
* 验证码过滤器
* @author shuzhuo
*/
@Component
public class ValidateCodeFilter extends OncePerRequestFilter {

private static final String CAPTCHA_VERIFICATION = "captchaVerification";

@Override
protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException {
CaptchaService captchaService = SpringContextHolder.getBean(CaptchaService.class);
if (StringUtils.equals("/system/login", request.getRequestURI()) && RequestUtils.isPost(request)) {
String captchaVerification = getCaptchaVerification(request);
if (StringUtils.isBlank(captchaVerification)) {
throw new LoginFailException("验证码参数不能为空");
}
CaptchaVO captchaVO = new CaptchaVO();
captchaVO.setCaptchaVerification(captchaVerification);
ResponseModel responseModel = captchaService.verification(captchaVO);
if (!StringUtils.equals("0000", responseModel.getRepCode())) {
throw new LoginFailException(responseModel.getRepMsg());
}
}
filterChain.doFilter(request, response);
}

@SneakyThrows
public String getCaptchaVerification(HttpServletRequest request) {
if (StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) {
String bodyString = HttpHelper.getBodyString(request);
if (StringUtils.isBlank(bodyString)) {
throw new LoginFailException("参数不能为空");
}
LoginUserParam loginUserParam = JSON.parseObject(bodyString, LoginUserParam.class);
LoginUserParamHolder.LOGIN_USER_PARAM_LOCAL.set(loginUserParam);
return loginUserParam.getCaptchaVerification();
}
return ServletRequestUtils.getStringParameter(request, CAPTCHA_VERIFICATION);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import lombok.Data;

/**
* 登录用户参数
*
* @author shuzhuo
*/
@Data
public class LoginUserParam {

private String username;

private String password;

private String captchaVerification;
}

1
2
3
4
5
6
7
8
9
10
/**
* 登录用户参数 Holder
*
* @author shuzhuo
*/
public class LoginUserParamHolder {

public static final ThreadLocal<LoginUserParam> LOGIN_USER_PARAM_LOCAL = new ThreadLocal<>();
}

方法二 WebSecurityConfig 配置 RepeatableFilter 即可在filter 多次获取body

1
2
3
4
.addFilterBefore(new RepeatableFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
//添加JWT过滤器 除/system/login其它请求都需经过此过滤器
.addFilterBefore(jsonUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)