• 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

  • 相关阅读:
    第四篇:new和delete的基本用法
    第三篇:C++ 中的几种初始化
    第七篇:使用 CUDA 进行计算优化的两种思路
    第六篇:二维数组的传输 (host <-> device)
    poj 2762(强连通+判断链)
    poj 3352(边双连通分量)
    poj 3228(二分+最大流)
    poj 3522(最小生成树应用)
    poj 2349(最小生成树应用)
    poj 1733(带权并查集+离散化)
  • 原文地址:https://www.cnblogs.com/sjqq/p/10132974.html
Copyright © 2020-2023  润新知