我曾经使用 Interceptor
实现了一个简单网站Demo的登陆拦截和Session处理工做,虽然可以实现相应的功能,可是无疑Spring Security提供的配置方法更加简单明确,可以更好的保护Web应用。java
这里你们能够参考Spring Security的官方介绍文档:spring-security-architecture
简单的来讲:node
Filter
,其具体的类型是FilterChainProxy
,其是做为@Bean
在ApplicationContext
中配置的。从容器的角度来看,Spring Security是一个单一的Filter,可是在其中有不少额外的Filter,每个都扮演着他们各自的角色,以下图所示:
git
Spring Security的身份验证,主要由AuthenticationManager
这个接口完成,其验证的主要方法是authenticate()
web
public interface AuthenticationManager { Authentication authenticate(Authentication authentication) throws AuthenticationException; }
Authentication
(一般包含 authenticated=true
)AuthenticationException
null
AuthicationManager
的实现是ProviderManager
,它将其委托给AuthticationProvider
这个实例,AuthenticationProvider
和AuthenticationManager
有一点像,可是含有一些额外的方法,来容许调用者来查询是否支持该Authenticaion
形式。public interface AuthenticationProvider { Authentication authenticate(Authentication authentication) throws AuthenticationException; boolean supports(Class<?> authentication); }
supports()
方法中的Class<?>
参数是Class<? extends Authentication>
,它只会询问其是否支持传递给authenticate()
方法。spring
ProviderManager
经过委托一系列的AuthenticaitonProviders
,以此来支支持多个不一样的认证机制,若是ProviderManager
没法识别一个特定的Authentication
实例类型,则会跳过它。不少时候,一个程序含有多个资源保护逻辑组,每个组都有他们独有的AuthenticationManager
,一般他们共享父级,那么父级就成为了了一个"global"资源
,做为全部provider
的后背。
框架
Spring Security提供了一些配置帮助咱们快速的开启验证功能,最经常使用的就是AuthenticationManagerBuiler
,它在内存(in-memory)、JDBC、LDAP或者我的定制的UserDetailService
这些领域都很擅长。ssh
注意:本后续代码以SpringBoot为框架实现,其DEMO Git: Spring-Security-Demoide
方法 | 描述 |
---|---|
configure(WebSecurity) | 经过重载,配置Spring Security的Filter链 |
configure(HttpSecurity) | 经过重载,配置如何拦截器保护请求 |
configure(AuthenticationManagerBuilder) | 经过重载,配置user-detail服务 |
@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/index").hasAnyAuthority("ROLE_USER","ROLE_ADMIN") .antMatchers("/oss").hasAuthority("ROLE_ADMIN") .antMatchers(HttpMethod.GET, "/login").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll()//.successHandler(successHandler) .and() .logout() .logoutSuccessUrl("/") .permitAll(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()) .withUser("root").password(new BCryptPasswordEncoder().encode("root")).roles("USER","ADMIN").and() .withUser("normal").password(new BCryptPasswordEncoder().encode("normal")).roles("USER"); //auth.authenticationProvider(userProvider); //auth.authenticationProvider(afterProvider); }
- 经过`antMatchers()`进行URL匹配,再进行相应的处理,好比见上代码,咱们将**/index**和**/oss**两个连接进行了拦截,并分别要求拥有`ROLE_USER`或`ROLE_ADMIN`、`ROLE_ADMIN`这两个身份才能访问。 - `anyRequest().authenticated()`指其余请求都会须要验证 - `formLogin()`使其有了登陆页面,若是没有后面的`loginPage()`,则会默认生成一个Spring Security的页面,然后面注释掉的`successHandler`则是后续会讲到的。 - `permitAll()`则表示当前链接不须要认证。 - `logout()`会拦截因此的**\logout**请求,完成登出操做,`logoutSuccessUrl()`则是登出后的重定向地址。 - `and()`在其中起链接做用。
则此时咱们的root帐号既可以访问index也可以访问oss,而normal帐号只能访问index,不能访问oss,若是访问oss会出现:
There was an unexpected error (type=Forbidden, status=403).网站
上面咱们经过重载configure(AuthenticationManagerBuilder auth)生成了两个内存用户root和normal,咱们也能够经过jdbc等方法实现。ui
Token
的设置等等,好比我如今打印一条登陆信息,并将请求重定向到首页@Component public class SuccessHandler implements AuthenticationSuccessHandler{ @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { System.out.println(authentication.getName()+" is loging , role is"+authentication.getAuthorities()); response.sendRedirect("/"); }
formLogin()
后,即:.formLogin() .loginPage("/login") .permitAll().successHandler(successHandler)
UserAuthProvider
,并让其实现AuthenticationProvider
接口:@Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { System.out.println("-----------------------------------------------------------------------"); System.out.println("This is UserAuthProvider"); System.out.println("starting authenticate ... ..."); System.out.println("Credentials:"+authentication.getCredentials()); System.out.println("Name:"+authentication.getName()); System.out.println("Class:"+authentication.getClass()); System.out.println("Details:"+authentication.getDetails()); System.out.println("Principal:"+authentication.getPrincipal()); System.out.println("-----------------------------------------------------------------------"); UsernamePasswordAuthenticationToken auth=new UsernamePasswordAuthenticationToken(authentication.getPrincipal(), authentication.getCredentials()); return auth; } @Override public boolean supports(Class<?> authentication) { System.out.println("This is UserAuthProvider"); System.out.println("starting supports"); System.out.println(authentication.getClass()); return false; }
auth.inMemoryAuthentication()
,将UserAuthProvider加入到AuthenticationManagerBuilder
中,即:@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()) // .withUser("root").password(new BCryptPasswordEncoder().encode("root")).roles("USER","ADMIN").and() // .withUser("normal").password(new BCryptPasswordEncoder().encode("normal")).roles("USER"); auth.authenticationProvider(userProvider); auth.authenticationProvider(afterProvider); }
This is UserAuthProvider starting supports java.lang. Class
supports()
方法,永远返回false,而返回false时,即不会再调用authenticate()
进行认证操做(正如上面所介绍的),咱们将supports()
的返回值变成true,再次登陆(username: root password: 1234),则控制台会输出This is UserAuthProvider starting supports class java.lang.Class ----------------------------------------------------------------------- This is UserAuthProvider starting authenticate ... ... Credentials:1234 Name:root Class:class org.springframework.security.authentication.UsernamePasswordAuthenticationToken Details:org.springframework.security.web.authentication.WebAuthenticationDetails@166c8: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: node04v47liue6knt1oghnzgiqb9dx0 Principal:root ----------------------------------------------------------------------- root is loging , role is[]
即成功登陆了,由于咱们在authenticate()
方法中直接声明了一个Authentication
的实例UsernamePasswordAuthenticationToken
,并返回了,正如上面所说,当返回Authentication
实例时,则默认为受权成功,而若是咱们返回null
,则说明没法判断,不会登陆成功。
此时咱们再建立一个对象UserAfterProvider
,其也实现AuthenticationProvider
接口,并将UserAfterProvider
和UserAuthProvider
的authenticate()
返回值都设置为null
,咱们再次使用上面的数据进行登陆,控制台输出以下:
This is UserAuthProvider starting supports class java.lang.Class ----------------------------------------------------------------------- This is UserAuthProvider starting authenticate ... ... Credentials:1234 Name:root Class:class org.springframework.security.authentication.UsernamePasswordAuthenticationToken Details:org.springframework.security.web.authentication.WebAuthenticationDetails@43458: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: node01m47f3t6xq5a470fu07jaipzb0 Principal:root ----------------------------------------------------------------------- This is UserAfterProvider starting supports class java.lang.Class ----------------------------------------------------------------------- This is UserAfterProvider starting authenticate ... ... Credentials:1234 Name:root Class:class org.springframework.security.authentication.UsernamePasswordAuthenticationToken Details:org.springframework.security.web.authentication.WebAuthenticationDetails@43458: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: node01m47f3t6xq5a470fu07jaipzb0 Principal:root -----------------------------------------------------------------------
AuthenticationManagerBuilder
的验证都会进行一遍,那么若是咱们将其中一个Provider的authenticate()
返回值还原为Authentication
实例,再次登陆,则控制台会输出以下结果:This is UserAuthProvider starting supports class java.lang.Class ----------------------------------------------------------------------- This is UserAuthProvider starting authenticate ... ... Credentials:1234 Name:root Class:class org.springframework.security.authentication.UsernamePasswordAuthenticationToken Details:org.springframework.security.web.authentication.WebAuthenticationDetails@166c8: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: node04v47liue6knt1oghnzgiqb9dx0 Principal:root ----------------------------------------------------------------------- root is loging , role is[] This is UserAuthProvider starting supports class java.lang.Class ----------------------------------------------------------------------- This is UserAuthProvider starting authenticate ... ... Credentials:null Name:root Class:class org.springframework.security.authentication.UsernamePasswordAuthenticationToken Details:org.springframework.security.web.authentication.WebAuthenticationDetails@166c8: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: node04v47liue6knt1oghnzgiqb9dx0 Principal:root -----------------------------------------------------------------------
由于咱们重写了AuthenticationSuccessHandler
,因此验证成功后悔重定向到/,而我Controller里对/又作了一次重定向到/index,因此发生了两次验证,而此次咱们发现由于UserAuthProvider
经过了,因此UserAfterProvider
并无进行验证,因此咱们能够知道,只要有一个Provider经过了验证咱们就能够认为经过了验证。
所以,咱们能够经过实现AuthenticationProvider
来写入本身的一些认证逻辑,甚至能够@Autowire相关Service来辅助实现。
个人博客即将搬运同步至腾讯云+社区,邀请你们一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=1353hw8jzy7ee