1、Spring Security介绍

Spring security,是一个强大的和高度可定制的身份验证和访问控制框架。它是确保基于Spring的应用程序的标准 ——来自官方参考手册

Spring securityshiro 一样,具有认证、授权、加密等用于权限管理的功能。和 shiro 不同的是,Spring security拥有比shiro更丰富的功能,并且,对于Springboot而言,Spring SecurityShiro更合适一些,因为都是Spring家族成员。今天,我们来为SpringBoot项目集成Spring Security

本文所使用的版本:

SpringBoot : 2.2.6.RELEASE

Spring Security : 5.2.2.RELEASE

2、配置Spring Security

SpringBoot中集成Spring Security很简单,只需要在pom.xml中添加下面代码就行:

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

这里可以不指定Spring Security的版本号,它会根据SpringBoot的版本来匹配对应的版本,SpringBoot版本是 2.2.6.RELEASE,对应Spring Security的版本是5.2.2.RELEASE

然后,我们就可以将springboot启动了。

当我们尝试访问项目时,它会跳转到这个界面来:

​ 对!在此之前,你什么也不用做。这就是Spring Security的优雅之处。你只需要引入Spring Security的包,它就能在你的项目中工作。因为它已经帮你实现了一个简单的登陆界面。根据官方介绍,登录使用的账号是user,密码是随机密码,这个随机密码可以在控制台中找到,类似这样的一句话:

	Using generated security password: 1cb77bc5-8d74-4846-9b6c-4813389ce096

​ Using generated security password后面的的就是系统给的随机密码,我们可以使用这个密码进行登录。随机密码在每一次启动服务后生成(如果你配置了热部署devtools,你得随时留意控制台了,因为每当你修改了代码,系统会自动重启,那时随机密码就会重新生成)。

​ 当然,这样的功能一定不是你想要的,也一定不会就这样拿给你的用户使用。那么,接下来,让我们把它配置成我们想要的样子。

​ 要实现自定义配置,首先要创建一个继承于WebSecurityConfigurerAdapter的配置类:

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter { }

​ 这里使用了@EnableWebSecurity注解,这个注解是Spring Security用于启用web安全的注解。具体实现,这里就不深入了。

​ 要实现自定义拦截配置,首先得告诉Spring Security,用户信息从哪里获取,以及用户对应的角色等信息。这里就需要重写WebSecurityConfigurerAdapterconfigure(AuthenticationManagerBuilder auth)方法了。这个方法将指使Spring Security去找到用户列表,然后再与想要通过拦截器的用户进行比对,再进行下面的步骤。

Spring Security的用户存储配置有多个方案可以选择,包括:

  • 内存用户存储
  • 数据库用户存储
  • LDAP用户存储
  • 自定义用户存储

​ 我们分别来看看这几种用户存储的配置方法:

1.内存用户存储

​ 此配置方式是直接将用户信息存储在内存中,这种方式在速度上无疑是最快的。但只适用于有限个用户数量,且这些用户几乎不会发生改变。我们来看看配置方法:

	@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(passwordEncoder())
.withUser("zhangsan").password(passwordEncoder().encode("123456")).authorities("ADMIN")
.and()
.withUser("lisi").password(passwordEncoder().encode("123456")).authorities("ORDINARY");
} private PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

​ 可以看到,AuthenticationManagerBuilder使用构造者方式来构建的。在上面方法中,先调用了inMemoryAuthentication()方法,它来指定用户存储在内存中。接下来又调用了passwordEncoder()方法,这个方法的作用是告诉Spring Security认证密码的加密方式。因为在Spring security5过后,必须指定某种加密方式,不然程序会报错。接下来调用的withUser()、password()、authorities()方法,分别是在指定用户的账号、密码以及权限名。在添加完一个用户后,要使用and()方法来连接下一个用户的添加。

​ 如果使用这种配置方法,你会发现,在修改用户时,就必须修改代码。对于绝大多数项目来说,这种方式是满足不了需求的,至少我们需要一个注册功能。

2.数据库用户存储

​ 将用户信息存储在数据库中,让我们可以很方便地对用户信息进行增删改查。并且还可以为用户添加除认证信息外的附加信息,这样的设计也是我们很多小心应用所采取的方式。让我们来实现以下:

	@Autowired
private DataSource dataSource; @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource).passwordEncoder(passwordEncoder())
.usersByUsernameQuery(
"select username, password, status from Users where username = ?")
.authoritiesByUsernameQuery(
"select username, authority from Authority where username = ?"); } private PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

​ 调用jdbcAuthentication()来告诉Spring Security使用jdbc的方式来查询用户和权限,dataSource()方法指定数据库连接信息,passwordEncoder()指定密码加密规则,用户的密码数据应该以同样的方式进行加密存储,不然,两个加密方式不同的密码,匹配补上。usersByUsernameQuery()authoritiesByUsernameQuery()方法分别定义了查询用户和权限信息的sql语句。其实,Spring security为我们默认了查询用户、权限甚至还有群组用户授权的sql,这三条默认的sql存放在org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl中,有兴趣的小伙伴可以点进去看看。如果你要使用默认的,那你的表中关键性的字段必须和语句中的一致。

​ 使用数据库来存储用户和权限等信息已经可以满足大部分的需求。但是Spring security还为我们提供了另外一种配置方式,让我们来看一下。

3.LDAP用户存储

LDAP:轻型目录访问协议,是一个开放的,中立的,工业标准的应用协议,通过IP协议提供访问控制和维护分布式信息的目录信息。简单来说,就是将用户信息存放在另外一台服务器中(当然,也可以在同一台服务器,但我们一般不这么做),通过网络来进行访问的技术。

​ 我们来简单配置一下:

	@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
LdapAuthenticationProviderConfigurer<AuthenticationManagerBuilder> configurer = auth.ldapAuthentication()
.userSearchBase("ou=people")
.userSearchFilter("(uid={0})")
.groupSearchBase("ou=groups")
.groupSearchFilter("member={0}"); configurer.passwordCompare()
.passwordEncoder(passwordEncoder())
.passwordAttribute("passcode");
configurer.contextSource().url("ldap://xxxxx.com:33389/dc=xxxxxx,dc=com");
} private PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

userSearchFilter()groupSearchFilter()设置的是用户和群组的过滤条件,而userSearchBase()groupSearchBase()设置了搜索起始位置,contextSource().url()设置LDAP服务器的地址。如果没有远程的服务器可以使用contextSource().root()来使用嵌入式LDAP服务器,此方式将使用项目中的用户数据文件来提供认证服务。

​ 如果以上几种方式还不能满足我们的需求,我们可以用自定义的方式来配置。

4.自定义用户存储

​ 自定义用户存储,就是自行使用认证名称来查找对应的用户数据,然后交给Spring Security使用。我们需要定义一个实现UserDetailsServiceservice类:

@Service
public class MyUserDetailsService implements UserDetailsService{ @Autowired
private UserMapper userMapper; @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.getUserByUsername(username);
return user == null ? new User() : user;
}
} public class User implements UserDetails {
...
}

​ 该类只需要实现一个方法:loadUserByUsername()。该方法需要做的是使用传过来的username来匹配一个带有密码等信息的用户实体。需要注意的是这里的User类需要实现UserDetails,也就是说,查到的信息里,必须得有Spring Security所需要的信息。

​ 下面,让我们来继续配置:


@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired
private MyUserDetailsService userDetailsService; @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
} @Bean
private PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

​ 这样的配置方法就很简单了,只需要告诉Spring Security你的UserDetailsService实现类是哪个就可以了,它会去调用loadUserByUsername()来查找用户。

​ 以上就是Spring Security所提供的4种用户存储方式,接下来,需要考虑的是,怎么拦截请求。

3、请求拦截

1.安全规则

Spring Security的请求拦截配置方法是用户存储配置方法的重载方法,我们先来简单配置一下:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/user", "/menu")
.hasRole("ADMIN")
.antMatchers("/", "/**").permitAll();
}
}

​ 调用authorizeRequests()方法后,就可以添加自定义拦截路径了。antMatchers()方法配置了请求路径,hasRole()permitAll()指定了访问规则,分别表示拥有“ADMIN”权限的用户才能访问、所有用户可以访问。

​ 需要注意的是:这里的配置需要成对出现,并且配置的顺序也很重要。声明在前面的规则拥有更高的优先级。也就是说,如果我们将.antMatchers("/", "/").permitAll()**放到了最前面,像这样:

@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/**").permitAll()
.antMatchers("/user", "/menu")
.hasRole("ADMIN");
}

​ 那么,下面的"/user"和 "/menu"的配置是徒劳,因为前面的规则已经指明所有路径能被所有人访问。当然权限的规则方法还有很多,我这里只列举了两个。以下为常见的内置表达式:

表达 描述
hasRole(String role) 返回true当前委托人是否具有指定角色。例如, hasRole('admin')默认情况下,如果提供的角色不是以“ ROLE_”开头,则会添加该角色。可以通过修改defaultRolePrefixon来自定义DefaultWebSecurityExpressionHandler
hasAnyRole(String… roles) 返回true当前委托人是否具有提供的任何角色(以逗号分隔的字符串列表形式)。例如, hasAnyRole('admin', 'user')默认情况下,如果提供的角色不是以“ ROLE_”开头,则会添加该角色。可以通过修改defaultRolePrefixon来自定义DefaultWebSecurityExpressionHandler
hasAuthority(String authority) 返回true当前委托人是否具有指定权限。例如, hasAuthority('read')
hasAnyAuthority(String… authorities) 返回true如果当前主体具有任何所提供的当局的(给定为逗号分隔的字符串列表)例如, hasAnyAuthority('read', 'write')
principal 允许直接访问代表当前用户的主体对象
authentication 允许直接访问AuthenticationSecurityContext
permitAll 始终评估为 true
denyAll 始终评估为 false
isAnonymous() 返回true当前委托人是否为匿名用户
isRememberMe() 返回true当前主体是否是“记住我”的用户
isAuthenticated() true如果用户不是匿名的,则返回
isFullyAuthenticated() 返回true如果用户不是匿名或记得,我的用户
hasPermission(Object target, Object permission) 返回true用户是否可以访问给定权限的给定目标。例如,hasPermission(domainObject, 'read')
hasPermission(Object targetId, String targetType, Object permission) 返回true用户是否可以访问给定权限的给定目标。例如,hasPermission(1, 'com.example.domain.Message', 'read')

除此之外,还有一个支持SpEL表达式计算的方法,它的使用方法如下:

	@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/user", "/menu")
.access("hasRole('ADMIN')")
.antMatchers("/", "/**").permitAll();
}

​ 它所实现的规则和上面的方法一样。Spring Security还提供了其他丰富的SpEL表达式,如:

表达 描述
hasRole(String role) 返回true当前委托人是否具有指定角色。例如, hasRole('admin')默认情况下,如果提供的角色不是以“ ROLE_”开头,则会添加该角色。可以通过修改defaultRolePrefixon来自定义DefaultWebSecurityExpressionHandler
hasAnyRole(String… roles) 返回true当前委托人是否具有提供的任何角色(以逗号分隔的字符串列表形式)。例如, hasAnyRole('admin', 'user')默认情况下,如果提供的角色不是以“ ROLE_”开头,则会添加该角色。可以通过修改defaultRolePrefixon来自定义DefaultWebSecurityExpressionHandler
hasAuthority(String authority) 返回true当前委托人是否具有指定权限。例如, hasAuthority('read')
hasAnyAuthority(String… authorities) 返回true如果当前主体具有任何所提供的当局的(给定为逗号分隔的字符串列表)例如, hasAnyAuthority('read', 'write')
principal 允许直接访问代表当前用户的主体对象
authentication 允许直接访问AuthenticationSecurityContext
permitAll 始终评估为 true
denyAll 始终评估为 false
isAnonymous() 返回true当前委托人是否为匿名用户
isRememberMe() 返回true当前主体是否是“记住我”的用户
isAuthenticated() true如果用户不是匿名的,则返回
isFullyAuthenticated() 返回true如果用户不是匿名或记得,我的用户
hasPermission(Object target, Object permission) 返回true用户是否可以访问给定权限的给定目标。例如,hasPermission(domainObject, 'read')
hasPermission(Object targetId, String targetType, Object permission) 返回true用户是否可以访问给定权限的给定目标。例如,hasPermission(1, 'com.example.domain.Message', 'read')
2.登录

​ 如果此时,我们有自己的登录界面,需要替换掉Spring Security所提供的默认的界面,这时可以用fromLogin()loginPage()方法来实现:

	@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/user", "/menu")
.access("hasRole('ADMIN')")
.antMatchers("/", "/**").permitAll()
.and()
.formLogin()
.loginPage("/login");
}

​ 这便将登录地址指向了“/login”。如果需要指定登录成功时,跳转的地址,可以使用defaultSuccessUrl()方法:

		   .and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/home")

​ 此时用户登录过后,将跳转到主页来。

​ 下面,我们来看看登出。

3.登出

​ 和登录类似的,可以使用logout()logoutSuccessUrl()方法来实现:

			.and()
.logout()
.logoutSuccessUrl("/login")

​ 上面例子中,用户登出后将跳转到登录界面。

4、小结

至此,我们已基本了解了Spring Security配置,可以将它配置成我们想要的样子(基本)。其实Spring Security能做的事还有很多,光看我这篇文章是不够的。学习它最有效的方法就是阅读官方文档。里面有关于Spring Security最全最新的知识!(官网地址:https://spring.io/projects/spring-security)


公众号:良许Linux

有收获?希望老铁们来个三连击,给更多的人看到这篇文章

SpringBoot集成Spring Security的更多相关文章

  1. SpringBoot集成Spring Security入门体验

    一.前言 Spring Security 和 Apache Shiro 都是安全框架,为Java应用程序提供身份认证和授权. 二者区别 Spring Security:重量级安全框架 Apache S ...

  2. SpringBoot集成Spring Security(7)——认证流程

    文章目录 一.认证流程 二.多个请求共享认证信息 三.获取用户认证信息 在前面的六章中,介绍了 Spring Security 的基础使用,在继续深入向下的学习前,有必要理解清楚 Spring Sec ...

  3. SpringBoot集成Spring Security(6)——登录管理

    文章目录 一.自定义认证成功.失败处理 1.1 CustomAuthenticationSuccessHandler 1.2 CustomAuthenticationFailureHandler 1. ...

  4. SpringBoot集成Spring Security(5)——权限控制

    在第一篇中,我们说过,用户<–>角色<–>权限三层中,暂时不考虑权限,在这一篇,是时候把它完成了. 为了方便演示,这里的权限只是对角色赋予权限,也就是说同一个角色的用户,权限是 ...

  5. SpringBoot集成Spring Security(4)——自定义表单登录

    通过前面三篇文章,你应该大致了解了 Spring Security 的流程.你应该发现了,真正的 login 请求是由 Spring Security 帮我们处理的,那么我们如何实现自定义表单登录呢, ...

  6. SpringBoot集成Spring Security(2)——自动登录

    在上一章:SpringBoot集成Spring Security(1)——入门程序中,我们实现了入门程序,本篇为该程序加上自动登录的功能. 文章目录 一.修改login.html二.两种实现方式 2. ...

  7. SpringBoot 集成Spring security

    Spring security作为一种安全框架,使用简单,能够很轻松的集成到springboot项目中,下面讲一下如何在SpringBoot中集成Spring Security.使用gradle项目管 ...

  8. SpringBoot集成Spring Security(1)——入门程序

    因为项目需要,第一次接触 Spring Security,早就听闻 Spring Security 功能强大但上手困难,学习了几天出入门道,特整理这篇文章希望能让后来者少踩一点坑(本文附带实例程序,请 ...

  9. SpringBoot集成Spring Security(授权与认证)

    ⒈添加starter依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifact ...

随机推荐

  1. Java实现 LeetCode 7整数反转

    7. 整数反转 给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转. 示例 1: 输入: 123 输出: 321 示例 2: 输入: -123 输出: -321 示例 3: 输入: ...

  2. Java实现奇偶数排序

    1 问题描述 给定一个整数数组,请调整 数组中数的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分.要求时间复杂度为O(n). 2 解决方案 2.1 一头一尾指针往中间扫描法 pack ...

  3. PAT 反转链表

    给定一个常数 K 以及一个单链表 L,请编写程序将 L 中每 K 个结点反转.例如:给定 L 为 1→2→3→4→5→6,K 为 3,则输出应该为 3→2→1→6→5→4:如果 K 为 4,则输出应该 ...

  4. redis基础知识详解

    一.redis基础知识 1.Redis是什么Redis是一个开源的key-value存储系统. 和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表 ...

  5. python—异常处理

    一:什么是异常? (异常就是程序运行时发生错误的信号) 错误分两种: 1.语法错误 2.逻辑错误 二:异常的种类? (在python中不同的异常可以用不同的类型(python中统一了类与类型,类型即类 ...

  6. kali设置NAT模式,无法正常上网请试试这个办法

    1.释放网卡: dhclient -r eth0 2.自动获取网络 dhclient -v eth0 3.开启22端口 lsof -i :22 4.打开ssh service ssh start sy ...

  7. abp-CMS模块-广告

    无论是开发app还是网站,可能都需要一个广告功能,比如我们常见的在首页有个轮播广告,里面会轮播显示多个图片.还有比如一个新闻门户网站 很常见的 banner横幅广告,还有js特效广告等.本篇说说在ab ...

  8. C#数据结构与算法系列(一):介绍

    1.介绍 数据结构:是指相互之间存在一种或多种特定关系的数据元素的集合用计算机存储.组织数据的方式.数据结构分别为逻辑结构.(存储)物理结构和数据的运算三个部分. 数据结构包括:线性结构和非线性结构. ...

  9. RFID-RC522 模块的读写操作【Arduino】

    接线 Arduino Uno <-> RFID-RC52210 <-> SDA13 <-> SCK11 <-> MOSI12 <-> MIS ...

  10. ca74a_c++__文件流对象的使用-用来读写文件ifstream

    /*ca74a_c++__文件流对象的使用-用来读写文件将文件流对象绑定到文件上检查文件是否打开成功将文件流与新文件重新绑定清楚文件流的状态infile.close();//关闭流 infile.cl ...