java每日精进1.31(SpringSecurity)

在所有的开发的系统中,都必须做认证(authentication)和授权(authorization),以保证系统的安全性。

一、基础使用

1.依赖

java 复制代码
<dependencies>
        <!-- 实现对 Spring MVC 的自动化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- 实现对 Spring Security 的自动化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
    </dependencies>

2.application.yml

java 复制代码
spring:
  # Spring Security 配置项,对应 SecurityProperties 配置类
  security:
    # 配置默认的 InMemoryUserDetailsManager 的用户账号与密码。
    user:
      name: user # 账号
      password: user # 密码
      roles: ADMIN # 拥有角色

3.Controller

java 复制代码
@RestController
@RequestMapping("/admin")
public class AdminController {

    @GetMapping("/demo")
    public String demo() {
        return "success!";
    }

}

项目启动成功后,浏览器访问 http://127.0.0.1:8080/admin/demo 接口。因为未登录,所以被 Spring Security 拦截到登录界面。

因为我们没有自定义登录界面,所以默认会使用 DefaultLoginPageGeneratingFilter 类,生成上述界面。

登录完成后,因为 Spring Security 会记录被拦截的访问地址,所以浏览器自动动跳转返回界面;

二、自定义规则(自定义 Spring Security 的配置,实现权限控制。)

1.SecurityConfig

创建 SecurityConfig 配置类,继承 WebSecurityConfigurerAdapter 抽象类,实现 Spring Security 在 Web 场景下的自定义配置。代码如下:

java 复制代码
// SecurityConfig.java

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    // ...

}

重写 #configure(AuthenticationManagerBuilder auth) 方法,实现 AuthenticationManager 认证管理器。

java 复制代码
// SecurityConfig.java

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.
            //使用内存中的 InMemoryUserDetailsManager
            inMemoryAuthentication()
            //不使用 PasswordEncoder 密码编码器
            .passwordEncoder(NoOpPasswordEncoder.getInstance())
            //  配置 admin 用户
            .withUser("admin").password("admin").roles("ADMIN")
            // 配置 normal 用户
            .and().withUser("normal").password("normal").roles("NORMAL");
}

重写 #configure(HttpSecurity http) 方法,主要配置 URL 的权限控制。代码如下:

java 复制代码
// SecurityConfig.java

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            // <X> 配置请求地址的权限
            .authorizeRequests()
                .antMatchers("/test/echo").permitAll() // 所有用户可访问
                .antMatchers("/test/admin").hasRole("ADMIN") // 需要 ADMIN 角色
                .antMatchers("/test/normal").access("hasRole('ROLE_NORMAL')") // 需要 NORMAL 角色。
                // 任何请求,访问的用户都需要经过认证
                .anyRequest().authenticated()
            .and()
            // <Y> 设置 Form 表单登录
            .formLogin()
//                    .loginPage("/login") // 登录 URL 地址
                .permitAll() // 所有用户可访问
            .and()
            // 配置退出相关
            .logout()
//                    .logoutUrl("/logout") // 退出 URL 地址
                .permitAll(); // 所有用户可访问
}

调用 HttpSecurity#authorizeRequests() 方法,开始配置 URL 的权限控制 。注意看艿艿配置的四个权限控制的配置。下面,是配置权限控制会使用到的方法:

  • #(String... antPatterns) 方法,配置匹配的 URL 地址,可传入多个。
  • 【常用】#permitAll() 方法,所有用户可访问。
  • 【常用】#denyAll() 方法,所有用户不可访问。
  • 【常用】#authenticated() 方法,登录用户可访问。
  • #anonymous() 方法,无需登录,即匿名用户可访问。
  • #rememberMe() 方法,通过remember me 登录的用户可访问。
  • #fullyAuthenticated() 方法,非remember me 登录的用户可访问。
  • #hasIpAddress(String ipaddressExpression) 方法,来自指定 IP 表达式的用户可访问。
  • 【常用】#hasRole(String role) 方法, 拥有指定角色的用户可访问。
  • 【常用】#hasAnyRole(String... roles) 方法,拥有指定任一角色的用户可访问。
  • 【常用】#hasAuthority(String authority) 方法,拥有指定权限(authority)的用户可访问。
  • 【常用】#hasAuthority(String... authorities) 方法,拥有指定任一权限(authority)的用户可访问。
  • 【最牛】#access(String attribute) 方法,执行结果为 true 时,可以访问。

2.Controller

java 复制代码
@RestController
@RequestMapping("/test")
public class TestController {

    @GetMapping("/echo")
    public String demo() {
        return "示例返回";
    }

    @GetMapping("/home")
    public String home() {
        return "我是首页";
    }

    @GetMapping("/admin")
    public String admin() {
        return "我是管理员";
    }

    @GetMapping("/normal")
    public String normal() {
        return "我是普通用户";
    }

}
  • 对于 /test/echo 接口,直接访问,无需登录。
  • 对于 /test/home 接口,无法直接访问,需要进行登录。
  • 对于 /test/admin 接口,需要登录「admin/admin」用户,因为需要 ADMIN 角色。
  • 对于 /test/normal 接口,需要登录「normal/normal」用户,因为需要 NORMAL 角色。

3.Spring Security 的注解,实现权限控制

1.修改权限配置类

修该 security config 配置类,增加 @EnableGlobalMethodSecurity 注解,开启对 Spring Security 注解的方法,进行权限验证。代码如下:

java 复制代码
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter

然后即可添加方法级别的权限验证

java 复制代码
@PreAuthorize("hasRole('ROLE_NORMAL')")
    @GetMapping("/normal")
    public String normal() {
        return "我是普通用户";
    }

三、Spring Session

Session 的一致性,简单来理解,就是相同 sessionid 在多个 Web 容器下,Session 的数据要一致。

我们先以用户使用浏览器,Web 服务器为单台 TomcatA 举例子。

  • 浏览器在第一次访问 Web 服务器 TomcatA 时,TomcatA 会发现请求的 Cookie 中存在 sessionid ,所以创建一个 sessionid 为 X 的 Session ,同时将该 sessionid 写回给浏览器的 Cookie 中。
  • 浏览器在下一次访问 Web 服务器 TomcatA 时,TomcatA 会发现请求的 Cookie 中存在 sessionid 为 X ,则直接获得 X 对应的 Session 。

但是如果情况如下:

  • 浏览器访问 TomcatA ,获得 sessionid 为 X 。同时,在多台 Tomcat 的情况下,我们需要采用 Nginx 做负载均衡。
  • 浏览器又发起一次请求访问 Web 服务器,Nginx 负载均衡转发请求到 TomcatB 上。TomcatB 会发现请求的 Cookie 中存在 sessionid 为 X ,则直接获得 X 对应的 Session 。结果呢,找不到 X 对应的 Session ,只好创建一个 sessionid 为 X 的 Session 。
  • 此时,虽然说浏览器的 sessionid 是 X ,但是对应到两个 Tomcat 中两个 Session 。那么,如果在 TomcatA 上做的 Session 修改,TomcatB 的 Session 还是原样,这样就会出现 Session 不一致的问题。

三种解决方案:

第一种,Session 黏连

使用 Nginx 实现会话黏连,将相同 sessionid 的浏览器所发起的请求,转发到同一台服务器。这样,就不会存在多个 Web 服务器创建多个 Session 的情况,也就不会发生 Session 不一致的问题。

不过,这种方式目前基本不被采用。因为,如果一台服务器重启,那么会导致转发到这个服务器上的 Session 全部丢失。

第二种,Session 复制

Web 服务器之间,进行 Session 复制同步。仅仅适用于实现 Session 复制的 Web 容器,例如说 Tomcat 、Weblogic 等等。

不过,这种方式目前基本也不被采用。试想一下,如果我们有 5 台 Web 服务器,所有的 Session 都要同步到每一个节点上,一个是效率低,一个是浪费内存。

第三种,Session 外部化存储

不同于上述的两种方案,Session 外部化存储,考虑不再采用 Web 容器的内存中存储 Session ,而是将 Session 存储外部化,持久化到 MySQL、Redis、MongoDB 等等数据库中。这样,Tomcat 就可以无状态化,专注提供 Web 服务或者 API 接口,未来拓展扩容也变得更加容易。

而实现 Session 外部化存储也有两种方式:

① 基于 Tomcat、Jetty 等 Web 容器自带的拓展,使用读取外部存储器的 Session 管理器。例如说:tomcat 使用 Redis 存储 Session 、实现 Jetty 使用 MySQL、MongoDB 存储 Session 。

② 基于应用层封装 HttpServletRequest 请求对象,包装成自己的 RequestWrapper 对象,从而让实现调用 HttpServletRequest#getSession() 方法时,获得读写外部存储器的 SessionWrapper 对象。

依赖:

java 复制代码
<dependencies>
        <!-- 实现对 Spring MVC 的自动化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- 实现对 Spring Session 使用 Redis 作为数据源的自动化配置 -->
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>

        <!-- 实现对 Spring Data Redis 的自动化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <!-- 去掉对 Lettuce 的依赖,因为 Spring Boot 优先使用 Lettuce 作为 Redis 客户端 -->
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- 引入 Jedis 的依赖,这样 Spring Boot 实现对 Jedis 的自动化配置 -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

        <!-- 实现对 Spring Security 的自动化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        
    </dependencies>

配置文件:

java 复制代码
spring:
  # 对应 RedisProperties 类
  redis:
    host: 127.0.0.1
    port: 6379
    password: # Redis 服务器密码,默认为空。生产中,一定要设置 Redis 密码!
    database: 0 # Redis 数据库号,默认为 0 。
    timeout: 0 # Redis 连接超时时间,单位:毫秒。
    # 对应 RedisProperties.Jedis 内部类
    jedis:
      pool:
        max-active: 8 # 连接池最大连接数,默认为 8 。使用负数表示没有限制。
        max-idle: 8 # 默认连接数最大空闲的连接数,默认为 8 。使用负数表示没有限制。
        min-idle: 0 # 默认连接池最小空闲的连接数,默认为 0 。允许设置 0 和 正数。
        max-wait: -1 # 连接池最大阻塞等待时间,单位:毫秒。默认为 -1 ,表示不限制。
  # 对应 SecurityProperties 类
  security:
    user: # 配置内存中,可登录的用户名和密码
      name: nihao
      password: nihao

SessionConfiguration:

java 复制代码
@Configuration
@EnableRedisHttpSession // 自动化配置 Spring Session 使用 Redis 作为数据源
public class SessionConfiguration {

}
相关推荐
yyytucj2 分钟前
优化 PHP-FPM 参数配置:实现服务器性能提升
服务器·开发语言·php
鲤籽鲲7 分钟前
C# 中 [MethodImpl(MethodImplOptions.Synchronized)] 的使用详解
java·开发语言·c#
SomeB1oody15 分钟前
【Rust自学】19.5. 高级类型
开发语言·后端·设计模式·rust
逆风局?22 分钟前
Java基础——分层解耦——IOC和DI入门
java·开发语言
ybq1951334543141 分钟前
javaEE-8.JVM(八股文系列)
java·jvm·java-ee
通信.萌新1 小时前
【Qt】常用的容器
开发语言·qt
美味小鱼1 小时前
Rust枚举(Enum)完全指南:用类型安全表达多样性
开发语言·安全·rust
飞翔的佩奇1 小时前
Java项目: 基于SpringBoot+mybatis+maven+mysql实现的疾病防控综合管理系统(含源码+数据库+毕业论文)
java·数据库·spring boot·mysql·spring·毕业设计·疾病防控
power-辰南1 小时前
技术架构师成长路线(2025版)
java·架构师·学习路线·技术专家
二十雨辰1 小时前
[Java基础]面向对象
java·开发语言