开课吧孤尽T31训练营学习笔记-DAY24-springsecurity 定制用户认证的逻辑

105 阅读3分钟

springsecurity 定制用户认证

如果定制spring security的用户认证,方法有很多,在T31项目中,我们定义了一个UserDetailServiceImpl即完成了定制逻辑,为什么这个起到了效果。通过深入了解spring security的认证逻辑来解密。

一、spring security 认证类图

未命名文件.png

1.1 认证管理器AuthenticationManager

认证过程由认证管理器AuthenticationManager来总负责,所以我们可以看到在spring security的安全配置类中,专门针对它有配置方法:

// 配置用户认证器
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(this.userDetailService)
        .passwordEncoder(passwordEncoder());
}

从类图中看出,ProviderManager实现了认证管理器。从组合关系看出这个管理器就是收集了一堆认证器,一旦来了认证请求,看那个认证器能搞,就让哪个认证器去搞。从ProviderManager源码来看一下Authenticate中核心部分:



   for (AuthenticationProvider provider : getProviders()) {
      if (!provider.supports(toTest)) {
         continue;
      }

      try {
         result = provider.authenticate(authentication);

         if (result != null) {
            copyDetails(authentication, result);
            break;
         }
      }
      catch (AccountStatusException | InternalAuthenticationServiceException e) {
         prepareException(e, authentication);
         // SEC-546: Avoid polling additional providers if auth failure is due to
         // invalid account status
         throw e;
      } catch (AuthenticationException e) {
         lastException = e;
      }
   }

1.2 认证器AuthenticationProvider

认证器一般是成对出现的,一个证件类型Authentication对应能够处理的认证器AuthenticationProvider。

有很多认证器,我们只关注一种,就是通过用户名密码来认证的这一分支。

首先这个AbstractUserDetailsAuthenticationProvider定义了认证过程,我们看一下这类的Authenticate方法(省掉cache部分):

public Authentication authenticate(Authentication authentication)
      throws AuthenticationException {

   // Determine username
   String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
         : authentication.getName();

   user = retrieveUser(username,
               (UsernamePasswordAuthenticationToken) authentication);

   preAuthenticationChecks.check(user);
   additionalAuthenticationChecks(user,
            (UsernamePasswordAuthenticationToken) authentication);
   postAuthenticationChecks.check(user);


   if (forcePrincipalAsString) {
      principalToReturn = user.getUsername();
   }

   return createSuccessAuthentication(principalToReturn, authentication, user);
}

核心代码就四步:

  1. 获取用户信息 retrieveUser(交给子类实现)
  2. 前置检查
  3. 核心校验逻辑(子类中实现)
  4. 后置检查

1.3 真正干活的DaoAuthenticationProvider

DaoAuthenticationProvider认证器负责去拿用户信息,然后校验用户名密码。

1. 获取用户信息

获取用户信息,就是借助于UserDetailsService来获取:

protected final UserDetails retrieveUser(String username,
      UsernamePasswordAuthenticationToken authentication)
      throws AuthenticationException {
   prepareTimingAttackProtection();

  UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
  return loadedUser;
}

2. 验证用户名密码

通过代码,看出是通过passwordEncoder来校验用户名和密码的。

protected void additionalAuthenticationChecks(UserDetails userDetails,
      UsernamePasswordAuthenticationToken authentication)
      throws AuthenticationException {


   String presentedPassword = authentication.getCredentials().toString();

   if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
      logger.debug("Authentication failed: password does not match stored value");

      throw new BadCredentialsException(messages.getMessage(
            "AbstractUserDetailsAuthenticationProvider.badCredentials",
            "Bad credentials"));
   }
}

二、定制实战

根据分析结果,其实我们只需要定制一下获取用户信息的逻辑即可,在具体实现中,我们通过OpenFeigh调用admin-server的服务,来获取用户信息。

用户信息包括用户的基本信息(用户名角色)和授权信息(角色、权限)。

2.1 配置认证管理器

配置认证管理器,注入新的获取用户信息的实现类。

// 配置用户认证器
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(this.userDetailService)
        .passwordEncoder(passwordEncoder());
}

2.2 定义实现类

public class UserDetailServiceImpl implements UserDetailsService {
    private static final Logger logger = LoggerFactory.getLogger(UserDetailServiceImpl.class);

    @Autowired
    UserClient userClient;

    @Autowired
    PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        UserDTO user = userClient.getByUsername(username);

        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        if (user != null) {
            logger.debug("current user = " + user);

            //获取⽤户的授权
            List<String> roles = userClient.listRolesByUserId(user.getId());

            //声明授权⽂件
            for (String role : roles) {
                GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_" + role);
                //spring Security中权限名称 必须满⾜ROLE_XXX
                grantedAuthorities.add(grantedAuthority);
            }
        }

        logger.debug("granted authorities = " + grantedAuthorities);
        return new User(user.getUsername(), user.getPassword(), grantedAuthorities);
    }
}

至此,完成理论和实战的过程。