关于spring security 4(以下简称SS) ,我们不能不否认,学习的成本是挺高的。如果光光是复制配置代码而不去理解SS的各个组件的实现原理和功能,那当然还是相当简单的一回事,因为配置的代码就那么几行
PS:本人不是大神,写博客只是为了增强记忆和理解,以下的内容都是本人通过大量学习SS官方文档和搜索stack overflow探索得来的观点
1.SS究竟主要实现什么功能?
SS实现了非常多的功能,但是概括地说,SS主要实现了Authentication和Authorization,中文译文为认证与授权。以下用一个生动的例子说明:
认证:就相当于去私人会所,进保安那一关,保安通过认脸(SS通过账户和密码等)确认你是否可以进入
授权:当然,私人会所里面也有森严的等级制度,一些抢手的红牌不是屌丝可以点的,当然就有些屌丝可以点红牌,也只允许陪酒和唱歌;而一些较牛的VIP,就有很高的权限可以为所欲为。
2. 认识各个关键部件
1. 基础上下文组件SecurityContextHolder
根据官网的译文,SecurityContextHolder是SS中最基础的类,主要用来储存认证信息、认证规则等信息。在SS 4之后的版本,几乎不需要我们手动去配置这个组件。但是我们还是来认识一下这个组件的具体原理:
SecurityContextHolder主要通过ThreadLocal来实现认证和授权信息的处理,这个组件如果要深究,请查看SS的源代码。
我们只要知道SecurityContextHolder有一个很重要的方法,那就是getContext(),方法返回SecurityContext 类,这个类同样有一个非常重要的getAuthentication(),返回的是一个Authentication类,这个类就是我们上文说到大名鼎鼎的认证接口。
2.认证接口Authentication,UsernamePasswordAuthenticationToken,AuthenticationProvide
值得注意的是Authentication继承了Principal接口,问题来了,什么是Principal,我们看看官方的说法:
This interface represents the abstract notion of a principal, which
can be used to represent any entity, such as an individual, a
corporation, and a login id.
这是一个虚拟的接口,可以代表任何的实体类
意思就是说,这个Principal什么都不干,只是一个纯粹的Object,开发者喜欢把它转换成什么就是什么。通常在SS里,一般会转换成UserDetails类,这个类等下再说。
回到Authentication接口,它有那么几个重要的方法值得注意:
getPrincipal():这个方法毫无疑问,返回的是一个Pincipal类,通常可以转换成UserDetails类
getCredentials():这个方法返回的是用户凭证,通常是密码
getAuthoritis():这个方法返回的是权限集合,例如{“ROLE_ADMIN”,”ROLE_EMPLOYER”}这样的集合。
实现了Authentication接口的具体类有以下几个,非常值得注意:
- 一个是UsernamePasswordAuthenticationToken,它就是一个简单代表账号、密码和权限的东西。
- 另外一个是AuthenticaitonManager和AuthenticationProvider,他们其实差别不大,就只拿AuthenticationProvider来讲好了。他们都有一个最重要的方法,那就是authenticate()方法
这个方法主要用来生成一个Authentication接口的实现类,好了相信很多童鞋看到这里都已经无法理解SS了,坦白说,我也是….
不过各位这样理解就好了,AuthenticationProvider是一个负责验证用户信息的类,如果通过认证,就返回一个UsernamePasswordAuthenticationToken类,说明认证成功。否则,抛出BadCredentialsException错误或者其他Exception。
3.UserDetails和UserDetailsService接口
这个接口的具体实现类是SS的org.springframework.security.core.userdetails.User类(以下简称User类),用来储存SS所需的用户信息。而UserDetails一般很少自己new出来,主要是通过UserDetailsService的loadUserByUserName(String name)来生成。在方法里,开发者可以通过任何途径将自己的用户类转换成SS的User类。
说了那么一大堆,我还是放出代码来比较好理解
SercurityConfig.java
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Bean
public UserDetailsService userDetailsService(){
return new MyUserDetailsService();
}
@Bean
public MyAuthenticationProvider myAuthenticationProvider(){
MyAuthenticationProvider provider = new MyAuthenticationProvider();
provider.setUserDetailsService(userDetailsService());
return provider;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// TODO Auto-generated method stub
http
.csrf()
.disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll();
}
}
MyUserDetailsService
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserDao userDao;
@Override
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
System.out.println("UserDetails invoked");
AppUser appUser = userDao.findUser(name);
return new User(appUser.getName(),appUser.getPassword(),true,true,true,true,appUser.getAuthorities());
}
}
MyAuthenticationProvider
public class MyAuthenticationProvider extends DaoAuthenticationProvider{
@Autowired
private MyUserDetailsService uds;
@Override
public Authentication authenticate(Authentication auth) throws AuthenticationException {
String requestName = auth.getName();
String requestPass = auth.getCredentials().toString();
UserDetails ud = uds.loadUserByUsername(requestName);
//对比用户输入密码与数据库密码是否一致
if (requestPass.equals(ud.getPassword())) {
System.out.println("auth:user auth success");
return new UsernamePasswordAuthenticationToken(auth.getPrincipal(), auth.getCredentials(),
ud.getAuthorities());
}
throw new BadCredentialsException("Bad credentials");
}
@Override
public boolean supports(Class<?> arg0) {
// TODO Auto-generated method stub
return true;
}
涉及其他配置的代码太多了,有需要的自己下载哈。
http://download.youkuaiyun.com/download/selfreeyuan/10118873