SpringBoot集成Spring Security

一、Spring Security介绍

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

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

本文所使用的版本:web

SpringBoot : 2.2.6.RELEASESpring Security : 5.2.2.RELEASE面试

二、配置Spring Security

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

而后,咱们就能够将springboot启动了。数据库

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

image-20200415201525535

​ 对!在此以前,你什么也不用作。这就是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,用户信息从哪里获取,以及用户对应的角色等信息。这里就须要重写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种用户存储方式,接下来,须要考虑的是,怎么拦截请求。

三、请求拦截

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")复制代码

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

四、小结

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


最后,最近不少小伙伴找我要Linux学习路线图,因而我根据本身的经验,利用业余时间熬夜肝了一个月,整理了一份电子书。不管你是面试仍是自我提高,相信都会对你有帮助!目录以下:

免费送给你们,只求你们金指给我点个赞!

电子书 | Linux开发学习路线图

也但愿有小伙伴能加入我,把这份电子书作得更完美!

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

推荐阅读:

相关文章
相关标签/搜索