zoukankan      html  css  js  c++  java
  • SpringSecurity-UsernamePasswordAuthenticationFilter的作用

    UsernamePasswordAuthenticationFilter应该是我们最关注的Filter,因为它实现了我们最常用的基于用户名和密码的认证逻辑。

    先看一下一个常用的form-login配置:

    1 <form-login login-page="/login" 
    2         username-parameter="ssoId"  
    3         password-parameter="password"
    4         authentication-failure-url ="/loginfailure" 
    5         default-target-url="/loginsuccess"/>  
    6         <logout invalidate-session="true"/>

    在这里可以自定义表单中对应的用户名密码的name,已经登录登录成功或失败后跳转的url地址以及登录表单的action。

      UsernamePasswordAuthenticationFilter继承虚拟类AbstractAuthenticationProcessingFilter。

      AbstractAuthenticationProcessingFilter要求设置一个authenticationManager,authenticationManager的实现类将实际处理请求的认证。AbstractAuthenticationProcessingFilter将拦截符合过滤规则的request,并试图执行认证。子类必须实现 attemptAuthentication 方法,这个方法执行具体的认证。

      认证处理:如果认证成功,将会把返回的Authentication对象存放在SecurityContext;然后setAuthenticationSuccessHandler(AuthenticationSuccessHandler)

    方法将会调用;这里处理认证成功后跳转url的逻辑;可以重新实现AuthenticationSuccessHandler的onAuthenticationSuccess方法,实现自己的逻辑,比如需要返回json格式数据时,就可以在这里重新相关逻辑。如果认证失败,默认会返回401代码给客户端,当然也可以在<form-login>节点中配置失败后跳转的url,还可以重写AuthenticationFailureHandler的onAuthenticationFailure方法实现自己的逻辑。

    一个典型的自定义配置如下:

    复制代码
    1 <beans:bean id="restfulUsernamePasswordAuthenticationFilter"  
    2         class="com.kingdee.core.config.RestfulUsernamePasswordAuthenticationFilter">  
    3         <beans:property name="authenticationManager" ref="authenticationManager" />  
    4         <beans:property name="authenticationSuccessHandler" ref="restfulAuthenticationSuccessHandler" />  
    5         <beans:property name="authenticationFailureHandler" ref="restfulAuthenticationFailureHandler" />  
    6         <beans:property name="loginUrl" value="/login/restful" />  
    7     </beans:bean>
    复制代码

    下面先看一下authentication-manager的配置,这个配置实现自定义UserDetail,需要重新实现一个继承UserDetailsService接口的类。

    1 <authentication-manager alias="authenticationManager">  
    2         <authentication-provider user-service-ref="customUserDetailsService">  
    3             <password-encoder ref="bcryptEncoder"/> 
    4         </authentication-provider>  
    5     </authentication-manager>

    我们看到authentication-manager节点有一个子节点authentication-provider,而authentication-provider有一个属性user-service-ref,user-service-ref的值就是我们要实现的自定义类。

    整个调用过程大致如下:

      继承虚拟类AbstractAuthenticationProcessingFilter的UsernamePasswordAuthenticationFilter实现了attemptAuthentication方法

    复制代码
     1 public Authentication attemptAuthentication(HttpServletRequest request,
     2             HttpServletResponse response) throws AuthenticationException {
     3         if (postOnly && !request.getMethod().equals("POST")) {
     4             throw new AuthenticationServiceException(
     5                     "Authentication method not supported: " + request.getMethod());
     6         }
     7 
     8         String username = obtainUsername(request);
     9         String password = obtainPassword(request);
    10 
    11         if (username == null) {
    12             username = "";
    13         }
    14 
    15         if (password == null) {
    16             password = "";
    17         }
    18 
    19         username = username.trim();
    20 
    21         UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
    22                 username, password);
    23 
    24         // Allow subclasses to set the "details" property
    25         setDetails(request, authRequest);
    26 
    27         return this.getAuthenticationManager().authenticate(authRequest);
    28     }
    复制代码

    这个方法的最后this.getAuthenticationManager().authenticate(authRequest)是实现自接口Authentication,而实现这个接口的类中有一个叫ProviderManager的,它有一个成员变量List<AuthenticationProvider>,对应于我们配置文件中的authentication-provider,这里也说明是可以配置多个authentication-provider的。我们只使用一个我们需要的。我们需要关注的是AbstractUserDetailsAuthenticationProvider这个虚拟类,它实现了我们所需要的authenticate方法:

    复制代码
     1 public Authentication authenticate(Authentication authentication)
     2             throws AuthenticationException {
     3         Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
     4                 messages.getMessage(
     5                         "AbstractUserDetailsAuthenticationProvider.onlySupports",
     6                         "Only UsernamePasswordAuthenticationToken is supported"));
     7 
     8         // Determine username
     9         String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
    10                 : authentication.getName();
    11 
    12         boolean cacheWasUsed = true;
    13         UserDetails user = this.userCache.getUserFromCache(username);
    14 
    15         if (user == null) {
    16             cacheWasUsed = false;
    17 
    18             try {
    19                 user = retrieveUser(username,
    20                         (UsernamePasswordAuthenticationToken) authentication);
    21             }
    22             catch (UsernameNotFoundException notFound) {
    23                 logger.debug("User '" + username + "' not found");
    24 
    25                 if (hideUserNotFoundExceptions) {
    26                     throw new BadCredentialsException(messages.getMessage(
    27                             "AbstractUserDetailsAuthenticationProvider.badCredentials",
    28                             "Bad credentials"));
    29                 }
    30                 else {
    31                     throw notFound;
    32                 }
    33             }
    34 
    35             Assert.notNull(user,
    36                     "retrieveUser returned null - a violation of the interface contract");
    37         }
    38 
    39         try {
    40             preAuthenticationChecks.check(user);
    41             additionalAuthenticationChecks(user,
    42                     (UsernamePasswordAuthenticationToken) authentication);
    43         }
    44         catch (AuthenticationException exception) {
    45             if (cacheWasUsed) {
    46                 // There was a problem, so try again after checking
    47                 // we're using latest data (i.e. not from the cache)
    48                 cacheWasUsed = false;
    49                 user = retrieveUser(username,
    50                         (UsernamePasswordAuthenticationToken) authentication);
    51                 preAuthenticationChecks.check(user);
    52                 additionalAuthenticationChecks(user,
    53                         (UsernamePasswordAuthenticationToken) authentication);
    54             }
    55             else {
    56                 throw exception;
    57             }
    58         }
    59 
    60         postAuthenticationChecks.check(user);
    61 
    62         if (!cacheWasUsed) {
    63             this.userCache.putUserInCache(user);
    64         }
    65 
    66         Object principalToReturn = user;
    67 
    68         if (forcePrincipalAsString) {
    69             principalToReturn = user.getUsername();
    70         }
    71 
    72         return createSuccessAuthentication(principalToReturn, authentication, user);
    73     }
    复制代码

    从代码中可以看到,它会先从cache中取user(这与配置有关,这里我们不涉及),如果没有,在执行retrieveUser方法。代码中还可以看到,UsernameNotFoundException默认是被转换成BadCredentialsException的。

    它的子类DaoAuthenticationProvider重写了retrieveUser方法:

    复制代码
     1 protected final UserDetails retrieveUser(String username,
     2             UsernamePasswordAuthenticationToken authentication)
     3             throws AuthenticationException {
     4         UserDetails loadedUser;
     5 
     6         try {
     7             loadedUser = this.getUserDetailsService().loadUserByUsername(username);
     8         }
     9         catch (UsernameNotFoundException notFound) {
    10             if (authentication.getCredentials() != null) {
    11                 String presentedPassword = authentication.getCredentials().toString();
    12                 passwordEncoder.isPasswordValid(userNotFoundEncodedPassword,
    13                         presentedPassword, null);
    14             }
    15             throw notFound;
    16         }
    17         catch (Exception repositoryProblem) {
    18             throw new InternalAuthenticationServiceException(
    19                     repositoryProblem.getMessage(), repositoryProblem);
    20         }
    21 
    22         if (loadedUser == null) {
    23             throw new InternalAuthenticationServiceException(
    24                     "UserDetailsService returned null, which is an interface contract violation");
    25         }
    26         return loadedUser;
    27     }
    复制代码

    在代码第7行可以看到,UserDetails从UserDetailsService().loadUserByUsername(username)中获得的。我们已经配置了userService方法,所以只要在配置类中重写loadUserByUsername(username)方法就可以了。这里需要注意的是我们重写的方法需要返回一个实现了UserDetails接口的对象,而org.springframework.security.core.userdetails.User就是我们经常实际返回的对象。

    它的一个构造方法如下:

    复制代码
     1 public User(String username, String password, boolean enabled,
     2             boolean accountNonExpired, boolean credentialsNonExpired,
     3             boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
     4 
     5         if (((username == null) || "".equals(username)) || (password == null)) {
     6             throw new IllegalArgumentException(
     7                     "Cannot pass null or empty values to constructor");
     8         }
     9 
    10         this.username = username;
    11         this.password = password;
    12         this.enabled = enabled;
    13         this.accountNonExpired = accountNonExpired;
    14         this.credentialsNonExpired = credentialsNonExpired;
    15         this.accountNonLocked = accountNonLocked;
    16         this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
    17     }
    复制代码

    我们根据自己的需要,从数据库中取得user和user对应的权限,构造一个org.springframework.security.core.userdetails.User返回即可。

    这里只是重新实现了User的认证方法,如果想在SecurityContext中添加用户的其他信息,如email,address等,可以新指定一个authentication-provider的实现类,可以实现复用DaoAuthenticationProvider的大部分代码,只需要添加authentication.setDetails的相关代码即可。虽然UsernamePasswordAuthenticationFilter的注释是在setDetails(request, authRequest);方法中实现添加自定义的details,但也可以根据实际情况修改。甚至可以不用在这里修改,直接把需要的信息放在httpSession中。

    这些博客都是重在理解SpringSecurity的工作过程,有助于重写一些自己的逻辑,但不涉及重写的具体实现(这些实现很多是可以在网上找到的)。

    https://www.cnblogs.com/zsxneil/p/6622564.html

  • 相关阅读:
    Populating Next Right Pointers in Each Node II
    Populating Next Right Pointers in Each Node
    Construct Binary Tree from Preorder and Inorder Traversal
    Construct Binary Tree from Inorder and Postorder Traversal
    Path Sum
    Symmetric Tree
    Solve Tree Problems Recursively
    632. Smallest Range(priority_queue)
    609. Find Duplicate File in System
    poj3159最短路spfa+邻接表
  • 原文地址:https://www.cnblogs.com/sjqq/p/10132974.html
Copyright © 2011-2022 走看看