这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

SpringBoot Security

SpringSecurity安全管理

1 - SpringSecurity

SpringBoot Security基础系列教程,目标是实现权限管理的无障碍接入使用

1.1 - 0.起源篇(零)

本篇为SpringSecurity的第一篇,主要来介绍下什么是SpringSecurity,以及在springboot中如何使用它

I. 基本知识点

官方文档: https://docs.spring.io/spring-security/site/docs/5.2.2.BUILD-SNAPSHOT/reference/htmlsingle/#community-help

下面是官方介绍

Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.

Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements

用国语,简单抽象的说一下它的定义

  • 很🐂的认证和访问权限校验框架

那么具体能干嘛?

  • 用户登录认证:用户名+密码登录,确定用户身份
  • 用户访问鉴权(常见的ACL访问控制列表,RBAC角色访问控制):判定是否有权限访问某个资源
  • 安全保护(CSRF跨站点攻击,Session Fixation会话固定攻击…)

II. 初体验

接下来我们看一下再springboot中如何使用springsecurity

1. 配置

首先得是spring boot项目,然后添加上security的依赖即可,相对完整的pom配置如下(注意我们使用的springboot版本为2.2.1.RELEASE)

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.1.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
</properties>

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

<build>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </pluginManagement>
</build>
<repositories>
    <repository>
        <id>spring-snapshots</id>
        <name>Spring Snapshots</name>
        <url>https://repo.spring.io/libs-snapshot-local</url>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
    </repository>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/libs-milestone-local</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
    <repository>
        <id>spring-releases</id>
        <name>Spring Releases</name>
        <url>https://repo.spring.io/libs-release-local</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
</repositories>

2. 实例demo

上面配置完之后,啥都不需要干,项目已经接入了spring security;项目中的服务都需要登录之后才能访问

// 程序启动类
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

// rest 服务
@RestController
public class IndexRest {

    @GetMapping(path = {"/", "/index"})
    public String index() {
        return "hello this is index!";
    }

    @GetMapping(path = "hello")
    public String hello(String name) {
        return "welcome " + name;
    }
}

当我们需要访问首页时,会发现直接302重定向到登录页面了,如下图

spring security默认给我们生成了一个用户名为user,密码为控制台中输出的一行日志如Using generated security password: aa410186-5c04-4282-b217-507ffb1f61eb

登录之后会重定向回我们之前访问的url,通过抓包可以看到,登录成功之后,会设置请求方的cookie,后续的请求携带cookie来表明用户身份

3. 基本配置

上面虽然演示了一个hello world的初体验项目,但是这个默认的用户名/密码有点鬼畜,默认的配置主要来自于org.springframework.boot.autoconfigure.security.SecurityProperties.User,下面是截图(所以前面的用户名为user)

接下来我们需要配置为对人类友好的方式,在项目的配置文件application.yml中,指定登录的用户名/密码

spring:
  security:
    user:
      name: yihuihui
      password: 123456

重启测试项目,使用新的用户名/密码(yihuihui/123456)就可以登录成功了;

4. 用户身份获取

上面虽然是一个简单的case,但还有一点不得不提一下,在我的接口中,虽然知道你登录了,但怎么知道你是谁呢?

我们可以直接通过HttpServletRequest#getRemoteUser()的方法来获取登录用户; 或者通过SecurityContextHolder.getContext().getAuthentication().getPrincipal()来获取授权信息

我们来写一个通用方法

public String getUser() {
    return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getRemoteUser();
}

// or
public Object getUser() {
    SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}

然后稍微改一下我们的服务接口

@GetMapping(path = {"/", "/index"})
public String index() {
    return "hello this is index! welcome " + getUser();
}

再次访问之后,结果如下

5. 小结

本文主要是spring security系列的起源篇,第一节介绍了下什么是SpringSecurity,有什么特点

  • spring security是一个很🐂🍺的认证(可以简单理解为登录验证)和鉴权(可简单理解为访问控制)框架
  • 三大特点:登录 + 鉴权 + 安全防护

第二节介绍了一个简单入门的HelloWorld实例

  • springboot项目,添加依赖 spring-boot-starter-security; 所有的http接口访问都需要登录,默认提供一个用户名为user,密码为控制台输出的UUID字符串
  • 通过spring.security.user.namespring.security.user.password来指定用户名密码
  • 通过HttpServletRequest#getRemoteUser()获取登录用户

那么问题来了,什么系统可能只有一个用户呢?要多用户怎么办?不同的用户不同的权限怎么办?某些接口所有人都可以访问又怎么办?

II. 其他

0. 项目

1.2 - 1.基于内存认证(一)

在第一篇的教程中,我们简单的了解了一下SpringSecurity的使用姿势,添加依赖,在application.yml文件中加几行配置,就可以实现一个基本的登录认证;

默认的配置只能设置一个账号,那么如果需要多个账号可以怎么支持呢?

本文将介绍一下基于内存的认证方式

I. 内存认证

基于内存保存认证信息的方式,本篇博文中,会介绍两种常见的使用姿势

0. 项目配置

环境配置和前面一致,相关内容可以参考博文: 191223-SpringBoot 整合 SpringSecurity 之起源篇(零)

1. WebSecurityConfigurerAdapter

这里主要是借助SpringSecurity的配置适配器来处理,下面是一个简单的case

@Configuration
public class SecurityAdapterConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 测试时,可以直接用下面的方式
        //        User.UserBuilder builder = User.withDefaultPasswordEncoder();
        User.UserBuilder builder = User.builder().passwordEncoder(passwordEncoder()::encode);
        auth.inMemoryAuthentication().withUser(builder.username("hui1").password("123456").roles("guest").build());
        auth.inMemoryAuthentication().withUser(builder.username("hui2").password("123456").roles("guest").build());
    }
}

主要逻辑在 configure这个方法中,但是需要注意,我们额外的设置了密码的加密方式, 当我们不设置这个的时候,实际登录的时候会发现,即便你输入了正确的用户名密码,也会提示失败(欢迎各位大佬实测一下)

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

其次,在创建用户的时候,需要注意的是,除了设置了用户名和密码之外,还给用户加上了一个角色,这个会在后续文章的RBAC(基于角色的授权)中介绍它的作用

2. UserDetailsService

这里介绍另外一种方式,在后面的db中保存认证信息时,也会用到;在SpringSecurity的实现中,通过 UserDetailService 这个bean来根据用户名查询对应的用户信息;所以我们只需要实现一个我们自定义的Bean来替换默认的,就可以来实现我们的目标

我们的配置类如下

@Configuration
public class SecurityAutoConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 基于内存的认证方式
     *
     * @param passwordEncoder
     * @return
     */
    @Bean
    public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
        User.UserBuilder users = User.builder().passwordEncoder(passwordEncoder::encode);
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(users.username("1hui").password("123456").roles("guest").build());
        manager.createUser(users.username("2hui").password("666666").roles("manager").build());
        manager.createUser(users.username("3hui").password("root").roles("admin").build());
        return manager;
    }
}

3. 测试

上面两种方式,都可以实现在内存中保存认证信息,接下来我们进入实测环节,首先写一个http接口

@RestController
public class IndexRest {

    public String getUser() {
        // 获取用户信息
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

        String userName;
        if (principal instanceof UserDetails) {
            userName = ((UserDetails) principal).getUsername();
        } else {
            userName = principal.toString();
        }
        return userName;
    }

    /**
     * @return
     */
    @GetMapping(path = {"/"})
    public String index() {
        return "hello this is index! welcome " + getUser();
    }
}

在实际测试时,上面两种case都是ok的,下面的演示过程主要是基于第二种方式给出的示例

II. 其他

0. 系列博文&项目源码

博文

源码