Spring Security 快速了解

在Spring Security以前


我曾经使用 Interceptor 实现了一个简单网站Demo的登陆拦截和Session处理工做,虽然可以实现相应的功能,可是无疑Spring Security提供的配置方法更加简单明确,可以更好的保护Web应用。java

Spring Security的相关结构


这里你们能够参考Spring Security的官方介绍文档:spring-security-architecture
简单的来讲:node

  • Spring Security是一个单一的Filter,其具体的类型是FilterChainProxy,其是做为@BeanApplicationContext中配置的。
  • 从容器的角度来看,Spring Security是一个单一的Filter,可是在其中有不少额外的Filter,每个都扮演着他们各自的角色,以下图所示:
    git

  • Spring Security的身份验证,主要由AuthenticationManager这个接口完成,其验证的主要方法是authenticate()web

public interface AuthenticationManager {   
    
  Authentication authenticate(Authentication authentication)   
    throws AuthenticationException;   
   
}
  • 该方法能够完成三件事:
    • 若是它能够验证输入表明一个有效的主体,就返回一个Authentication(一般包含 authenticated=true
    • 若是它能够验证输入表明一个无效的主体,就throw一个AuthenticationException
    • 若是它不能决断,就返回null
  • 最经常使用的AuthicationManager的实现是ProviderManager,它将其委托给AuthticationProvider这个实例,AuthenticationProviderAuthenticationManager有一点像,可是含有一些额外的方法,来容许调用者来查询是否支持该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


使用Spring Security实现访问和权限控制

注意:本后续代码以SpringBoot为框架实现,其DEMO Git: Spring-Security-Demoide

  • 主要经过重载WebSecurityConfigurerAdapter的configure方法进行访问和权限控制
方法 描述
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()`在其中起链接做用。
  • 一些经常使用的保护路径配置方法
    • authenticated() : 容许认证过的用户访问
    • denyAll() : 无条件拒绝全部访问
    • fullyAuthenticated() : 若是用户是完整认证(不经过Remeber me)访问
    • hasIpAdress(String) : 若是骑牛来自给定IP地址,就能够访问
    • hasAnyAuthority(String ...) : 若是用于具有任意一个给定角色,就能够访问
    • hasAnthority(String) : 若是用户具有给定角色,就能够访问
    • permitAl() : 无条件容许方法
    • remeberMe():若是用户是经过Remeber-me认证的,就能够访问
    • 另外,与Autheority对应有一个Role,二者是一个概念,Autheority必须以“ROLE_”开头,而Role不须要,见上代码。
  • 则此时咱们的root帐号既可以访问index也可以访问oss,而normal帐号只能访问index,不能访问oss,若是访问oss会出现:
    There was an unexpected error (type=Forbidden, status=403).网站

  • 上面咱们经过重载configure(AuthenticationManagerBuilder auth)生成了两个内存用户root和normal,咱们也能够经过jdbc等方法实现。ui


经过AuthenticationSuccessHandler实现认证成功后的处理

  • 经过实现AuthenticationSuccessHandler接口,咱们能够在验证成功后执行相应的代码,好比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)
  • 再次登陆root帐户,则会在控制台看到: root is loging , role is[ROLE_ADMIN, ROLE_USER]

经过AuthenticationProvider实现个性化认证

  • 咱们创建一个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接口,并将UserAfterProviderUserAuthProviderauthenticate()返回值都设置为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
-----------------------------------------------------------------------
  • 即两个Porvider都进行了验证,都没有经过(返回null),说明全部加入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

相关文章
相关标签/搜索