Spring Security的自定义登录如何工作

时间:2016-10-14 01:27:07

标签: java authentication login spring-security logout

我正在尝试通过Spring Security。我必须实现自定义登录表单,所以我需要很好地理解我的配置意味着什么。

弹簧security.xml文件

<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-4.0.xsd
    http://www.springframework.org/schema/security
    http://www.springframework.org/schema/security/spring-security-4.0.xsd">

    <http auto-config="true">
        <intercept-url pattern="/user**" access="isAuthenticated()" />
        <form-login authentication-failure-url="/login" login-page="/login"
            login-processing-url="/login" default-target-url="/user" />
        <logout invalidate-session="true" logout-success-url="/index"
            logout-url="/logout" />
    </http>

    <authentication-manager id="custom-auth">
        <authentication-provider>
            <user-service>
                <user name="my_username" password="my_password"
                    authorities="ROLE_USER" />
            </user-service>
        </authentication-provider>
    </authentication-manager>

的LoginController

@Controller
public class LoginController {
    [....]

    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public ModelAndView doLogin() {
        System.out.println("***LOGIN_POST***");
        return new ModelAndView("users/home");
    }

    @RequestMapping(value = "/logout", method = RequestMethod.POST)
    public ModelAndView doLogout() {
        System.out.println("***LOGOUT_POST***");
        return new ModelAndView("index");
    }
}

我知道我可以使用RequestMethod.GET映射/ login URL,但是当我在表单提交后尝试拦截POST时它不起作用。

  1. 我相信,但需要确认,那是因为安全正在做 幕后的东西:从中获取用户名和密码值 发布的表单并将其与身份验证中的表单进行比较 provider:如果匹配,则显示default-target-url,否则显示user 必须重复登录。是不是?
  2. 然后我的问题是:我需要输入用户名和密码值 安全的登录表单,因为我必须向一个HTTP请求发送 外部服务器验证这些是否匹配。在介绍之前 安全性我使用/ login GET和/ login开发了这个机制 POST,带有@ModelAttribute注释。我现在该怎么办?
  3. 使用实现UserDetailsS​​ervice的类更改身份验证提供程序,会发生什么?我相信,在这种情况下,登录表单中输入的用户名和密码将与从db检索到的用户名和密码进行比较,因为这些用户名和密码已分配给User对象。这样对吗?
  4. UserDetailsS​​erviceImpl

        @Service
        public class UserDetailsServiceImpl implements UserDetailsService {
            @Autowired
            private CustomerDao customerDao;
    
            @Override
            public UserDetails loadUserByUsername(String username)
                    throws UsernameNotFoundException {
                Customer customer = customerDao.findCustomerByUsername(username);
                return new User(customer.getUsername(), customer.getPassword(), true, true, true, true,
                    Arrays.asList(new SimpleGrantedAuthority(customer.getRole())));
            }
        }
    

    N.B。 用户的数据最初不在我的数据库中,这是因为我不确定UserDetailsS​​ervice解决方案(其中UserDetails只是通过用户名加载)。要检索我的Customer对象,我需要用户名和密码(发送到特定的外部URL),然后,如果JSON响应是肯定的(用户名和密码是正确的),我必须发送2个其他HTTP请求以获取客户的数据作为名字,姓氏,国籍等。此时我的用户可以被视为已登录。

    有什么建议吗? 提前谢谢。

1 个答案:

答案 0 :(得分:6)

  
      
  1. 我相信,但需要确认,那是因为安全正在做   幕后的东西:从中获取用户名和密码值   发布的表单并将其与身份验证中的表单进行比较   provider:如果匹配,则显示default-target-url,否则显示user   必须重复登录。是不是?
  2.   

没错。在安全配置中声明<login-form>元素时,您正在配置UsernamePasswordAuthenticationFilter

你配置了一些网址:

  • login-page =“/ login”:指向返回登录表单的@RequestMapping的网址
  • login-processing-url =“/ login”:触发UsernamePasswordAuthenticationFilter的网址。这与spring-security相当于构建后处理控制器方法。
  • default-target-url =“/ user”:提供有效用户凭据后将重定向用户的默认页面。
  • authentication-failure-url =“/ login”:尝试使用无效凭据登录时将重定向用户的URL。

虽然login-processing-url,default-target-url和authentication-failure-url必须是有效的RequestMappings,但login-processing-url将不会到达Spring MVC控制器层,因为它在执行Spring MVC之前执行调度员servlet。

所以

@RequestMapping(value = "/login", method = RequestMethod.POST)
    public ModelAndView doLogin() {
        System.out.println("***LOGIN_POST***");
        return new ModelAndView("users/home");
    }

永远不会到达。

将帖子归档到/login uri时,UsernamePasswordAuthenticationFilter将执行它的doFilter()方法来捕获用户提供的凭据,构建UsernamePasswordAuthenticationToken并将其委托给AuthenticationManager ,此Authentication将在匹配的AuthenticationProvider中执行。

  
      
  1. 然后我的问题是:我需要在安全性的登录表单中输入用户名和密码值,因为我必须向外部服务器发送HTTP请求以验证这些是否匹配。在介绍安全性之前,我使用/ login GET和/ login POST,使用@ModelAttribute注释开发了这个机制。我现在该怎么办?   我想你曾经对外部服务器执行身份验证时通过从POST /登录RequestMapping委托给一个类来完成它。
  2.   

因此,只需创建一个自定义AuthenticationProvider,它将用户验证内容委托给您的旧逻辑:

public class ThirdPartyAuthenticationProvider implements AuthenticationProvider {

    private Class<? extends Authentication> supportingClass = UsernamePasswordAuthenticationToken.class;

    // This represents your existing username/password validation class
    // Bind it with an @Autowired or set it in your security config
    private ExternalAuthenticationValidator externalAuthenticationValidator;

    /* (non-Javadoc)
     * @see org.springframework.security.authentication.AuthenticationProvider#authenticate(org.springframework.security.core.Authentication)
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        boolean validated = this.externalAuthenticationValidator.validate(authentication.getName(), authentication.getCredentials().toString());
        if(!validated){
            throw new BadCredentialsException("username and/or password not valid");
        }
        Collection<? extends GrantedAuthority> authorities = null; 
        // you must fill this authorities collection
        return new UsernamePasswordAuthenticationToken(
                    authentication.getName(),
                    authentication.getCredentials(),
                    authorities
                );      
    }

    /* (non-Javadoc)
     * @see org.springframework.security.authentication.AuthenticationProvider#supports(java.lang.Class)
     */
    @Override
    public boolean supports(Class<?> authentication) {
        return this.supportingClass.isAssignableFrom(authentication);
    }

    public ExternalAuthenticationValidator getExternalAuthenticationValidator() {
        return externalAuthenticationValidator;
    }

    public void setExternalAuthenticationValidator(ExternalAuthenticationValidator externalAuthenticationValidator) {
        this.externalAuthenticationValidator = externalAuthenticationValidator;
    }   

}

安全配置xml:

    <beans:bean id="thirdPartyAuthenticationProvider" class="com.xxx.yyy.ThirdPartyAuthenticationProvider">
        <!-- here set your external authentication validator in case you can't autowire it -->
        <beans:property name="externalAuthenticationValidator" ref="yourExternalAuthenticationValidator" />
    </beans:bean>

    <security:authentication-manager id="custom-auth">
        <security:authentication-provider ref="thirdPartyAuthenticationProvider" />
    </security:authentication-manager>

    <security:http auto-config="true" authentication-manager-ref="custom-auth">
        <security:intercept-url pattern="/user**" access="isAuthenticated()" />
        <security:form-login authentication-failure-url="/login" login-page="/login"
            login-processing-url="/login" default-target-url="/user" />
        <security:logout invalidate-session="true" logout-success-url="/index"
            logout-url="/logout" />
        <!-- in spring security 4.x CSRF filter is enabled by default. Disable it if 
             you don't plan to use it, or at least in the first attempts -->
        <security:csrf disabled="true"/>
    </security:http>
  
      
  1. 使用实现UserDetailsS​​ervice的类更改身份验证提供程序,会发生什么?我相信,在这种情况下,登录表单中输入的用户名和密码将与从db检索到的用户名和密码进行比较,因为这些用户名和密码已分配给User对象。这样对吗?
  2.   

正如您所说,您必须同时发送用户名和密码,我认为UserServiceDetails架构并不符合您的要求。我很瘦,你应该按照我在第2点的建议去做。

编辑:

  

最后一件事:现在我在身份验证方法中发送HTTP请求,   如果凭据是正确的,我会在响应中收到一个令牌,我就是   需要访问其他外部服务器服务。我怎么能通过   它在我的Spring控制器中?

要接收和处理收到的令牌,我会这样做:

ExternalAuthenticationValidator接口:

public interface ExternalAuthenticationValidator {

    public abstract ThirdPartyValidationResponse validate(String name, String password);

}

ThirdPartyValidationResponse模型界面:

public interface ThirdPartyValidationResponse{

    public boolean isValid();

    public Serializable getToken();

}

然后,更改提供商处理和管理它的方式:

public class ThirdPartyAuthenticationProvider implements AuthenticationProvider {

    private Class<? extends Authentication> supportingClass = UsernamePasswordAuthenticationToken.class;

    private ExternalAuthenticationValidator externalAuthenticationValidator;

    /* (non-Javadoc)
     * @see org.springframework.security.authentication.AuthenticationProvider#authenticate(org.springframework.security.core.Authentication)
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        ThirdPartyValidationResponse response = this.externalAuthenticationValidator.validate(authentication.getName(), authentication.getCredentials().toString());
        if(!response.isValid()){
            throw new BadCredentialsException("username and/or password not valid");
        }
        Collection<? extends GrantedAuthority> authorities = null; 
        // you must fill this authorities collection
        UsernamePasswordAuthenticationToken authenticated =  
                new UsernamePasswordAuthenticationToken(
                    authentication.getName(),
                    authentication.getCredentials(),
                    authorities
                );
        authenticated.setDetails(response);
        return authenticated;
    }

    /* (non-Javadoc)
     * @see org.springframework.security.authentication.AuthenticationProvider#supports(java.lang.Class)
     */
    @Override
    public boolean supports(Class<?> authentication) {
        return this.supportingClass.isAssignableFrom(authentication);
    }

    public ExternalAuthenticationValidator getExternalAuthenticationValidator() {
        return externalAuthenticationValidator;
    }

    public void setExternalAuthenticationValidator(ExternalAuthenticationValidator externalAuthenticationValidator) {
        this.externalAuthenticationValidator = externalAuthenticationValidator;
    }   

}

现在,您必须使用此代码段从userDetails中检索令牌:

        SecurityContext context = SecurityContextHolder.getContext();
        Authentication auth = context.getAuthentication();
        if(auth == null){
            throw new IllegalAccessException("Authentication is null in SecurityContext");
        }
        if(auth instanceof UsernamePasswordAuthenticationToken){
            Object details = auth.getDetails();
            if(details != null && details instanceof ThirdPartyValidationResponse){
                return ((ThirdPartyValidationResponse)details).getToken();
            }
        }
        return null;

不是将它包含在您需要的任何地方,而是创建一个从身份验证的详细信息中检索它的类可能更好:

public class SecurityContextThirdPartyTokenRetriever {

    public Serializable getThirdPartyToken() throws IllegalAccessException{
        SecurityContext context = SecurityContextHolder.getContext();
        Authentication auth = context.getAuthentication();
        if(auth == null){
            throw new IllegalAccessException("Authentication is null in SecurityContext");
        }
        if(auth instanceof UsernamePasswordAuthenticationToken){
            Object details = auth.getDetails();
            if(details != null && details instanceof ThirdPartyValidationResponse){
                return ((ThirdPartyValidationResponse)details).getToken();
            }
        }
        return null;
    }

}

如果你选择了这种最后一种方式,只需在security xml config中声明它(或使用@Service等注释注释):

<beans:bean id="tokenRetriever" class="com.xxx.yyy.SecurityContextThirdPartyTokenRetriever" />

还有其他方法,例如扩展UsernamePasswordAuthenticationToken以将令牌作为字段包含在其中,但这是我认为最简单的方法。