springsecurity快速了解

Vol.Richard      2022-04-10     177

关键词:

在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.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

springsecurity学习——快速开始(代码片段)

前言SpringSecurity是目前Java后台管理系统中使用最常见的安全认证框架之一。它的上手没Shiro那么直观。Shiro是可以直接开个javaSE应用验证的,但SpringSecurity基本要求和web关联起来。刚开始看甚至不知道从何入手。最近决定静下... 查看详情

认证和授权学习2:springboot中快速使用springsecurity

认证和授权学习2:springboot中快速使用springsecurity本文以一个示例工程记录下如何在springboot工程中快速的使用springsecurity,使用的springboot版本是目录认证和授权学习2:springboot中快速使用springsecurity一、创建工程,导入依赖二、默认... 查看详情

springboot学习——springboot快速整合使用springsecurity组

SpringSecurity简介springsecurity的核心功能为认证(Authentication),授权(Authorization),即认证用户是否能访问该系统,和授权用户可以在系统中进行哪些操作。引入springsecurity组件在pom.xml中加入<dependency><groupId>org.springframewo... 查看详情

springsecurity项目快速搭建

springSecurity的底层原理就是拦截器链。在上一篇文章使用注解方式搭建spring项目中,可以自定义一套拦截器,拦截实现spring提供的HandlerInterceptr接口,然后实现三个拦截器方法,在prexxx方法中定义验证逻辑。拦截器定义好后在servl... 查看详情

springsecurity入门

 接触SpringSecurity是因为在面试之前,面试官要我用SpringBoot+SpringSecurity实现用户登录校验的功能。在此之前接触过一些SpringBoot,对SpringSecurity则完全没有了解,只知道它是一个权限管理的框架。也好,借此机会写些文章记录下了... 查看详情

springsecurity学习——快速开始(代码片段)

前言SpringSecurity是目前Java后台管理系统中使用最常见的安全认证框架之一。它的上手没Shiro那么直观。Shiro是可以直接开个javaSE应用验证的,但SpringSecurity基本要求和web关联起来。刚开始看甚至不知道从何入手。最近决定静下... 查看详情

源码时代java干货分享|springsecurity快速上手秘籍(代码片段)

...#xff0c;今天咱们探讨的是Spring旗下的一个款认证工具:SpringSecurity,如今认证框架主流“shiro”和“SpringSecurity”,由于和Spring的无缝衔接, 查看详情

轻松上手springsecurity,oauth,jwt(代码片段)

目录学习目标一.SpringSecurity1.SpringSecurity简介及快速入门<1>.SpringSecurity简介<2>.SpringSecurity快速入门2.SpringSecurity基本原理<1>.UserDetailsService详解<2>.PasswordEncoder密码解析器详解3.SpringSecurity自定义 查看详情

springsecurity+jwt认证流程解析

参考技术A本文适合:对SpringSecurity有一点了解或者跑过简单demo但是对整体运行流程不明白的同学,对SpringSecurity有兴趣的也可以当作你们的入门教程,示例代码中也有很多注释。大家在做系统的时候,一般做的第一个模块就是认... 查看详情

springboot快速整合springsecurity,新手都会的详细步骤(代码片段)

一、什么是SpringSecurity?SpringSecurity是一个基于Spring框架的安全性框架,提供了一组轻量级的API和工具,用于实现身份验证、授权、防止攻击等常见的安全性功能。它支持各种身份验证方式,例如基本身份验证、表... 查看详情

springsecurity4实战与原理分析视频课程(扩展+自定义)

SpringSecurity概述与课程概要介绍SpringSecurity快速入门(基于XML)SpringSecurity快速入门(基于XML)URL匹配详解自定义登陆配置退出Ajax登陆退出JDBC认证层级角色关系认证体系介绍自定义认证匿名认证认证流程分析配置权限授权体系介... 查看详情

springsecurity4实战与原理分析视频课程(扩展+自定义)

SpringSecurity概述与课程概要介绍SpringSecurity快速入门(基于XML)SpringSecurity快速入门(基于XML)URL匹配详解自定义登陆配置退出Ajax登陆退出JDBC认证层级角色关系认证体系介绍自定义认证匿名认证认证流程分析配置权限授权体系介... 查看详情

springsecurity架构原理学习笔记

引用SpringSecurity的工作原理(简单)https://blog.csdn.net/wu2374633583/article/details/108199205一文带你了解强大的SpringSecurity架构原理!(全面)https://zhuanlan.zhihu.com/p/1000144561结构总览SpringSecurity所解决的问 查看详情

springsecurity知识盲区

Springsecurity知识盲区SpringSecurity中文文档JWT进阶--JJWTGenericFilterBean---MVC过滤器-超类注解EnableWebSecurity启用Web安全SpringSecurity相关资料SpringSecurity中文文档SpringSecurity中文文档JWT进阶–JJWT侧重用法,不够细致侧重用法,较为详... 查看详情

springsecurity问题记录

...。最近觉得应该去了解规范的用户及权限认证技术了,从SpringSecurity开始学习使用。本文记录学习SpringSecurity过程中遇到的一些问题及解决方案。1.版本问题SpringBoot2.x和SpringSecurity5.x前配置用户名密码:security.user.name=admi 查看详情

springsecurity:认证

快速入门1.引入依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>2.编写controller@RestControllerpubl 查看详情

一文快速了解iptables和systemctl|快速了解linux防火墙

docker网络配置以及docker操作CentOS7中默认的防火墙是FireWallfirewallsystemctlstopfirewalld.servicesystemctlstartfirewalld.servicesystemctlstatusnfs-server.servicesystemctlrestartnfs-server.servicesystemctllist-units-- 查看详情

《springsecurity框架专题》-01基础入门

文章目录1、安全框架概述2、常用安全框架2、SpringSecurity概述2.1.概述2.2.核心功能2.3.历史3、初识SpringSecurity3.1.SpringSecurity概念3.2.快速入门案例3.3.环境准备3.3.1创建web项目3.3.2导入相关的依赖3.3.3.创建相关配置文件3.3.4.web.xml设置3.4... 查看详情