Spring Session + REST + 自定义身份验证过滤器(从 JSON 读取凭据而不是查询参数)
Posted
技术标签:
【中文标题】Spring Session + REST + 自定义身份验证过滤器(从 JSON 读取凭据而不是查询参数)【英文标题】:Spring Session + REST + Custom Authentication Filter(read credentials from JSON rather query param) 【发布时间】:2016-01-18 22:59:16 【问题描述】:我正在尝试将我的其余服务身份验证从基本身份验证转换为基于表单的身份验证,如果我在 url 中将身份验证详细信息作为查询参数发送如下@ 987654322@
但是我并不热衷于将凭据作为查询参数发送,而是我更愿意将其作为 json 发送,因此我创建了自定义身份验证过滤器。当我添加自定义身份验证过滤器时,我的 spring 会话停止工作。我在响应标头中找不到 x-auth-token
字段。任何关于如何同时启用 spring 会话和自定义身份验证的建议/或者可能是处理凭据的 json 输入的更简单方法。
@Configuration
@EnableWebSecurity
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfig extends WebSecurityConfigurerAdapter
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private ObjectMapper objectMapper;
@Bean
public PasswordEncoder passwordEncoder()
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception
builder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
@Override
protected void configure(HttpSecurity http) throws Exception
http.authorizeRequests().antMatchers("/", "/register", "/index.html").permitAll().and().authorizeRequests()
.anyRequest().authenticated().and().requestCache().requestCache(new NullRequestCache()).and()
.formLogin().failureHandler(getRESTAuthenticationFailureHandler())
.successHandler(getRESTAuthenticationSuccessHandler()).usernameParameter("username")
.passwordParameter("password").and().exceptionHandling()
.authenticationEntryPoint(getRESTAuthenticationEntryPoint()).and()
//.addFilter(getAuthenticationFilter())
.csrf().disable();
@Bean
public HttpSessionStrategy httpSessionStrategy()
return new HeaderHttpSessionStrategy();
@Bean
public RESTAuthenticationEntryPoint getRESTAuthenticationEntryPoint()
return new RESTAuthenticationEntryPoint();
@Bean
public RESTAuthenticationSuccessHandler getRESTAuthenticationSuccessHandler()
return new RESTAuthenticationSuccessHandler();
@Bean
public RESTAuthenticationFailureHandler getRESTAuthenticationFailureHandler()
return new RESTAuthenticationFailureHandler();
@Bean
public AuthenticationFilter getAuthenticationFilter()
AuthenticationFilter filter = new AuthenticationFilter();
try
filter.setAuthenticationManager(this.authenticationManager());
catch (Exception e)
e.printStackTrace();
return filter;
public class RESTAuthenticationEntryPoint implements AuthenticationEntryPoint
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
public class RESTAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException
clearAuthenticationAttributes(request);
public class RESTAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException
super.onAuthenticationFailure(request, response, exception);
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter
private final Logger LOG = LoggerFactory.getLogger(AuthenticationFilter.class);
private boolean postOnly = true;
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 = null;
String password = null;
UserDetails userDetails = null;
if ("application/json".equals(request.getHeader("Content-Type")))
userDetails = getJson(request);
if (userDetails != null)
username = userDetails.getUsername();
else
username = obtainUsername(request);
if ("application/json".equals(request.getHeader("Content-Type")))
if (userDetails != null)
password = userDetails.getPassword();
else
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);
private UserDetails getJson(HttpServletRequest request)
try
final List<String> data = IOUtils.readLines(request.getReader());
final String jsonData = data.stream().collect(Collectors.joining());
LOG.info(jsonData);
UserDetails userDetails = objectMapper.readValue(jsonData, UserDetails.class);
return userDetails;
catch (IOException e)
LOG.error("Failed to read data ", e.getMessage(), e);
return null;
【问题讨论】:
不要只添加过滤器,在登录表单过滤器之前添加它(使用另一种 addFilter 方法)。 你的意思是说添加http.addFilter(getAuthenticationFilter()); http.authorizeRequests() 之前 不,因为这不会改变任何东西,您需要在过滤器链中指定要添加过滤器的位置,目前它是在所有其他 Spring Security 过滤器已经执行后添加的。您希望在表单登录之前(或之后)执行过滤器,而不是在链的末尾。我强烈建议阅读addFilter
方法的 javadoc。
这可能会有所帮助,***.com/questions/17268855/…
【参考方案1】:
正如建议的那样,我创建了自定义过滤器,它将 json 对象转换为请求参数并添加到 HttpServletRequest,但是是否有任何内置的 spring 安全自定义过滤器来做同样的工作?
@Configuration
@EnableWebSecurity
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfig extends WebSecurityConfigurerAdapter
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private ObjectMapper objectMapper;
@Bean
public FilterRegistrationBean contextFilterRegistrationBean()
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
CstFilter contextFilter = new CstFilter();
registrationBean.addUrlPatterns("/login");
registrationBean.setFilter(contextFilter);
registrationBean.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
return registrationBean;
@Bean
public PasswordEncoder passwordEncoder()
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception
builder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
@Override
protected void configure(HttpSecurity http) throws Exception
http
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(getRESTAuthenticationEntryPoint())
.and()
.formLogin()
.permitAll()
.loginProcessingUrl("/login")
.failureHandler(getRESTAuthenticationFailureHandler())
.successHandler(getRESTAuthenticationSuccessHandler())
.usernameParameter("username")
.passwordParameter("password")
.and()
.logout()
.permitAll()
.logoutSuccessHandler(getRESTLogoutSuccessHandler())
.and()
.authorizeRequests()
.antMatchers("/", "/index.html")
.permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.requestCache()
.requestCache(new NullRequestCache());
@Bean
public HttpSessionStrategy httpSessionStrategy()
return new HeaderHttpSessionStrategy();
@Bean
public RESTAuthenticationEntryPoint getRESTAuthenticationEntryPoint()
return new RESTAuthenticationEntryPoint();
@Bean
public RESTAuthenticationSuccessHandler getRESTAuthenticationSuccessHandler()
return new RESTAuthenticationSuccessHandler();
@Bean
public RESTAuthenticationFailureHandler getRESTAuthenticationFailureHandler()
return new RESTAuthenticationFailureHandler();
@Bean
public RESTLogoutSuccessHandler getRESTLogoutSuccessHandler()
return new RESTLogoutSuccessHandler();
public class JsonConvertFilter extends HttpServletRequestWrapper
private final Logger LOG = LoggerFactory.getLogger(JsonConvertFilter.class);
private UserDetails userDetails;
public JsonConvertFilter(HttpServletRequest request)
super((HttpServletRequest)request);
userDetails = getJson();
public String getParameter(String key)
if(userDetails!=null)
if("username".equals(key))
return userDetails.getUsername();
if("password".equals(key))
return userDetails.getPassword();
System.out.println("Called wrapper");
return super.getParameter(key);
private UserDetails getJson()
try
final List<String> data = IOUtils.readLines(super.getReader());
final String jsonData = data.stream().collect(Collectors.joining());
LOG.info(jsonData);
UserDetails userDetails = objectMapper.readValue(jsonData, UserDetails.class);
return userDetails;
catch (IOException e)
LOG.warn("Failed to read data ", e.getMessage(), e);
return null;
public class CstFilter implements Filter
@Override
public void destroy()
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
chain.doFilter(new JsonConvertFilter((HttpServletRequest)request), response);
@Override
public void init(FilterConfig arg0) throws ServletException
public class RESTLogoutSuccessHandler implements LogoutSuccessHandler
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException
String uri = request.getRequestURI();
if ("logout".equals(uri))
response.sendError(HttpServletResponse.SC_OK, "Succesfully Logged out");
public class RESTAuthenticationEntryPoint implements AuthenticationEntryPoint
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().print("Unauthorizated....");
public class RESTAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException
clearAuthenticationAttributes(request);
public class RESTAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException
String uri = request.getRequestURI();
if ("logout".equals(uri))
response.sendError(HttpServletResponse.SC_OK, "Succesfully Logged out");
else
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
"Authentication Failed: " + exception.getMessage());
对于任何想要使用 jquery ajax 进行测试的人。
/* Alerts the results */
$.ajax(
url : "login",
type : "POST",
async : false,
contentType: "application/json",
data : " \"username\":\""+username+"\", \"password\":\"" + password + "\"",
success : function(data, status, request)
alert("Success");
authtokenKey = request.getResponseHeader('x-auth-token');
,
error : function(xhr, status, error)
alert(error);
);
【讨论】:
以上是关于Spring Session + REST + 自定义身份验证过滤器(从 JSON 读取凭据而不是查询参数)的主要内容,如果未能解决你的问题,请参考以下文章
为 Spring 会话和自定义数据使用不同的 Redis 数据库
Spring Boot MongoDB REST - 自定义存储库方法
Spring中自定义Session管理,Spring Session源码解析