如何使 Spring Security 接受 JSON 而不是表单参数



我正在尝试更改JHipster,以便它使用JSON对象而不是表单参数进行身份验证。我已经设法为其 JWT 身份验证机制工作。现在我想为其他身份验证选项执行此操作。

有没有一种简单的方法来更改 Spring 安全性的默认安全配置以允许这样做?以下是JHipster现在使用的内容:

.and()
    .rememberMe()
    .rememberMeServices(rememberMeServices)
    .rememberMeParameter("remember-me")
    .key(env.getProperty("jhipster.security.rememberme.key"))
.and()
    .formLogin()
    .loginProcessingUrl("/api/authentication")
    .successHandler(ajaxAuthenticationSuccessHandler)
    .failureHandler(ajaxAuthenticationFailureHandler)
    .usernameParameter("j_username")
    .passwordParameter("j_password")
    .permitAll()

我想将以下内容作为 JSON 而不是表单参数发送:

{username: "admin", password: "admin", rememberMe: true}

我只是需要一些非常相似的东西,所以我写了它。

这使用 Spring Security 4.2, WebSecurityConfigurationAdapter。在那里,我没有使用...formLogin()...而是编写了一个自己的配置器,该配置器在可用时使用 JSON,如果没有,则默认为 Form(因为我需要这两个功能)。

我从org.springframework.security.config.annotation.web.configurers.FormLoginConfigurerorg.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter复制了所有需要存在的东西(但我不在乎),源代码和文档对我有很大帮助。

您可能还需要复制其他函数,但原则上应该这样做。

实际解析 JSON 的筛选器位于末尾。代码示例是一个类,因此可以直接复制。

/** WebSecurityConfig that allows authentication with a JSON Post request */
@Configuration
@EnableWebSecurity(debug = false)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    // resources go here
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // here you will need to configure paths, authentication provider, etc.
        // initially this was http.formLogin().loginPage...
        http.apply(new JSONLoginConfigurer<HttpSecurity>()
                  .loginPage("/authenticate")
                  .successHandler(new SimpleUrlAuthenticationSuccessHandler("/dashboard"))
                  .permitAll());
    }
    /** This is the a configurer that forces the JSONAuthenticationFilter.
     * based on org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer
     */
    private class JSONLoginConfigurer<H extends HttpSecurityBuilder<H>> extends
              AbstractAuthenticationFilterConfigurer<H, JSONLoginConfigurer<H>, UsernamePasswordAuthenticationFilter> {
        public JSONLoginConfigurer() {
            super(new JSONAuthenticationFilter(), null);
        }
        @Override
        public JSONLoginConfigurer<H> loginPage(String loginPage) {
            return super.loginPage(loginPage);
        }
        @Override
        protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
            return new AntPathRequestMatcher(loginProcessingUrl, "POST");
        }
    }
    /** This is the filter that actually handles the json
     */
    private class JSONAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
        protected String obtainPassword(JsonObject obj) {
            return obj.getString(getPasswordParameter());
        }
        protected String obtainUsername(JsonObject obj) {
            return obj.getString(getUsernameParameter());
        }
        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) 
                  throws AuthenticationException {
            if (!"application/json".equals(request.getContentType())) {
                // be aware that objtainPassword and Username in UsernamePasswordAuthenticationFilter
                // have a different method signature
                return super.attemptAuthentication(request, response);
            }
            try (BufferedReader reader = request.getReader()) {
                //json transformation using javax.json.Json
                JsonObject obj = Json.createReader(reader).readObject();
                String username = obtainUsername(obj);
                String password = obtainPassword(obj);
                UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                          username, password);
                return this.getAuthenticationManager().authenticate(authRequest);
            } catch (IOException ex) {
                throw new AuthenticationServiceException("Parsing Request failed", ex);
            }
        }
    }
}

我做过这样的事情。解决方案并不困难,但我创建了一个主要基于用户名密码身份验证过滤器的自定义安全过滤器。

实际上,您应该覆盖尝试身份验证方法。只是覆盖 gettainPassword 和 acquiretainUsername 可能不会被引用,因为你想读取请求正文,你必须同时对两个参数执行此操作(如果你没有创建一种多读 HttpServletRequest 包装器)

解决方案必须如下所示:

    public class JsonUserNameAuthenticationFilter extends UsernamePasswordAuthenticationFilter{
    //[...]
    public Authentication attemptAuthentication(HttpServletRequest request,
                HttpServletResponse response) throws AuthenticationException {
            if (postOnly && !request.getMethod().equals("POST")) {
                throw new AuthenticationServiceException(
                        "Authentication method not supported: " + request.getMethod());
            }
    UsernamePasswordAuthenticationToken authRequest =
            this.getUserNamePasswordAuthenticationToken(request);
            // Allow subclasses to set the "details" property
            setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
        //[...]
protected UserNamePasswordAuthenticationToken(HttpServletRequest request){
    // here read the request body and retrieve the params to create a UserNamePasswordAuthenticationToken. You may use jackson of whatever you like most
}
//[...]
}

然后,您必须对其进行配置。我都使用基于xml的配置进行这种复杂的配置,

    <beans:bean id="jsonUserNamePasswordAuthenticationFilter" 
                class="xxx.yyy.JsonUserNamePasswordAuthenticationFilter">
            <beans:property name="authenticationFailureHandler>
                <beans:bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
                    <!-- set the failure url to a controller request mapping returning failure response body.
                    it must be NOT secured -->
                </beans:bean>
            </beans:property>
            <beans:property name="authenticationManager" ref="mainAuthenticationManager" />
            <beans:property name="authenticationSuccessHandler" >
                <beans:bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
                    <!-- set the success url to a controller request mapping returning success response body.
                    it must be secured -->
                </beans:bean>
            </beans:property>
        </beans:bean>
        <security:authentication-manager id="mainAuthenticationManager">                
            <security:authentication-provider ref="yourProvider" />
        </security:authentication-manager>
<security:http pattern="/login-error" security="none"/>
    <security:http pattern="/logout" security="none"/>
<security:http pattern="/secured-pattern/**" auto-config='false' use-expressions="false"
        authentication-manager-ref="mainAuthenticationManager" 
        create-session="never" entry-point-ref="serviceAccessDeniedHandler">
        <security:intercept-url pattern="/secured-pattern/**" access="ROLE_REQUIRED" />
        <security:custom-filter ref="jsonUserNamePasswordAuthenticationFilter" 
            position="FORM_LOGIN_FILTER" />     
        <security:access-denied-handler ref="serviceAccessDeniedHandler"/>
        <security:csrf disabled="true"/>
    </security:http>

您可以创建一些额外的对象作为拒绝访问处理程序,但这是最简单的部分

最新更新