Spring Security 认证过程源码分析

先看一下我做登录代码从这个开始

/**
	 * 系统登录认证
	 * @param request
	 * @param username
	 * @param password
	 * @param authenticationManager
	 * @return
	 */
	public static JwtAuthenticatioToken login(HttpServletRequest request, String username, String password, AuthenticationManager authenticationManager) {
        //我不知道用户名密码是不是对的,所以构造一个未认证的Token先
		JwtAuthenticatioToken token = new JwtAuthenticatioToken(username, password);
		token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
		// 执行登录认证过程
	    Authentication authentication = authenticationManager.authenticate(token);
	    // 认证成功存储认证信息到上下文
	    SecurityContextHolder.getContext().setAuthentication(authentication);
		// 生成令牌并返回给客户端
	    token.setToken(JwtTokenUtils.generateToken(authentication));
		return token;
	}

Token给谁处理呢?当然是给当前的AuthenticationManager,从代码中我们看到调用了AuthenticationManagerauthenticate方法

但是AuthenticationManager是一个接口,真正做事的是他的实现类ProviderManager,那么就是说直接代用ProviderManagerauthenticate方法

 AuthenticationProvider provider = (AuthenticationProvider)var7.next();
 result = provider.authenticate(authentication);

这里说的ProviderManager 它也不是真的做事的,那么做事情到底是谁呢,那肯定是AuthenticationManager接口中的抽象实现类,

AbstractUserDetailsAuthenticationProvider 

 String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);
        if (user == null) {
            cacheWasUsed = false;

            try {
                user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            } catch (UsernameNotFoundException var6) {
                this.logger.debug("User '" + username + "' not found");
                if (this.hideUserNotFoundExceptions) {
                    throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
                }

                throw var6;
            }

            Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
        }

进入AbstractUserDetailsAuthenticationProvider 类后会调用 authenticate(Authentication authentication) 方法对用户的身份进行校验,首先是判断用户是否为空,这个 user 是 UserDetail 的对象,如果为空,表示还没有认证,就需要调用 retrieveUser 方法去获取用户的信息,这个方法是抽象类 的扩展类DaoAuthenticationProvider 的一个方法。

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

        try {
            UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
            } else {
                return loadedUser;
            }
        } catch (UsernameNotFoundException var4) {
            this.mitigateAgainstTimingAttack(authentication);
            throw var4;
        } catch (InternalAuthenticationServiceException var5) {
            throw var5;
        } catch (Exception var6) {
            throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
        }
    }

在该扩展类的 retrieveUser 方法中调用 UserDetailsService 这个接口的实现类的 loadUserByUsername 方法去获取用户信息,而这里我自己编写了实现类 UserDetailsServiceImpl 类,在这个实现类中,我们可以编写自己的逻辑,从数据库中获取用户密码等权限信息返回。

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private SysUserService sysUserService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser user = sysUserService.findByName(username);
        if (user == null) {
            throw new UsernameNotFoundException("该用户不存在");
        }
        // 用户权限列表,根据用户拥有的权限标识与如 @PreAuthorize("hasAuthority('sys:menu:view')") 标注的接口对比,决定是否可以调用接口
        Set<String> permissions = sysUserService.findPermissions(user.getName());
        List<GrantedAuthority> grantedAuthorities = permissions.stream().map(GrantedAuthorityImpl::new).collect(Collectors.toList());
        return new JwtUserDetails(user.getName(), user.getPassword(), user.getSalt(), grantedAuthorities);
    }
}

 在拿到用户的信息后,返回到 AbstractUserDetailsAuthenticationProvider 类中调用 createSuccessAuthentication(principalToReturn, authentication, user) 方法

  return this.createSuccessAuthentication(principalToReturn, authentication, user);

 在该方法中会调用三个参数的UsernamePasswordAuthenticationToken 构造器,不同于前面调用两个参数的,因为这里已经验证了用户的信息和权限,因此不再是给父类构造器中传null 值了,而是用户的权限集合,并且设置认证通过setAuthenticated(true)

protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
        result.setDetails(authentication.getDetails());
        return result;
    }

 看下UsernamePasswordAuthenticationToken  构造器

 public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        super.setAuthenticated(true);
    }

所以现在 authorities 不再为空了。

AbstractUserDetailsAuthenticationProvider的authenticate方法中还有一个密码校验
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);

这里也只是抽象出来方法而已,功能扩展类DaoAuthenticationProvider完成,我这里写了自己的方法JwtAuthenticationProvider

去集成DaoAuthenticationProvider

public class JwtAuthenticationProvider extends DaoAuthenticationProvider {

    public JwtAuthenticationProvider(UserDetailsService userDetailsService) {
        setUserDetailsService(userDetailsService);
    }

    @Override
	protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
		if (authentication.getCredentials() == null) {
			logger.debug("Authentication failed: no credentials provided");
			throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
		}

		String presentedPassword = authentication.getCredentials().toString();
		String salt = ((JwtUserDetails) userDetails).getSalt();
		// 覆写密码验证逻辑
		if (!new PasswordEncoder(salt).matches(userDetails.getPassword(), presentedPassword)) {
			logger.debug("Authentication failed: password does not match stored value");
			throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
		}
	}

}

最终成功的话就返回 Authentication ,在将它设置到上下文中

// 执行登录认证过程
Authentication authentication = authenticationManager.authenticate(token);
// 认证成功存储认证信息到上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
发布了90 篇原创文章 · 获赞 29 · 访问量 7256

猜你喜欢

转载自blog.csdn.net/weixin_38982591/article/details/104851260