说说 Spring Security 账户权限体系

说说 Spring Security 账户权限体系自定义服务。 不管使用哪种方法,都是通过覆盖 WebSecurityConfigurerAdapter 基础配置类中定义的 configure() 方法来实现的。 auth.inMemoryAuthentication() 会返回 InMemoryUserDetailsMana…

Spring Security 为这套账户体系提供了多种方案,

  1. 基于内存;

  2. 基于 JDBC;

  3. 基于 LDAP;

  4. 自定义服务。

不管使用哪种方法,都是通过覆盖 WebSecurityConfigurerAdapter 基础配置类中定义的 configure() 方法来实现的。

1 基于内存方式

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("deniro")
                .password("2382")
                .authorities("ROLE_USER")
                .and()
                .withUser("echo")
                .password("2382")
                .authorities("ROLE_USER");
    }
}

auth.inMemoryAuthentication() 会返回 InMemoryUserDetailsManagerConfigurer 对象,这个对象拥有 withUser() 方法,每次调用后都会配置一个用户放在内存中。withUser() 方法的入参是账户名,而密码和授权信息是通过 password() 和 authorities() 方法来指定的。

这种方式使用简单,适用于测试或简单的应用,但不方便维护。因为是硬编码,所以如果需要新增 、 删除或修改账户,就必须改代码,然后重新构建和部署应用。

2 基于 JDBC 方式

一般情况下,我们会把账户信息存储在数据库中,这时就会用到基于 JDBC 方式来配置账户。

(1)基础代码

基础代码模板如下:

    @Autowired
    DataSource dataSource;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.jdbcAuthentication()
                .dataSource(dataSource);
                
    }

这里使用注入方式来初始化 dataSource。

(2)基于角色的权限体系

springframework security 默认使用基于角色的权限体系的 5 张表来来存放账户、权限与角色信息。

说说 Spring Security 账户权限体系

从图中的关系中可以看出,权限不仅仅可以赋予给一个组,也可以赋予给一个账户。

如果应用中已经有权限体系,我们也可以自定义。可以自定义以下三条 SQL 语句。

  1. 依据账号查询账户(username,password,enabled);
  2. 依据账号查询权限(username,authority);
  3. 依据账号查询组名与权限(id, group_name, authority )。

这些语句可以在 JdbcDaoImpl.java 中看到实现 SQL:

public static final String DEF_USERS_BY_USERNAME_QUERY = "select username,password,enabled "
		+ "from users " + "where username = ?";
public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY = "select username,authority "
		+ "from authorities " + "where username = ?";
public static final String DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY = "select g.id, g.group_name, ga.authority "
		+ "from groups g, group_members gm, group_authorities ga "
		+ "where gm.username = ? " + "and g.id = ga.group_id "
		+ "and g.id = gm.group_id";

(3)自定义权限体系

JdbcUserDetailsManagerConfigurer 对象支持连缀编程,我们可以通过连缀编程来自定义这三条语句,形如:

 auth.jdbcAuthentication()
                .dataSource(dataSource)
                .usersByUsernameQuery(
                        "select username,password,enabled from Users where username=?"
                ).authoritiesByUsernameQuery(
                        "select username,authority from UserAuthorities where username=?"
        ).groupAuthoritiesByUsername("xxx");

注意:查询出来的字段名必须与默认语句的字段名一致。所以不是完全灵活的自定义,而是必须遵守一定的规范。

(4)加密存储

一般来说为了安全起见,数据库中的密码都必须是密文的。 Spring Security 已经考虑到了这一点。JdbcUserDetailsManagerConfigurer 类定义了一个 passwordEncoder() 方法,接受指定密码编码器作为入参。调用方式形如:

auth.jdbcAuthentication()
          .dataSource(dataSource)
          ...
          .passwordEncoder(new StandardPasswordEncoder("xxx"));

StandardPasswordEncoder 类在 Spring Boot2.x 中已经不再推荐使用了,因为已经不再安全。不用这个类也没关系,因为只要实现了 PasswordEncoder 接口的类,就可以作为 passwordEncoder() 方法的入参。

Security 的加密模块中定义很多这样的 PasswordEncoder 接口的实现类 。

  1. BCryptPasswordEncoder :使用 BCrypt 加密(推荐) 。
  2. NoOpPasswordEncoder :不进行任何转码 。
  3. Pbkdf2PasswordEncoder :使用 PBKDF2 加密(推荐) 。
  4. SCryptPasswordEncoder :使用 SCrypt 加密(推荐) 。

因为是单向加密,所以用户在登录时,会按照指定的算法,加密输入的密码,然后再与数据库中已经转码过的密码进行比对。这实际上会调用 PasswordEncoder 接口的 matches() 方法。该方法定义如下:

boolean matches(CharSequence rawPassword, String encodedPassword);

3 基于 LDAP 方式

(1)概念

LDAP 是轻型目录访问协议,它是是一个开放的,中立的,工业标准的应用协议,通过 IP 协议提供访问控制和维护分布式信息的目录信息。

目录服务是一种特殊的数据库,用来保存描述性的、基于属性的详细信息,支持过滤功能。它是动态的,灵活的,易扩展的。像人员组织管理,电话簿,地址簿等等就比较适合以目录的形式提供服务。

目录数据库和关系数据库不同,它有很好地读性能,但写性能差,并且没有事务处理、回滚等复杂功能,所以不适于存储修改频繁的数据。更适合应用与查询场景。

(2)基本用法

Spring Security 中,AuthenticationManagerBuilder 类定义了 ldapAuthentication() 方法支持 LDAP 认证方式。

首先在项目中的 pom.xml 中加入 spring-security-ldap 依赖包:

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

如果未正确引入依赖,就会抛出 Caused by: java.lang.ClassNotFoundException: org.springframework.security.ldap.DefaultSpringSecurityContextSource

接着在 SecurityConfig 类的 configure() 方法中配置 LDAP 服务器:

auth.ldapAuthentication()
              .userSearchFilter("(uid={0})")
              .groupSearchFilter("member={0}");

ldapAuthentication() 方法会返回 LdapAuthenticationProviderConfigurer 类,它定义的 userSearchFilter() 与 groupSearchFilter() 方法,可分别用于查询账户与组。

默认情况下,会从 LDAP 层级结构的根开始查询。我们也可以指定查询起始点。

auth.ldapAuthentication()
              .userSearchBase("ou=apple")
              .userSearchFilter("(uid={0})")
              .groupSearchBase("ou=fruits")
              .groupSearchFilter("member={0}");

userSearchBase() 方法指定账户从名为 apple 的组织下开始查询;而 groupSearchBase() 方法则指定组从名为 fruits 的组织下开始查询。

(3)密码比对策略

LDAP 认证的默认策略是绑定,即直接通过 LDAP 服务器来认证账户。也可以改为采用密码比对策略。这就需要将输入的密码发送到 LDAP 目录上,LDAP 服务器会比对这个密码和账户的密码。因为只有 LDAP 服务器持有账户的密码,所以对应用来说,是不知道账户的真实密码的,这就进一步提升了账户密码的安全性。

改为采用密码比对策略很简单,只需要在 auth.ldapAuthentication() 之后加上 .passwordCompare() 方法即可:

 auth.ldapAuthentication()
                .userSearchBase("ou=apple")
                .userSearchFilter("(uid={0})")
                .groupSearchBase("ou=fruits")
                .groupSearchFilter("member={0}")
                //密码比对策略
                .passwordCompare();

默认情况下,用户输入的密码会与 LDAP 服务器中的 userPassword 属性进行比对。如果密码被定义在其它属性中,我们可以通过passwordAttribute() 方法来指定实际存放密码的属性名称。

auth.ldapAuthentication()
        .userSearchBase("ou=apple")
        .userSearchFilter("(uid={0})")
        .groupSearchBase("ou=fruits")
        .groupSearchFilter("member={0}")
        //密码比对策略
        .passwordCompare()
        //指定实际存放密码的属性名称
        .passwordAttribute("pwd");

(4)远程 LDAP 服务器

默认情况下, Spring Security 会监听本机的 33389 端口,即假设 LDAP 服务与应用服务在同一台机器上。可以通过 contextSource() 方法来配置远程 LDAP 服务器。

因为 contextSource() 方法返回的是 ContextSourceBuilder,所以不能把它挂在之前的连缀编码代码段下,必须另起炉灶。

LdapAuthenticationProviderConfigurer<AuthenticationManagerBuilder> configurer = auth.ldapAuthentication();
...

//指定 LDAP 服务器
configurer.contextSource()
        .url("ldap://xxx:xxx");

(5)嵌入式 LDAP 服务器

利用 contextSource() 的 root() 方法就可以开启嵌入式 LDAP 服务器:

configurer.contextSource()
                .root("");

这时启动应用,就会在日志中看到启动的本地嵌入式 LDAP 服务器的 URL 地址:

说说 Spring Security 账户权限体系

当 LDAP 服务器启动时,它会在类路径下查找 LDIF 文件来加载数据。 LDIF ( LDAP Data Interchange Format)是 LDAP 服务器的数据交换格式。


关于如何自定义账户体系,会另开一篇文章详述。

今天的文章说说 Spring Security 账户权限体系分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/22692.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注