这一篇文章可能会陆续更新很久,主要是不一定有空,有精力整理那么多。不太习惯连载教程,网上其他的博客也很多了。可不太令我满意的是,大多数spring-security的配置都是停留在xml配置,springboot如何整合spring-security讲解的不多。所以本篇博客的定位是基于javaConfig,结合springboot的一些自动配置,详解一些spring-security的核心类和接口(建议有一定spring-security配置经验的人观看,不然你会不知道我在扯什么)。
spring-security最核心的接口是AuthenticationManager
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
记住,全局唯一身份管理器,AuthenticationManager是一个用来处理认证(Authentication)请求的接口。在其中只定义了一个方法authenticate(),该方法只接收一个代表认证请求的Authentication对象作为参数,如果认证成功,则会返回一个封装了当前用户权限等信息的Authentication对象进行返回。
spring-security为了方便用户的配置,提供了很多接口,很多配置适配器类,来适应不同用户的需求,所以可能把一些小白搞得晕头转向,明明就是想从数据库加载一个简单的用户,却不得不搞清楚那么多类。但是请记住,最核心的身份认证就是这个接口。
他有两个核心的子类
AuthenticationManager
----AuthenticationManagerDelegator
----ProviderManager
ProviderManager
是我们打交道比较多的一个类,而AuthenticationManagerDelegator
看名字就可以发现这是一个委托类,通常是由SecurityBuilder
接口的子类来帮助你配置生成一个身份管理器。spring的命名很规范,后缀带有Builder字样的类,都可以理解为“帮助你配置一个类”的工具类。
说回重要的ProviderManager
类
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
private List providers = Collections.emptyList();
private AuthenticationManager parent;
public ProviderManager(List providers) {
this(providers, null);
}
public ProviderManager(List providers,
AuthenticationManager parent) {
Assert.notNull(providers, "providers list cannot be null");
this.providers = providers;
this.parent = parent;
checkState();
}
}
截取它的部分重要代码,发现内部是维护了一个List
,对,非常之绕!AuthenticationManager
自己是一个接口不干活,靠他的实现类ProviderManager
去认证请求,他自己又不干活,交给内部的一个AuthenticationProvider
列表去认证请求。绕是绕,但是有它的道理,学过shiro的同学可能知道realm这个东西,就跟spring-security中的AuthenticationProvider
类似,设计成一个列表,意思是一个用户可能用多种方式进行认证(邮箱登录,手机号码登录,指纹登录?,第三方登录等等乱编的登录)。只需要通过一个即可认证成功,不过暂时没有找到类似shiro的认证策略的配置。
身份提供者AuthenticationProvider
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
boolean supports(Class> authentication);
}
和AuthenticationMannager
接口很像,多了一个supports方法,表示这个身份提供者支持认证什么类型的身份信息,只有返回true,才会执行authenticate方法。
AuthenticationProvider
----AbstractUserDetailsAuthenticationProvider
----DaoAuthenticationProvider
----RememberMeAuthenticationProvider
----xxxxProvider
有很多默认的实现,不过最常用的当然就是第一个类了:DaoAuthenticationProvider
,从某个数据源加载身份信息。
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
//密码相关
private PasswordEncoder passwordEncoder;
private String userNotFoundEncodedPassword;
//盐相关
private SaltSource saltSource;
//用户相关
private UserDetailsService userDetailsService;
}
基本的认证相关的类到这儿就结束了,主要就是看DaoAuthenticationProvider内部维护的UserDetailsService接口是个什么东西了。看名字就可以猜出来,这是个跟用户打交道的接口。
下面介绍UserDetailsService
接口
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
以及它的一个扩展接口UserDetailsManager
public interface UserDetailsManager extends UserDetailsService {
void createUser(UserDetails user);
void updateUser(UserDetails user);
void deleteUser(String username);
void changePassword(String oldPassword, String newPassword);
boolean userExists(String username);
}
于是加载用户就分成了两条路。
当然,这一切的前提都是你选择使用spring-security的默认实现。其实到了UserDetailsService这一步,就可以自己实现了,自定义加载用户的方式。
官网demo给了一个简单明了的认证授权流程,如下:
import org.springframework.security.authentication.*;
import org.springframework.security.core.*;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
public class AuthenticationExample {
private static AuthenticationManager am = new SampleAuthenticationManager();
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());
}
}
class SampleAuthenticationManager implements AuthenticationManager {
static final List AUTHORITIES = new ArrayList();
static {
AUTHORITIES.add(new SimpleGrantedAuthority("ROLE_USER"));
}
public Authentication authenticate(Authentication auth) throws AuthenticationException {
if (auth.getName().equals(auth.getCredentials())) {
return new UsernamePasswordAuthenticationToken(auth.getName(),
auth.getCredentials(), AUTHORITIES);
}
throw new BadCredentialsException("Bad Credentials");
}
}