Spring Security Ajax 登录

Posted

技术标签:

【中文标题】Spring Security Ajax 登录【英文标题】:Spring Security Ajax login 【发布时间】:2011-06-22 04:48:01 【问题描述】:

我已经在我的项目中实现了这个安全过程: Spring Security 3 - MVC Integration Tutorial (Part 2).

我的问题是我需要将其转换为基于 Ajax 的登录。 为了使这个 XML 适合仅将字符串/JSON 返回给客户端,我需要做什么? 我知道问题可能出在form-login 标签中。

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
                           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                           http://www.springframework.org/schema/security 
                           http://www.springframework.org/schema/security/spring-security-3.0.xsd">

    <!-- This is where we configure Spring-Security  -->
    <http auto-config="true" use-expressions="true" access-denied-page="/Management/auth/denied" >

        <intercept-url pattern="/Management/auth/login" access="permitAll"/>
        <intercept-url pattern="/Management/main/admin" access="hasRole('ROLE_ADMIN')"/>
        <intercept-url pattern="/Management/main/common" access="hasRole('ROLE_USER')"/>

        <form-login 
                login-page="/Management/auth/login" 
                authentication-failure-url="/Management/auth/login?error=true" 
                default-target-url="/Management/main/common"/>

        <logout 
                invalidate-session="true" 
                logout-success-url="/Management/auth/login" 
                logout-url="/Management/auth/logout"/>

    </http>

    <!-- Declare an authentication-manager to use a custom userDetailsService -->
    <authentication-manager>
            <authentication-provider user-service-ref="customUserDetailsService">
                    <password-encoder ref="passwordEncoder"/>
            </authentication-provider>
    </authentication-manager>

    <!-- Use a Md5 encoder since the user's passwords are stored as Md5 in the database -->
    <beans:bean class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" id="passwordEncoder"/>

    <!-- A custom service where Spring will retrieve users and their corresponding access levels  -->
    <beans:bean id="customUserDetailsService" class="com.affiliates.service.CustomUserDetailsService"/>

</beans:beans>

【问题讨论】:

【参考方案1】:

这是一篇旧帖子,但它仍然是“spring security ajax login”的最佳结果之一,所以我想我会分享我的解决方案。它遵循 Spring Security 标准并且设置起来非常简单,诀窍是在您的安全配置中有 2 个&lt;http&gt; 元素,一个用于 REST/Ajax,一个用于应用程序的其余部分(常规 html 页面)。 &lt;http&gt; 出现的顺序很重要,它必须从更具体的 URL 到更通用的 URL,就像 &lt;http&gt; 中的 &lt;url-intercept&gt; 元素一样。

第 1 步:设置两个独立的 &lt;http&gt;'s

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security" 
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:beans="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">

    <!-- a shared request cache is required for multiple http elements -->
    <beans:bean id="requestCache" class="org.springframework.security.web.savedrequest.HttpSessionRequestCache" />

    <!-- remove security from static resources to avoid going through the security filter chain -->
    <http pattern="/resources/**" security="none" />

    <!-- http config for REST services (AJAX interface) 
    =================================================== -->
    <http auto-config="true" use-expressions="true" pattern="/rest/**">
        <!-- login configuration 
            login-processing-url="/rest/security/login-processing" front-end AJAX requests for authentication POST to this URL
            login-page="/rest/security/login-page" means "authentication is required"
            authentication-failure-url="/rest/security/authentication-failure" means "authentication failed, bad credentials or other security exception"
            default-target-url="/rest/security/default-target" front-end AJAX requests are redirected here after success authentication
        -->
        <form-login 
            login-processing-url="/rest/security/login-processing" 
            login-page="/rest/security/login-page" 
            authentication-failure-url="/rest/security/authentication-failure" 
            default-target-url="/rest/security/default-target" 
            always-use-default-target="true" />
        <logout logout-url="/rest/security/logout-url" />

        <!-- REST services can be secured here, will respond with JSON instead of HTML -->
        <intercept-url pattern="/rest/calendar/**" access="hasRole('ROLE_USER')" />

        <!-- other REST intercept-urls go here -->

        <!-- end it with a catch all -->
        <intercept-url pattern="/rest/**" access="isAuthenticated()" />

        <!-- reference to the shared request cache -->
        <request-cache ref="requestCache"/>
    </http>

    <!-- http config for regular HTML pages
    =================================================== -->
    <http auto-config="true" use-expressions="true">
        <form-login 
            login-processing-url="/security/j_spring_security_check" 
            login-page="/login" 
            authentication-failure-url="/login?login_error=t" />
        <logout logout-url="/security/j_spring_security_logout" />

        <intercept-url pattern="/calendar/**" access="hasRole('ROLE_USER')" />

        <!-- other intercept-urls go here -->

        <!-- in my app's case, the HTML config ends with permitting all users and requiring HTTPS
             it is always a good idea to send sensitive information like passwords over HTTPS -->
        <intercept-url pattern="/**" access="permitAll" requires-channel="https" />

        <!-- reference to the shared request cache -->
        <request-cache ref="requestCache"/>
    </http>

    <!-- authentication manager and other configuration go below -->
</beans:beans>

第 2 步:REST 身份验证控制器

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import flexjson.JSONSerializer;

@Controller
@RequestMapping(value = "/rest/security")
public class RestAuthenticationController 

    public HttpHeaders getJsonHeaders() 
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type", "application/json");
        return headers;
    

    @RequestMapping(value="/login-page", method = RequestMethod.GET)
    public ResponseEntity<String> apiLoginPage() 
        return new ResponseEntity<String>(getJsonHeaders(), HttpStatus.UNAUTHORIZED);
    

    @RequestMapping(value="/authentication-failure", method = RequestMethod.GET)
    public ResponseEntity<String> apiAuthenticationFailure() 
        // return HttpStatus.OK to let your front-end know the request completed (no 401, it will cause you to go back to login again, loops, not good)
        // include some message code to indicate unsuccessful login
        return new ResponseEntity<String>("\"success\" : false, \"message\" : \"authentication-failure\"", getJsonHeaders(), HttpStatus.OK);
    

    @RequestMapping(value="/default-target", method = RequestMethod.GET)
    public ResponseEntity<String> apiDefaultTarget() 
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        // exclude/include whatever fields you need
        String userJson = new JSONSerializer().exclude("*.class", "*.password").serialize(authentication);
        return new ResponseEntity<String>(userJson, getJsonHeaders(), HttpStatus.OK);
    

第三步:提交AJAX表单并处理响应,需要jQuery的ajaxForm库

<form action="/rest/security/login-processing" method="POST">
...
</form>

$('form').ajaxForm(
    success: function(response, statusText, xhr, $form)  
        console.log(response);
        if(response == null || response.username == null) 
            alert("authentication failure");
         else 
            // response is JSON version of the Spring's Authentication
            alert("authentication success");
        
    ,
    error: function(response, statusText, error, $form)   
        if(response != null && response.message == "authentication-failure") 
            alert("authentication failure");
        
    
);

【讨论】:

您的解决方案看起来不错,但是响应“/rest/security/login-processing”的控制器方法在哪里定义? @MichaelDeKeyser - 它映射到 SpringSecurity 提供的 UsernamePasswordAuthenticationFilter,查看文档并搜索“UsernamePasswordAuthenticationFilter”和“login-processing-url”。 docs.spring.io/autorepo/docs/spring-security/3.2.0.RELEASE/… 我尝试通过 Spring Security 的 JavaConfig 进行此操作,最后不得不放弃并使用 XML ftw! @Boon - 看看这个例子:***.com/questions/4912485/spring-security-ajax-login/…【参考方案2】:

Spring 正在从基于 XML 的配置转向 Java @Configuration 类。以下是上述帖子 (Spring Security Ajax login) 中解释的设置的@Configuration 版本。第 2 步和第 3 步保持不变,用此代码替换第 1 步。顺序再次很重要,需要在更通用的定义之前加载更具体的定义,使用@Order(1)@Order(2) 来控制它。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;

@Configuration
@EnableWebSecurity
public class WebMvcSecurityConfiguration extends WebSecurityConfigurerAdapter 

    @Bean(name = "requestCache")
    public RequestCache getRequestCache() 
        return new HttpSessionRequestCache();
    

    @Configuration
    @Order(1)
    public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter 

            @Autowired private RequestCache requestCache;

            @Override
            protected void configure(HttpSecurity http) throws Exception 
                http
                    .regexMatcher("/rest.*")
                    .authorizeRequests()
                        .antMatchers("/rest/calendar/**")
                            .hasAuthority("ROLE_USER")
                        .antMatchers("/rest/**")
                            .permitAll()
                        .and()
                   .headers()
                        .xssProtection()
                        .and()
                   .logout()
                       .logoutUrl("/rest/security/logout-url")
                       .and()
                    .requestCache()
                        .requestCache(requestCache)
                        .and()
                    .formLogin()
                        .loginProcessingUrl("/rest/security/login-processing")
                        .loginPage("/rest/security/login-page")
                        .failureUrl("/rest/security/authentication-failure")
                        .defaultSuccessUrl("/rest/security/default-target", false)
                        .and()
                    .httpBasic();
           
    

    @Configuration
    @Order(2)
    public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter 

        @Autowired private RequestCache requestCache;

       @Override
       protected void configure(HttpSecurity http) throws Exception 
           http
                .authorizeRequests()
                    .regexMatchers("/calendar/.*")
                        .hasAuthority("ROLE_USER")
                    .regexMatchers("/.*")
                        .permitAll()
                   .and()
               .logout()
                   .logoutUrl("/security/j_spring_security_logout")
                   .and()
                .requestCache()
                    .requestCache(requestCache)
                    .and()
                .formLogin()
                    .loginProcessingUrl("/security/j_spring_security_check")
                    .loginPage("/login")
                    .failureUrl("/login?login_error=t" )
                    .and()
                .httpBasic();
       
    

    @Override
    public void configure(WebSecurity web) throws Exception 
        web
            .ignoring()
                .antMatchers("/resources/**")
                .antMatchers("/sitemap.xml");
    

【讨论】:

【参考方案3】:

这取决于您的 ajax-login 的实现。无论如何,我想你需要实现一个自定义过滤器。在 ExtJs 中使用 Spring Security 有两个很好的教程:

Integrating Spring Security 3 with Extjs

Integrating Spring Security with ExtJS Login Page

它应该与其他 Ajax 登录表单非常相似。

【讨论】:

【参考方案4】:

你可以使用HttpServletRequest.login(username,password)登录,就像:

@Controller
@RequestMapping("/login")
public class AjaxLoginController 

    @RequestMapping(method = RequestMethod.POST)
    @ResponseBody
    public String performLogin(
        @RequestParam("username") String username,
        @RequestParam("password") String password,
        HttpServletRequest request, HttpServletResponse response) 
    try 
        request.login(username,password);
        return "\"status\": true";
     catch (Exception e) 
        return "\"status\": false, \"error\": \"Bad Credentials\"";
    

【讨论】:

以上是关于Spring Security Ajax 登录的主要内容,如果未能解决你的问题,请参考以下文章

Spring Security Ajax 登录

Spring Security Ajax 登录

如何在 Grails 和 AngularJS 上使用 Spring Security 进行 Ajax 登录

spring security 在使用 AJAX 登录时显示 403 错误

Spring Security Ajax 登录在以下请求中成功验证 null

Ajax登陆,使用Spring Security缓存跳转到登陆前的链接