【问题标题】:How to make Spring Security accept JSON instead of form parameters?如何让 Spring Security 接受 JSON 而不是表单参数?
【发布时间】:2016-06-11 18:14:23
【问题描述】:

我正在尝试更改 JHipster,使其使用 JSON 对象而不是表单参数进行身份验证。我已经设法使其 JWT 身份验证机制发挥作用。现在我想为其他身份验证选项这样做。

是否有一种简单的方法可以更改 Spring Security 的默认安全配置以允许这样做?这是 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}

【问题讨论】:

    标签: java spring-boot spring-security jhipster


    【解决方案1】:

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

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

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

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

    真正解析JSON的Filter在最后。代码示例是一个类,所以可以直接复制过来。

    /** 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);
                }
            }
        }
    }
    

    【讨论】:

    • 我在 loginPage 方法启动时遇到异常,作为评论发布太长了
    • 引起:java.lang.IllegalStateException: securityBuilder 不能在 org.springframework.security.config.annotation.SecurityConfigurerAdapter.getBuilder(SecurityConfigurerAdapter.java:68) 处为空
    • @Andy 我目前不在我的开发站。如果您需要支持,请给我发送电子邮件或发布问题
    • @GregoryGolberg 我不再从事此工作,无法验证您的更改是否有效;所以我不会改变帖子。我认为将您的优化隐藏在外部链接后面是没有好处的。您想将其作为单独的答案发布吗?
    【解决方案2】:

    我做过这样的事情。解决方案并不难,但我成功地创建了一个主要基于 UserNamePasswordAuthenticationFilter 的自定义安全过滤器。

    其实你应该重写attemptAuthentication方法。仅仅覆盖obtainPassword 和obtainUsername 可能还不够,因为您想读取请求正文并且必须同时为这两个参数执行(如果您不创建一种多读取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>
    

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

    【讨论】:

    • 你能看看这个帖子吗:stackoverflow.com/questions/35687148/…
    • XML 配置完全损坏。请参考this question instead
    • @Younes 你确定吗?自此回复以来已经有一段时间了,但它来自一个经过测试和工作的示例。究竟是什么坏了?当然,也有一些元素没有配置,只是xml注释标签的位置,但是我写答案的时候肯定是有效的
    • @jlumietu 是的,非常确定。第 10 行的 &lt;beans:property&gt; 没有结束标签,第 1 行的 &lt;beans:bean&gt; 没有结束标签。为了正确,您必须将第 15 行的 &lt;/beans:bean&gt; 替换为 &lt;\beans:property&gt;
    • @Younes 你用你的设置填补了 cmets 的空白吗?等等,我发现一个错字...
    猜你喜欢
    • 2015-10-23
    • 2016-11-05
    • 2018-02-28
    • 2015-07-24
    • 2011-11-20
    • 1970-01-01
    • 2016-02-29
    • 2021-10-29
    • 2022-12-18
    相关资源
    最近更新 更多