Spring Security系列-Spring Security运行机制分析(三)

前言

继续第三篇,这次增加了Spring Security的DaoAuthenticationProvider和UserDetailsService类,离源代码又会更近一步。

从一个小程序开始

使用DaoAuthenticationProvider类来代替上篇中自定义类SimpleAuthenticationProvider,代码如下:

public class AuthenticationExample {
	public static List<AuthenticationProvider> providers = Arrays.asList(getDaoAuthentication());
    public static AuthenticationManager am = new ProviderManager(providers);

    public static void main(String[] args) throws Exception {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

        while (true) {
            System.out.println("Please enter your username:");
            String name = in.readLine();
            System.out.println("Please enter your password:");
            String password = in.readLine();
            try {
                Authentication request = new UsernamePasswordAuthenticationToken(name, password);
                Authentication result = am.authenticate(request);
                SecurityContextHolder.getContext().setAuthentication(result);
                break;
            } catch (AuthenticationException e) {
                System.out.println("Authentication failed: " + e.getMessage());
            }
        }
        System.out.println("Successfully authenticated. Security context contains: " +
                SecurityContextHolder.getContext().getAuthentication());
    }

    public static AuthenticationProvider getDaoAuthentication() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
        UserDetails admin = User.withUsername("admin")
                .password(passwordEncoder.encode("123456"))
                .authorities("ROLE_USER")
                .build();
        UserDetailsService uds = new InMemoryUserDetailsManager(admin);
        provider.setPasswordEncoder(passwordEncoder);
        provider.setUserDetailsService(uds);
        return provider;
    }
}

在getDaoAuthentication方法中,还使用了UserDetails、UserDetailsService和PasswordEncoder用来读取用户信息。这里直接用代码添加的用户,而真实项目开发会从数据库中读取用户信息。

DaoAuthenticationProvider

DaoAuthenticationProvider类的源代码如下,retrieveUser方法是最关键的。通过username用来获取用户相关信息。此处用到了UserDetailsServiceo类的loadUserByUsername方法,请往下看。

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
	...
	protected final UserDetails retrieveUser(String username,
			UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
		prepareTimingAttackProtection();
		try {
			UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
			if (loadedUser == null) {
				throw new InternalAuthenticationServiceException(
						"UserDetailsService returned null, which is an interface contract violation");
			}
			return loadedUser;
		}
		catch (UsernameNotFoundException ex) {
			mitigateAgainstTimingAttack(authentication);
			throw ex;
		}
		catch (InternalAuthenticationServiceException ex) {
			throw ex;
		}
		catch (Exception ex) {
			throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
		}
	}
	...
}

UserDetailsService

UserDetailsService接口中只定义了一个loadUserByUsername方法。实现接口的类主要有InMemoryUserDetailsManager和JdbcUserDetailsManager,一个是通过HashMap存储和读取用户信息列表,一个是通过读取数据库中的用户信息列表。

public interface UserDetailsService {
	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

AbstractUserDetailsAuthenticationProvider

回个头再来看看DaoAuthenticationProvider类的父类AbstractUserDetailsAuthenticationProvider。下面的只展示了关键代码。

public abstract class AbstractUserDetailsAuthenticationProvider implements
		AuthenticationProvider, InitializingBean, MessageSourceAware {
    public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
		String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
				: authentication.getName();
		user = retrieveUser(username,
				(UsernamePasswordAuthenticationToken) authentication);
		preAuthenticationChecks.check(user);
		additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);

		postAuthenticationChecks.check(user);
		Object principalToReturn = user;

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

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

		return result;
	}
}

可以看到,这里authenticate方法,也就是我们上篇使用到的,用于用户认证。在调用authenticate方法时,就会去调用DaoAuthenticationProvider类重写的retrieveUser方法。验证成功后,createSuccessAuthentication方法就返回了一个完整的授权后的Authentication。

源代码

github源代码:https://github.com/camellibby/security-demo

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值