Spring Security 快速了解

在Spring Security之前



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

Spring Security的相关结构



这里大家可以参考Spring Security的官方介绍文档:spring-security-architecture
简单的来说:

  • Spring Security是一个单一的Filter,其具体的类型是FilterChainProxy,其是作为@BeanApplicationContext中配置的。
  • 从容器的角度来看,Spring Security是一个单一的Filter,但是在其中有很多额外的Filter,每一个都扮演着他们各自的角色,如下图所示:
  • Spring Security的身份验证,主要由AuthenticationManager这个接口完成,其验证的主要方法是authenticate()
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()方法。

  • 在同一个程序中,一个ProviderManager通过委托一系列的AuthenticaitonProviders,以此来支支持多个不同的认证机制,如果ProviderManager无法识别一个特定的Authentication实例类型,则会跳过它。
  • 很多时候,一个程序含有多个资源保护逻辑组,每一个组都有他们独有的AuthenticationManager,通常他们共享父级,那么父级就成为了了一个"global"资源,作为所有provider的后背。
  • Spring Security提供了一些配置帮助我们快速的开启验证功能,最常用的就是AuthenticationManagerBuiler,它在内存(in-memory)、JDBC、LDAP或者个人定制的UserDetailService这些领域都很擅长。

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

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

  • 主要通过重载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等方法实现。

通过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.sprin[email protected]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.sprin[email protected]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.sprin[email protected]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.sprin[email protected]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.sprin[email protected]166c8: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: node04v47liue6knt1oghnzgiqb9dx0
Principal:root
-----------------------------------------------------------------------
  • 因为我们重写了AuthenticationSuccessHandler,所以验证成功后悔重定向到/,而我Controller里对/又做了一次重定向到/index,所以发生了两次验证,而这次我们发现因为UserAuthProvider通过了,所以UserAfterProvider并没有进行验证,所以我们可以知道,只要有一个Provider通过了验证我们就可以认为通过了验证。
  • 因此,我们可以通过实现AuthenticationProvider来写入自己的一些认证逻辑,甚至可以@Autowire相关Service来辅助实现。

原文地址:https://www.cnblogs.com/rekent/p/9456892.html

时间: 2024-08-02 14:44:13

Spring Security 快速了解的相关文章

Spring Security LDAP简介

1.概述 在本快速教程中,我们将学习如何设置Spring Security LDAP. 在我们开始之前,了解一下LDAP是什么? - 它代表轻量级目录访问协议.它是一种开放的,与供应商无关的协议,用于通过网络访问目录服务. 2. Maven Dependency 首先,让我们看看我们需要的maven依赖项: <dependency> <groupId>org.springframework.security</groupId> <artifactId>spr

SpringBoot学习(二)—— springboot快速整合使用spring security组

Spring Security 简介 spring security的核心功能为认证(Authentication),授权(Authorization),即认证用户是否能访问该系统,和授权用户可以在系统中进行哪些操作. 引入spring security组件 在 pom.xml 中加入 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starte

spring security实现限制登录次数功能

本节是在基于注解方式进行的,后面的例子都会基于注解形式,不再实现XML配置形式,毕竟注解才是趋势嘛! 关键在于实现自定义的UserDetailsService和AuthenticationProvider 项目结构如下: 查看spring security的源代码可以发现默认security已经定义的user中的一些变量,鉴于此创建users表如下: CREATE TABLE users ( username VARCHAR(45) NOT NULL, password VARCHAR(45)

Java Web系列:Spring Security 基础

Spring Security虽然比JAAS进步很大,但还是先天不足,达不到ASP.NET中的认证和授权的方便快捷.这里演示登录.注销.记住我的常规功能,认证上自定义提供程序避免对数据库的依赖,授权上自定义提供程序消除从缓存加载角色信息造成的角色变更无效副作用. 1.基于java config的Spring Security基础配置 (1)使用AbstractSecurityWebApplicationInitializer集成到Spring MVC 1 public class Securit

Spring Security教程系列(一)基础篇-2

第 4 章 自定义登陆页面 Spring Security虽然默认提供了一个登陆页面,但是这个页面实在太简陋了,只有在快速演示时才有可能它做系统的登陆页面,实际开发时无论是从美观还是实用性角度考虑,我们都必须实现自定义的登录页面. 4.1. 实现自定义登陆页面 自己实现一个login.jsp,放在src/main/webapp/目录下. 4.2. 修改配置文件 在xml中的http标签中添加一个form-login标签. <http auto-config="true">

(一)关于spring security的简要介绍以及相关配置和jar包认识

Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架.它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作. 安全主要包括两个操作,"认证和验证(权限控制)"

Spring Security简介

1. Spring Security 是什么? Spring Security是一个强大的和高度可定制的身份验证和访问控制框架,它的前身是 Acegi Security. Spring Security着重于为Java应用程序提供身份验证和授权.身份验证是为用户建立一个他所声明的主体的过程(主体一般式指用户,设备或可以在你系统中执行动作的其他系统).授权指的是一个用户能否在你的应用中执行某个操作,在到达授权判断之前,身份的主体已经由身份验证过程建立了.这些概念是通用的,并不是Spring Sec

Spring Security概论

1. Spring Security 是什么? Spring Security是一个强大的和高度可定制的身份验证和访问控制框架,它的前身是 Acegi Security. Spring Security着重于为Java应用程序提供身份验证和授权.身份验证是为用户建立一个他所声明的主体的过程(主体一般式指用户,设备或可以在你系统中执行动作的其他系统).授权指的是一个用户能否在你的应用中执行某个操作,在到达授权判断之前,身份的主体已经由身份验证过程建立了.这些概念是通用的,并不是Spring Sec

Spring MVC、Mybatis、Hibernate、Bootstrap、HTML5、jQuery、Spring Security安全权限、Lucene全文检索、Ehcache分布式缓存 、高性能、高并发【Java企业通用开发平台框架】

功能特点: 1.适配所有设备(PC.平板.手机等),兼容所有浏览器(Chrome.Firefox.Opera.Safari.IE6~IE11等),适用所有项目(MIS管理信息系统.OA办公系统.ERP企业资源规划系统.CRM客户关系管理系统.网站.管理后台等). 2.快速开发,敏捷的数据持久层解决方案. 2.1.事务自动处理. 2.2.O/R Mapping基于注解,零配置XML,便于维护,学习成本低. 2.3.接口和实现分离,不需写数据持久层代码,只需写接口,自动生成添加.修改.删除.排序.分