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 Data JPA + REST

Spring中自定义Session管理,Spring Session源码解析

java Spring rest-template自定义处理

Spring Boot REST API 版本控制的自定义标头方法