springsecurity⾯试题
1、spring security所谓的全局上下⽂是如何实现的?
ThreadLocal
2、了解spring security哪些核⼼组件,并介绍?
AuthenticationManagerBuilder
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth)throws Exception {
auth
.
inMemoryAuthentication()
.withUser("admin").password("admin").roles("USER");
}
}
想要在WebSecurityConfigurerAdapter中进⾏认证相关的配置,可以使⽤configure(AuthenticationManagerBuilder auth)暴露⼀个AuthenticationManager的建造器:AuthenticationManagerBuilder 。如上所⽰,我们便完成了内存中⽤户的配置。
细⼼的朋友会发现,在前⾯的⽂章中我们配置内存中的⽤户时,似乎不是这么配置的,⽽是:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin").password("admin").roles("USER");
}
}
如果你的应⽤只有唯⼀⼀个WebSecurityConfigurerAdapter,那么他们之间的差距可以被忽略,从⽅法名可以看出两者的区别:使⽤
@Autowired注⼊的AuthenticationManagerBuilder是全局的⾝份认证器,作⽤域可以跨越多个WebSecurityConfigurerAdapter,以及影响到基于Method的安全控制;⽽ protected configure()的⽅式则类似于⼀个匿名内部类,它的作⽤域局限于⼀个WebSecurityConfigurerAdapter内部。官⽅⽂档中,也给出了配置多个WebSecurityConfigurerAdapter的场景以及demo。
SecurityContextHolder
SecurityContextHolder⽤于存储安全上下⽂(security context)的信息。当前操作的⽤户是谁,该⽤户是否已经被认证,他拥有哪些⾓⾊权限…这些都被保存在SecurityContextHolder中。SecurityContextHolder默认使⽤ThreadLocal 策略来存储认证信息。看到ThreadLocal 也就意味着,这是⼀种与线程绑定的策略。Spring Security在⽤户登录时⾃动绑定认证信息到当前线程,在⽤户退出时,⾃动清除当前线程的认证信息。但这⼀切的前提,是你在web场景下使⽤Spring Security,⽽如果是Swing界⾯,Spring也提供了⽀
持,SecurityContextHolder的策略则需要被替换。
获取当前⽤户的信息 因为⾝份信息是与线程绑定的,所以可以在程序的任何地⽅使⽤静态⽅法获取⽤户信息。⼀个典型的获取当前登录⽤户的姓名的例⼦如下所⽰:
Object principal = Context().getAuthentication().getPrincipal();
if(principal instanceof UserDetails){ String username =((UserDetails)principal).getUsername();}else{ String username = String();}
getAuthentication()返回了认证信息,再次getPrincipal()返回了⾝份信息,UserDetails便是Spring对⾝份信息封装的⼀个接⼝。Authentication
package;// <1>
public interface Authentication extends Principal, Serializable {// <1>
Collection<?extends GrantedAuthority>getAuthorities();// <2>
Object getCredentials();// <2>
Object getDetails();// <2>
Object getPrincipal();// <2>
boolean isAuthenticated();// <2>
void setAuthenticated(boolean var1)throws IllegalArgumentException;
}
<1> Authentication是spring security包中的接⼝,直接继承⾃Principal类,⽽Principal是位于java.security包中的。可以见
得,Authentication在spring security中是最⾼级别的⾝份/认证的抽象。
<2>由这个顶级接⼝,我们可以得到⽤户拥有的权限信息列表,密码,⽤户细节信息,⽤户⾝份信息,认证信息。
接⼝详细解读如下:
getAuthorities(),权限信息列表,默认是GrantedAuthority接⼝的⼀些实现类,通常是代表权限信息的⼀系列字符串。
getCredentials(),密码信息,⽤户输⼊的密码字符串,在认证过后通常会被移除,⽤于保障安全。
getDetails(),细节信息,web应⽤中的实现接⼝通常为 WebAuthenticationDetails,它记录了访问者的ip地址和sessionId的值。getPrincipal(),最重要的⾝份信息,⼤部分情况下返回的是UserDetails接⼝的实现类,也是框架中的常⽤接⼝之⼀。
AuthenticationManager
初次接触Spring Security的朋友相信会被AuthenticationManager,ProviderManager ,AuthenticationProvider …这么多相似的Spring认证类搞得晕头转向,但只要稍微梳理⼀下就可以理解清楚它们的联系和设计者的⽤意。AuthenticationManager(接⼝)是认证相关的核⼼接⼝,也是发起认证的出发点,因为在实际需求中,我们可能会允许⽤户使⽤⽤户名+密码登录,同时允许⽤户使⽤邮箱+密码,⼿机号码+密码登录,甚⾄,可能允许⽤户使⽤指纹登录(还有这样的操作?没想到吧),所以说AuthenticationManager⼀般不直接认证,AuthenticationManager接⼝的常⽤实现类ProviderManager 内部会维护⼀个List列表,存放多种认证⽅式,实际上这是委托者模式的应⽤(Delegate)。也就是说,核⼼的认证⼊⼝始终只有⼀个:AuthenticationManager,不同的认证⽅式:⽤户名+密码(UsernamePasswordAuthenticationToken),邮箱+密码,⼿机号码+密码登录则对应了三个AuthenticationProvider。这样⼀来四不四就好理解多了?熟悉shiro的朋友可以把AuthenticationProvider理解成Realm。在默认策略下,只需要通过⼀个AuthenticationProvider的认证,即可被认为是登录成功。
只保留了关键认证部分的ProviderManager源码:
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
/
/ 维护⼀个AuthenticationProvider列表
private List<AuthenticationProvider> providers = ptyList();
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Class<?extends Authentication> toTest = Class();
AuthenticationException lastException =null;
Authentication result =null;
// 依次认证
for(AuthenticationProvider provider :getProviders()){
if(!provider.supports(toTest)){
continue;
}
try{
result = provider.authenticate(authentication);
if(result !=null){
copyDetails(authentication, result);
break;
}
}
...
catch(AuthenticationException e){
lastException = e;
}
}
// 如果有Authentication信息,则直接返回
if(result !=null){
if(eraseCredentialsAfterAuthentication
&&(result instanceof CredentialsContainer)){
//移除密码
((CredentialsContainer) result).eraseCredentials();
}
//发布登录成功事件
eventPublisher.publishAuthenticationSuccess(result);
return result;
}
...
//执⾏到此,说明没有认证成功,包装异常信息
if(lastException ==null){
lastException =new Message(
"ProviderManager.providerNotFound",
new Object[]{ Name()},
"No AuthenticationProvider found for {0}"));
}
prepareException(lastException, authentication);
throw lastException;
}
}
ProviderManager 中的List,会依照次序去认证,认证成功则⽴即返回,若认证失败则返回null,下⼀个AuthenticationProvider会继续尝试认证,如果所有认证器都⽆法认证成功,则ProviderManager 会抛出⼀个ProviderNotFoundException异常。
UserDetails与UserDetailsService
UserDetails这个接⼝,它代表了最详细的⽤户信息,这个接⼝涵盖了⼀些必要的⽤户信息字段,具体的实现类对它进⾏了扩展。
public interface UserDetails extends Serializable {
Collection<?extends GrantedAuthority>getAuthorities();
String getPassword();
shiro安全框架
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
它和Authentication接⼝很类似,⽐如它们都拥有username,authorities,区分他们也是本⽂的重点内容之⼀。Authentication的getCredentials()与UserDetails中的getPassword()需要被区分对待,前者是⽤户提交的密码凭证,后者是⽤户正确的密码,认证器其实就是对这两者的⽐对。Authentication中的getAuthorities()实际是由UserDetails的getAuthorities()传递⽽形成的。还记得Authentication接⼝中的getUserDetails()⽅法吗?其中的UserDetails⽤户详细信息便是经过了AuthenticationProvider之后被填充的。
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
UserDetailsService和AuthenticationProvider两者的职责常常被⼈们搞混,关于他们的问题在⽂档的FAQ和issues中屡见不鲜。记住⼀点即可,敲⿊板UserDetailsService只负责从特定的地⽅(通常是数据库)加载⽤户信息,仅此⽽已,记住这⼀点,可以避免⾛很多弯路。UserDetailsService常见的实现类有JdbcDaoImpl,InMemoryUserDetailsManager,前者从数据库加载⽤户,后者从内存中加载⽤户,也可以⾃⼰实现UserDetailsService,通常这更加灵活。
DaoAuthenticationProvider
AuthenticationProvider最最最常⽤的⼀个实现便是DaoAuthenticationProvider。顾名思义,Dao正是数据访问层的缩写,也暗⽰了这个⾝份认证器的实现思路。
按照我们最直观的思路,怎么去认证⼀个⽤户呢?⽤户前台提交了⽤户名和密码,⽽数据库中保存了⽤户名和密码,认证便是负责⽐对同⼀个⽤户名,提交的密码和保存的密码是否相同便是了。在Spring Security中。提交的⽤户名和密码,被封装成了UsernamePasswordAuthenticationToken,⽽根据⽤户名加载⽤户的任务则是交给了UserDetailsService,在DaoAuthenticationProvider中,对应的
⽅法便是retrieveUser,虽然有两个参数,但是retrieveUser只有第⼀个参数起主要作⽤,返回⼀个UserDetails。还需要完成UsernamePasswordAuthenticationToken和UserDetails密码的⽐对,这便是交给additionalAuthenticationChecks⽅法完成的,如果这个void⽅法没有抛异常,则认为⽐对成功。⽐对密码的过程,⽤到了PasswordEncoder和SaltSource,密码加密和盐的概念相信不⽤我赘述了,它们为保障安全⽽设计,都是⽐较基础的概念。
如果你已经被这些概念搞得晕头转向了,不妨这么理解DaoAuthenticationProvider:它获取⽤户提交的⽤户名和密码,⽐对其正确性,如果正确,返回⼀个数据库中的⽤户信息(假设⽤户信息被保存在数据库中)。
3、Spring Security是如何完成⾝份认证的?
1 ⽤户名和密码被过滤器获取到,封装成Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类。
2 AuthenticationManager ⾝份管理器负责验证这个Authentication
3 认证成功后,AuthenticationManager⾝份管理器返回⼀个被填充满了信息的(包括上⾯提到的权限信息,⾝份信息,细节信息,但密码通常会被移除)Authentication实例。
4 SecurityContextHolder安全上下⽂容器将第3步填充了信息的Authentication,通过
这是⼀个抽象的认证流程,⽽整个过程中,如果不纠结于细节,其实只剩下⼀个AuthenticationManager 是我们没有接触过的了,这个⾝份管理器我们在后⾯的⼩节介绍。将上述的流程转换成代码,便是如下的流程:
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);
break;
}catch(AuthenticationException e){
System.out.println("Authentication failed: "+ e.getMessage());
}
}
System.out.println("Successfully authenticated. Security context contains: "+
}
}
class SampleAuthenticationManager implements AuthenticationManager {
static final List<GrantedAuthority> AUTHORITIES =new ArrayList<GrantedAuthority>();
static{
AUTHORITIES.add(new SimpleGrantedAuthority("ROLE_USER"));
}
public Authentication authenticate(Authentication auth)throws AuthenticationException { Name().Credentials())){
return new Name(),
}
throw new BadCredentialsException("Bad Credentials");
}
}
4、简述⼀下spring security核⼼过滤器

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。