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实现海盗比酒量

    ** 海盗比酒量** 有一群海盗(不多于20人),在船上比拼酒量.过程如下:打开一瓶酒,所有在场的人平分喝下,有几个人倒下了.再打开一瓶酒平分,又有倒下的,再次重复- 直到开了第4瓶酒,坐着的已经所剩 ...

  2. 7.keras-模型保存和载入

    keras-模型保存和载入 1.数据的载入与预处理 import numpy as np from keras.datasets import mnist from keras.utils impor ...

  3. 【1】svn 指令总结

    [1]svn log 1.svn log 2. [2]svn di [3]

  4. 【译】Introducing YARP Preview 1

    1 YARP YARP是一个项目,用于创建反向代理服务器.它开始于我们注意到来自微软内部团队的一系列问题.他们要么为其服务构建反向代理,要么询问 API 和用于构建 API 的技术.因此我们决定让他们 ...

  5. (六)TestNg中的软断言和硬断言

    原文链接:https://cloud.tencent.com/developer/article/1479172 前言 在执行自动化测试脚本的时候,我们需要自动判断测试脚本执行完成后的实际结果是否与预 ...

  6. Android开发学习笔记DDMS的使用

    打开DDMS DDMS 的全称是Dalvik Debug Monitor Service,是 Android 开发环境中的Dalvik虚拟机调试监控服务. DDMS里面包含了:Device(设备) F ...

  7. cb41a_c++_STL_算法_填充新值fill_generate

    cb41a_c++_STL_算法_填充新值fill_generatefill(b,e,v)fill_n(b,n,v),填充n个vgenerate(b,e,p)generate_n(b,n,p) gen ...

  8. 005.OpenShift访问控制-权限-角色

    一 Kubetcl namespace 1.1 namespace描述 Kubernetes namespace提供了将一组相关资源组合在一起的机制.在Red Hat OpenShift容器平台中,p ...

  9. 01 . 容器编排简介及Kubernetes核心概念

    Kubernetes简介 Kubernetes是谷歌严格保密十几年的秘密武器-Borg的一个开源版本,是Docker分布式系统解决方案.2014年由Google公司启动. Kubernetes提供了面 ...

  10. disruptor架构二

    小故事:Disruptor说的是生产者和消费者的故事. 有一个数组.生产者往里面扔芝麻.消费者从里面捡芝麻. 但是扔芝麻和捡芝麻也要考虑速度的问题. 1 消费者捡的比扔的快 那么消费者要停下来.生产者 ...