权限管理系统 02 Spring Security 学习

Spring Security 介绍

Spring Security是基于Spring的应用程序提供声明式安全保护的安全性框架,它提供了完整的安全性解决方案,能够在web请求级别和方法调用级别处理身份证验证和授权.它充分使用了依赖注入和面向切面的技术.

Spring Security主要做两件事,一件是认证,一件是授权。

Spring Security主要是从两个方面解决安全性问题:

web请求级别:使用servlet过滤器保护web请求并限制URL级别的访问
方法调用级别:使用Spring AOP保护方法调用,确保具有适当权限的用户采用访问安全保护的方法.

Spring Security 主要组件图
权限管理系统 02 Spring Security 学习

Spring Security 提供了有很多的过滤器,他们拦截 Servlet 请求,并将这些请求转交给认证处理过滤器和访问决策过滤器进行处理,并强制安全性认证用户身份和权限以达到保护 Web 资源的目的。

Spring Security安全机制包括两个主要的操作:认证(主体等)和验证(权限控制,能否在系统中执行某个操作)。

几种常见的认证模式

  • Basic : Http1.0提出的认证方法。HTTP基础认证(BA)是一种简单的认证机制。当一个web客户端需要保护任何web资源的时候,服务器会发送一个带有401状态码(未授权)的HTTP回应,还有类似WWW-Authenticate: Basic realm=”realm here” 的 WWW-Authenticate HTTP头。而浏览器这时候就会弹出一个登录对话框,提示输入用户名和密码。不安全,是明文密码,而且是无状态的,每次都要认证。
  • Digest:主要是为了解决 Basic 认证安全的问题,用户替代原来的Basic认证模式,Digest认证也是采用challenge/response认证模式。当访问特定的资源时,浏览器跳出对话框,让用户输入用户名密码。浏览器对用户名、密码、nonce值、HTTP请求方法、被请求资源URI等组合后进行MD5运算,把计算得到的摘要信息发送给服务端。服务端web容器获取HTTP报文头部相关认证信息,从中获取到username,根据username获取对应的密码,同样对用户名、密码、nonce值、HTTP请求方法、被请求资源URI等组合进行MD5运算,计算结果和response进行比较,如果匹配则认证成功并返回相关资源,否则,重新进行认证。避免了密码的明文传输,提高了安全性。但是也有缺点,认证的报文如果被攻击者拦截到,也能够获取资源
  • X.509:X.509是一种非常通用的证书格式,所有的X.509证书,包含以下数据:
    版本号,持有人公钥,证书*** 有效期 主体名 签发人姓名
    证书***手机
  • LDAP : LDAP认证是通过WSS3.0加上轻量目录LDAP协议搭建的种认证方式,使用https加密传输,主要用于做文档管理。
    LDAP认证就是把用户数据放在LDAP服务器上,通过LDAP服务器上的数据对用户进行认证处理。
  • Form:Form模式跳出了HTTP规范提供了自定义的更加灵活的认证模式,但由于Form模式属于J2EE范畴,一般出现在java体系中,而且它也存在密码明文传输安全问题。假设用户要浏览需要权限的页面,此时,安全机制先启动,检查当前用户请求是否持有用户票据的Cookie 如此Cookie存在:解析Cookie中的票据信息,获得用户角色,创建用户标识 否则:认为用户无权浏览该页面,跳转至登入页面,登入成功后重定向到所请求页面

Spring Security 常用权限拦截

权限管理系统 02 Spring Security 学习
Spring Security提供了很多的过滤器,拦截Servlet 请求,并将这些请求转交给认证处理过滤器和访问决策过滤器进行处理,并强制安全性认证用户身份和权限以达到Web资源的目的。具体有哪些常用的Filter。

  • SecurityContextPersistenceFilter:主要是在SecurityContextRepository中保存更新一个securityContext,并将securityContext给以后的过滤器使用 本质上就是在session中生成一个securityContext httpSession.setAttribute(springSecurityContextKey, context);
  • LoginOutFilter
  • AbstractAuthenticationProcessingFilter
  • DefaultLoginPageGeneratingFilter
  • BasicAuthenticationFilter
  • SecurityContextHolderAwareRequestFilte
  • RememberMeAuthenticationFilter
  • AnonymousAuthenticationFilter:为了保证操作统一性
  • ExceptionTranslationFilter
  • SessionManagementFilter
  • FilterSecurityInterceptor

这十一个拦截器如何按照顺序来执行的,下面来介绍一个类 FilterChainProxy

Spring Security 数据库管理

权限管理系统 02 Spring Security 学习

数据库管理

Spring Security的核心处理流程:当一个用户登录时,会先执行身份认证,如果身份认证未通过会邀请用户重新认证。当用户身份认证通过后会调用角色管理器判断他是否可以访问。如果要实现数据库管理用户及权限就需要自定义用户登录功能。Spring Security为我们提供了UserDetailService接口,该接口只有一个方法UserDetail loadUserByUsername(),其中UserDetail也是一个接口,getAuthorities属性中存储了用户所有的权限。UserDetail也提供了用户是否过期,用户是否被锁定,证书是否过期,用户是否有效这些方法。任何一个方法返回false用户的凭证就会被认为是无效的。Authentication对象是提供用户安全访问信息的安全对象。Authentication有已认证和未认证的两种状态,在作为参数传入认证管理器的时候,它是一个未认证对象。它从客户端获取的用户信息如用户、密码可以从登陆页面也可以从cookie中获取,并由系统自动构成一个Authentication对象。而UserDetails它代表的是用户信息安全的源,这个源可以是从数据库、LDAP服务器、CA中心返回。Spring Security要做的就是将这个未认证的Authentication对象和UserDetails做进行匹配,成功后将UserDetails的用户权限信息拷贝到Authentication中,组成一个完整的Authentication对象与其他组件进行共享。UserDetails即可从数据库返回也可以从其它如LDAP中返回。当我们需要使用数据库进行管理时,我们需要实现UserDetailService里的loadUserByUsername方法。这就需要我们准备以下几张表:用户表。权限表、角色表、用户和角色关系表、角色和权限关系表。UserDetails中的用户状态通过用户表里的属性去填充,UserDetails中的权限集合则是通过用户表、权限表、用户和角色关系表、角色和权限关系表构成的RBAC模型来提供。

权限缓存

Spring Security的权限缓存和数据库管理有关,都是在用户认证上做文章,因此都UserDetailService有关,与数据库管理不同的是Spring Security提供了一个可以缓存UserDetailService的实现类,这个类的名字叫做CachingUserDetailService,该类的构造接收一个真正加载UserDetails的UserDetailService实现类,当需要加载UserDetails时,会首先从缓存中获取,如果缓存中没有对应的UserDetails存在,则使用持久的UserDetailService实现类进行加载,然后将加载后的结果保存在缓存中。UserDetails与缓存的交互是通过UserCache接口来实现的。CachingUserDetailService默认拥有UserCache的一个空引用实现–null usercache,当缓存中不存在对应的UserDetails时,将使用引用的UserDetailService类型的dalegate进行加载,加载后再将其放在缓存中进行返回,除了nullusercache之外,Spring Security还提供了一个基于EhCache的UserCache的实现类,这个类的名字叫做EhCacheBaseUserCache。当我们需要对UserDetails进行缓存时,我们只需要进行定义一个EhCache的实例,然后注入到EhCacheBaseUserCache中就可以了。上述两个类都是Spring Security已经实现了的。在实际项目中为了更好的使用及控制缓存,我们会尝试引入更多的Cache,我们不仅缓存UserCache还有缓存用户相关的权限。实际项目在做权限控制时,我们一般会选择自己对相关的数据进行缓存

自定义决策

当用户身份认证通过后,它会调用角色管理器判断是否可以继续访问,要实现自定义决策管理器需要继承AbstractAccessDecisionManager这个类,它的核心方法是supports方法,其下有个AccessDecisionVoter,是Spring Security引入的投票器的概念,有无权限的最终决定权就是由这个投票器决定的。最常见的投票器有RoleVoter,投票器核心由vote选举实现的。AffirmmativeBased方案是只要有一个投票通过就允许访问。ConsensusBased,这个投票器的方案是有有一半以上投票通过才允许访问资源。UnanimousBaesd这个投票器的方案是所有的投票器都通过才允许访问资源。

环境搭建及使用

打开 https://start.spring.io/
按照以下新建项目
权限管理系统 02 Spring Security 学习
点击 generate project
然后用Idea 打开

目录如下:
权限管理系统 02 Spring Security 学习

  1. 打开 DemoApplication,如下编写
    权限管理系统 02 Spring Security 学习

  2. 先注释掉 pom.xml文件中的security和tomcat
    权限管理系统 02 Spring Security 学习

  3. 执行 DemoApplication,在浏览器打开 http://localhost:8080/,会跳转到页面
    权限管理系统 02 Spring Security 学习
    项目启动成功。

详解 pom.xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<!-- 定义基本 -->
	<groupId>com.lsy.security</groupId>
	<artifactId>demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>

	<name>demo</name>
	<!-- 描述 -->
	<description>Demo project for Spring Security</description>

	<!-- 关键 -->
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<!-- 指定springboot版本 -->
		<version>2.0.5.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>

		<!--
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		-->

		<!-- 核心依赖一  -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<!--
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>
		-->

		<!-- 核心依赖二  -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<!-- 核心依赖三  -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<!-- 编译的插件  -->
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>


</project>

  1. 先取消注释 Spring Security
  2. 编写 SpringSecurityConfig
package com.lsy.security.demo;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
 * 文件说明:制定安全策略
 * Created by SyLeo on 2018/10/14.
 */


/*
    Springboot推荐使用用java代码的形式申明注册bean。
    @Configuration注解可以用java代码的形式实现spring中xml配置文件配置的效果。
 */
@Configuration
@EnableWebSecurity //开启security安全声明
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    /*
    http请求的拦截
    //项目的主路径允许访问
    // 项目的其他请求都是需要进行验证
    //注销允许
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
       // http 安全过滤策略
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .anyRequest().authenticated()
                .and()
                .logout().permitAll()
                .and()
                .formLogin();
        http.csrf().disable();//关闭 csrf的认证
    }

    /*
    web资源的拦截
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        // 忽略对 js image 等文件的权限拦截
        web.ignoring().antMatchers("/js/**","/css/**","/images/**");
    }
}

跟拦截策略,主路径(http://localhost:8080)不拦截,其他都要拦截,可以测试一下,在DemoApplication类中,添加

 @RequestMapping("/hello")
    public String hello(){
        return "hello world";
    }

分别访问http://localhost:8080/ 和 http://localhost:8080/hello,前者直接跳转,没有任何影响,但是后者需要输入
权限管理系统 02 Spring Security 学习
权限管理系统 02 Spring Security 学习
至此,springboot+springsecurity环境成功。

实例编写

  • 只要登录即可。适用情形:组内可看,非机密,只需登录即可。且无需数据库。
  • 有指定的角色,每个角色有指定的权限

只要登录即可

SpringSecurity 提供了一套基于内存的验证。权限管理系统 02 Spring Security 学习
编写SpringSecurityConfig代码

    /*
    基于内存的验证
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 告诉系统,定义一个用户名(密码、角色),这是一个具有 ADMIN 角色权限的用户
        auth.inMemoryAuthentication().withUser("admin").password("123456").roles("ADMIN");
    }

  • 此时输入 admin 123456控制台会报错,这是版本的问题,点击了解

此时修改为

  /*
    解决 There is no PasswordEncoder mapped for the id "null"
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    /*
    基于内存的验证
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 告诉系统,定义一个用户名(密码、角色),这是一个具有 ADMIN 角色权限的用户
        auth.inMemoryAuthentication().withUser("admin").
                password(passwordEncoder().encode("111")).roles("ADMIN");
    }

或者直接

    /*
    基于内存的验证
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 告诉系统,定义一个用户名(密码、角色),这是一个具有 ADMIN 角色权限的用户
//        auth.inMemoryAuthentication().withUser("admin").
//                password(passwordEncoder().encode("111")).roles("ADMIN");

        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).
                withUser("admin").password(new BCryptPasswordEncoder().
                encode("123456")).roles("Admin");

    }

此时如果在地址栏输入http://localhost:8080/logout,则会跳出登录,再次进入http://localhost:8080/hello,需要重新登录。

有指定的角色,每个角色有指定的权限

优缺点总结