Spring security,是一个强大的和高度可定制的身份验证和访问控制框架。它是确保基于Spring的应用程序的标准 ——来自官方参考手册java
Spring security 和 shiro 同样,具备认证、受权、加密等用于权限管理的功能。和 shiro 不一样的是,Spring security拥有比shiro更丰富的功能,而且,对于Springboot而言,Spring Security比Shiro更合适一些,由于都是Spring家族成员。今天,咱们来为SpringBoot项目集成Spring Security。程序员
本文所使用的版本:web
SpringBoot : 2.2.6.RELEASE Spring Security : 5.2.2.RELEASE面试
在SpringBoot中集成Spring Security很简单,只须要在pom.xml中添加下面代码就行:spring
<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。sql
而后,咱们就能够将springboot启动了。数据库
当咱们尝试访问项目时,它会跳转到这个界面来:安全
对!在此以前,你什么也不用作。这就是Spring Security的优雅之处。你只须要引入Spring Security的包,它就能在你的项目中工做。由于它已经帮你实现了一个简单的登录界面。根据官方介绍,登陆使用的帐号是user,密码是随机密码,这个随机密码能够在控制台中找到,相似这样的一句话:springboot
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,用户信息从哪里获取,以及用户对应的角色等信息。这里就须要重写WebSecurityConfigurerAdapter的configure(AuthenticationManagerBuilder auth)方法了。这个方法将指使Spring Security去找到用户列表,而后再与想要经过拦截器的用户进行比对,再进行下面的步骤。
Spring Security的用户存储配置有多个方案能够选择,包括:
咱们分别来看看这几种用户存储的配置方法:
此配置方式是直接将用户信息存储在内存中,这种方式在速度上无疑是最快的。但只适用于有限个用户数量,且这些用户几乎不会发生改变。咱们来看看配置方法:
@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()方法来链接下一个用户的添加。
若是使用这种配置方法,你会发现,在修改用户时,就必须修改代码。对于绝大多数项目来讲,这种方式是知足不了需求的,至少咱们须要一个注册功能。
将用户信息存储在数据库中,让咱们能够很方便地对用户信息进行增删改查。而且还能够为用户添加除认证信息外的附加信息,这样的设计也是咱们不少当心应用所采起的方式。让咱们来实现如下:
@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还为咱们提供了另一种配置方式,让咱们来看一下。
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服务器,此方式将使用项目中的用户数据文件来提供认证服务。
若是以上几种方式还不能知足咱们的需求,咱们能够用自定义的方式来配置。
自定义用户存储,就是自行使用认证名称来查找对应的用户数据,而后交给Spring Security使用。咱们须要定义一个实现UserDetailsService的service类:
@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种用户存储方式,接下来,须要考虑的是,怎么拦截请求。
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_”开头,则会添加该角色。能够经过修改defaultRolePrefix on来自定义DefaultWebSecurityExpressionHandler 。 |
hasAnyRole(String… roles) |
返回true 当前委托人是否具备提供的任何角色(以逗号分隔的字符串列表形式)。例如, hasAnyRole('admin', 'user') 默认状况下,若是提供的角色不是以“ ROLE_”开头,则会添加该角色。能够经过修改defaultRolePrefix on来自定义DefaultWebSecurityExpressionHandler 。 |
hasAuthority(String authority) |
返回true 当前委托人是否具备指定权限。例如, hasAuthority('read') |
hasAnyAuthority(String… authorities) |
返回true 若是当前主体具备任何所提供的当局的(给定为逗号分隔的字符串列表)例如, hasAnyAuthority('read', 'write') |
principal |
容许直接访问表明当前用户的主体对象 |
authentication |
容许直接访问Authentication 从SecurityContext |
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_”开头,则会添加该角色。能够经过修改defaultRolePrefix on来自定义DefaultWebSecurityExpressionHandler 。 |
hasAnyRole(String… roles) |
返回true 当前委托人是否具备提供的任何角色(以逗号分隔的字符串列表形式)。例如, hasAnyRole('admin', 'user') 默认状况下,若是提供的角色不是以“ ROLE_”开头,则会添加该角色。能够经过修改defaultRolePrefix on来自定义DefaultWebSecurityExpressionHandler 。 |
hasAuthority(String authority) |
返回true 当前委托人是否具备指定权限。例如, hasAuthority('read') |
hasAnyAuthority(String… authorities) |
返回true 若是当前主体具备任何所提供的当局的(给定为逗号分隔的字符串列表)例如, hasAnyAuthority('read', 'write') |
principal |
容许直接访问表明当前用户的主体对象 |
authentication |
容许直接访问Authentication 从SecurityContext |
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') |
若是此时,咱们有本身的登陆界面,须要替换掉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")复制代码
此时用户登陆事后,将跳转到主页来。
下面,咱们来看看登出。
和登陆相似的,可使用logout()和logoutSuccessUrl()方法来实现:
.and()
.logout()
.logoutSuccessUrl("/login")复制代码
上面例子中,用户登出后将跳转到登陆界面。
至此,咱们已基本了解了Spring Security配置,能够将它配置成咱们想要的样子(基本)。其实Spring Security能作的事还有不少,光看我这篇文章是不够的。学习它最有效的方法就是阅读官方文档。里面有关于Spring Security最全最新的知识!(官网地址:spring.io/projects/sp…
最后,最近不少小伙伴找我要Linux学习路线图,因而我根据本身的经验,利用业余时间熬夜肝了一个月,整理了一份电子书。不管你是面试仍是自我提高,相信都会对你有帮助!目录以下:
免费送给你们,只求你们金指给我点个赞!
也但愿有小伙伴能加入我,把这份电子书作得更完美!
推荐阅读: