上一步拿到用户凭证,接下来就是对凭证进行认证。由AuthenticationManager提供。简单理解下AuthenticationManager是什么。
public interface AuthenticationManager { // ~ Methods /** * Attempts to authenticate the passed {@link Authentication} object, returning a * fully populated <code>Authentication</code> object (including granted authorities) * if successful. * <p> * An <code>AuthenticationManager</code> must honour the following contract concerning * exceptions: * <ul> * <li>A {@link DisabledException} must be thrown if an account is disabled and the * <code>AuthenticationManager</code> can test for this state.</li> * <li>A {@link LockedException} must be thrown if an account is locked and the * <code>AuthenticationManager</code> can test for account locking.</li> * <li>A {@link BadCredentialsException} must be thrown if incorrect credentials are * presented. Whilst the above exceptions are optional, an * <code>AuthenticationManager</code> must <B>always</B> test credentials.</li> * </ul> * Exceptions should be tested for and if applicable thrown in the order expressed * above (i.e. if an account is disabled or locked, the authentication request is * immediately rejected and the credentials testing process is not performed). This * prevents credentials being tested against disabled or locked accounts. * * @param authentication the authentication request object * * @return a fully authenticated object including credentials * * @throws AuthenticationException if authentication fails */ Authentication authenticate(Authentication authentication) throws AuthenticationException; }尝试认证传递过来的Authentication对象(即我们的UsernamePasswordAuthenticationToken), 如果认证通过,返回全部信息以及authority权限,否则抛出AuthenticationException异常表示认证失败。
AuthenticationManager的初始化比较复杂,绕了好多路。在我们的SecurityConfig里可以找到声明的地方。
//com.example.serverapi.config.SecurityConfig#authenticationManagerBean @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } //com.example.serverapi.config.SecurityConfig#configure(org.springframework.security.config.annotation.web.builders.HttpSecurity) TokenAuthenticationFilter filter = new TokenAuthenticationFilter("/**"); filter.setAuthenticationManager(authenticationManagerBean());而AuthenticationManager是AuthenticationManagerDelegator来代替的,其代理的则是org.springframework.security.authentication.ProviderManager。
所以,我们定义provider来认证上一步的token是否合法。
//com.example.serverapi.config.SecurityConfig#configure(org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder) @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //DaoAuthenticationConfigurer-DaoAuthenticationProvider用来提供登录时用户名和密码认证 //auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); //自定义TokenAuthenticationProvider, 用来提供token认证 auth.authenticationProvider(new UserTokenAuthenticationProvider()); }以下是provider全部信息
//com.example.serverapi.domain.security.config.UserTokenAuthenticationProvider /** * 这里只使用了username字段。 * * @author Ryan Miao * @date 2019/5/29 22:05 */ public class UserTokenAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider { @Override protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { } @Override protected UserDetails retrieveUser(String token, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { //验证token TokenManagement tokenManagement = ServerApiApplication.context .getBean(TokenManagement.class); com.example.serverapi.domain.security.entity.User userInfo = tokenManagement.get(token); if (userInfo == null) { throw new BadCredentialsException("token认证失败"); } authentication.setDetails(userInfo); Set<SimpleGrantedAuthority> authorities = userInfo.getRoleList().stream() .map(Role::getPermissionList) .flatMap(Collection::stream) .map(p -> new SimpleGrantedAuthority(p.getName())).collect( Collectors.toSet()); return new User(userInfo.getUsername(), userInfo.getPassword(), authorities); } /** * 对应我们的Token令牌类UsernamePasswordAuthenticationToken,可以采用本provide验证. */ @Override public boolean supports(Class<?> authentication) { return (UsernamePasswordAuthenticationToken.class .isAssignableFrom(authentication)); } }supports方法来表示本provider提供的认证范围,即传递UsernamePasswordAuthenticationToken的凭证将接受认证
自定义了我们自己的Token管理方法TokenManagement,来对token进行认证。根据token拿到userinfo则成功
从userInfo里提取authority,创建一个UserDetails,交给下一步的权限校验
权限校验