Spring Security(二)

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: Spring Security

内置访问控制方法

 Spring Security 匹配了 URL 后调用了permitAll()表示不需要认证,随意访问。在 Spring Security 中提供了多种内置控制。

permitAll()

permitAll()表示所匹配的 URL 任何人都允许访问。

ee4f08467a42dcf738ab8bfcd50b5e0a_202112081053847.png

authenticated()

authenticated()表示所匹配的 URL 都需要被认证才能访问。

1bdc427146c4e36823fc8528e554a31b_202112081053879.png

anonymous()

anonymous()表示可以匿名访问匹配的URL。和permitAll()效果类似,只是设置为 anonymous()的 url 会执行 filter 链中

87347b56ba7fde5a42096dc1e964a935_202112081053898.png

denyAll()

denyAll()表示所匹配的 URL 都不允许被访问。

bc3495890ffcb253769e4feed6280b2d_202112081053709.png

rememberMe()

被“remember me”的用户允许访问

38ebc6c640c72cc1bd49c77ed2bf9ee1_202112081053434.png

fullyAuthenticated()

如果用户不是被 remember me 的,才可以访问。

5a872d83c8f964ee13426984f77a00f4_202112081053199.png

角色权限判断

 除了之前讲解的内置权限控制。Spring Security 中还支持很多其他权限控制。这些方法一般都用于用户已经被认证后,判断用户是否具有特定的要求。

hasAuthority(String)

 判断用户是否具有特定的权限,用户的权限是在自定义登录逻辑中创建 User 对象时指定的。下图中 admin和normal 就是用户的权限。admin和normal 严格区分大小写。

4c470da36aa83f316652ce34f64fc19a_202112081053111.png

在配置类中通过 hasAuthority(“admin”)设置具有 admin 权限时才能访问。

.antMatchers("/main1.html").hasAuthority("admin")

hasAnyAuthority(String ...)

如果用户具备给定权限中某一个,就允许访问。

下面代码中由于大小写和用户的权限不相同,所以用户无权访问

.antMatchers("/main1.html").hasAnyAuthority("adMin","admiN")

hasRole(String)

如果用户具备给定角色就允许访问。否则出现 403。

参数取值来源于自定义登录逻辑 UserDetailsService实现类中创建 User 对象时给 User 赋予的授权。

 在给用户赋予角色时角色需要以:ROLE_开头,后面添加角色名称。例如:ROLE_abc 其中 abc 是角色名,ROLE_是固定的字符开头。

使用 hasRole()时参数也只写 abc 即可。否则启动报错。

给用户赋予角色:

e405db3db01abe89ff4d288689c64da4_202112081053448.png

 在配置类中直接写 abc 即可。

.antMatchers("/main1.html").hasRole("abc")

hasAnyRole(String ...)

如果用户具备给定角色的任意一个,就允许被访问

hasIpAddress(String)

如果请求是指定的 IP 就运行访问。

可以通过 request.getRemoteAddr()获取 ip 地址。

需要注意的是在本机进行测试时 localhost 和 127.0.0.1 输出的 ip地址是不一样的。

当浏览器中通过 localhost 进行访问时控制台打印的内容:

6712c1172f4d4169cadcabe55d7619ad_202112081053715.png

当浏览器中通过 127.0.0.1 访问时控制台打印的内容:

a43c5d3d14417f119499126d59c095b1_202112081053688.png

当浏览器中通过具体 ip 进行访问时控制台打印内容:

9020ec6ae99696768a2088f285df6b71_202112081053025.png

.antMatchers("/main1.html").hasIpAddress("127.0.0.1")

自定义403处理方案

使用 Spring Security 时经常会看见 403(无权限),默认情况下显示的效果如下:

41e63d927410a7a2755141612cb9141b_202112081053785.png

而在实际项目中可能都是一个异步请求,显示上述效果对于用户就不是特别友好了。Spring Security 支持自定义权限受限。

新建类

新建类实现 AccessDeniedHandler

package com.yjxxt.springsecuritydemo.handler;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
   @Override
   public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
      response.setStatus(HttpServletResponse.SC_FORBIDDEN);
      response.setHeader("Content-Type", "application/json;charset=utf-8");
      PrintWriter out = response.getWriter();
      out.write("{\"status\":\"error\",\"msg\":\"权限不足,请联系管理员!\"}");
      out.flush();
      out.close();
   }
}

修改配置类

配置类中重点添加异常处理器。设置访问受限后交给哪个对象进行处理。

myAccessDeniedHandler 是在配置类中进行自动注入的。

//异常处理
http.exceptionHandling()
      .accessDeniedHandler(myAccessDeniedHandler);

基于表达式的访问控制

access()方法使用

之前学习的登录用户权限判断实际上底层实现都是调用access(表达式)

063e89fb4f580b6b63d64baa508f37f4_202112081053974.png

可以通过access()实现和之前学习的权限控制完成相同的功能。

以 hasRole 和 和 permitAll 举例

0c9fbca20a5b5edb375011f8d8134e7c_202112081053280.png

使用自定义方法

虽然这里面已经包含了很多的表达式(方法)但是在实际项目中很有可能出现需要自己自定义逻辑的情况。

判断登录用户是否具有访问当前 URL 权限。

新建接口及实现类

MyService.java

package com.yjxxt.springsecuritydemo.service;
import org.springframework.security.core.Authentication;
import javax.servlet.http.HttpServletRequest;
public interface MyService {
   boolean hasPermission(HttpServletRequest request, Authentication authentication);
}

MyServiceImpl.java

package com.yjxxt.springsecuritydemo.service.impl;
import com.yjxxt.springsecuritydemo.service.MyService;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
@Component
public class MyServiceImpl implements MyService {
   @Override
   public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
      Object obj = authentication.getPrincipal();
      if (obj instanceof UserDetails){
         UserDetails userDetails = (UserDetails) obj;
         Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
         return authorities.contains(new SimpleGrantedAuthority(request.getRequestURI()));
      }
      return false;
   }
}
修改配置类

在 access 中通过@bean的id名.方法(参数)的形式进行调用配置类中修改如下:

//url拦截
http.authorizeRequests()
      //login.html不需要被认证
      // .antMatchers("/login.html").permitAll()
      .antMatchers("/login.html").access("permitAll")
      // .antMatchers("/main.html").hasRole("abc")
      .antMatchers("/main.html").access("hasRole('abc')")
      .anyRequest().access("@myServiceImpl.hasPermission(request,authentication)")

基于注解的访问控制

在 Spring Security 中提供了一些访问控制的注解。这些注解都是默认是都不可用的,需要通过@EnableGlobalMethodSecurity进行开启后使用。

如果设置的条件允许,程序正常执行。如果不允许会报 500

4c87f559087fc02994449b86c026b0a4_202112081053348.png

这些注解可以写到 Service 接口或方法上,也可以写到 Controller或 Controller 的方法上。通常情况下都是写在控制器方法上的,控制接口URL是否允许被访问。

@Secured

@Secured 是专门用于判断是否具有角色的。能写在方法或类上。参数要以 ROLE_开头。

开启注解

在 启 动 类 ( 也 可 以 在 配 置 类 等 能 够 扫 描 的 类 上 ) 上 添 加@EnableGlobalMethodSecurity(securedEnabled = true)

@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SpringsecurityDemoApplication {
   public static void main(String[] args) {
      SpringApplication.run(SpringsecurityDemoApplication.class, args);
   }
}
在控制器方法上添加@Secured 注解
/**
 * 成功后跳转页面
 * @return
 */
@Secured("ROLE_abc")
@RequestMapping("/toMain")
public String toMain(){
   return "redirect:/main.html";
}
配置类
@Override
protected void configure(HttpSecurity http) throws Exception {
   //表单提交
   http.formLogin()
         //自定义登录页面
         .loginPage("/login.html")
         //当发现/login时认为是登录,必须和表单提交的地址一样。去执行UserServiceImpl
         .loginProcessingUrl("/login")
         //登录成功后跳转页面,POST请求
         .successForwardUrl("/toMain")
   //url拦截
   http.authorizeRequests()
         //login.html不需要被认证
         .antMatchers("/login.html").permitAll()
         //所有请求都必须被认证,必须登录后被访问
         .anyRequest().authenticated();
   //关闭csrf防护
   http.csrf().disable();
}

@PreAuthorize/@PostAuthorize

@PreAuthorize 和@PostAuthorize 都是方法或类级别注解。

6732d935785dd663ed4d39efacaff85c_202112081053604.png

  • @PreAuthorize表示访问方法或类在执行之前先判断权限,大多情况下都是使用这个注解,注解的参数和access()方法参数取值相同,都是权限表达式。
  • @PostAuthorize 表示方法或类执行结束后判断权限,此注解很少被使用到。
开启注解
@SpringBootApplication
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringsecurityDemoApplication {
   public static void main(String[] args) {
      SpringApplication.run(SpringsecurityDemoApplication.class, args);
   }
}
添加@PreAuthorize

在控制器方法上添加@PreAuthorize,参数可以是任何 access()支持的表达式

/**
 * 成功后跳转页面
 * @return
 */
@PreAuthorize("hasRole('ROLE_abc')")
@RequestMapping("/toMain")
public String toMain(){
   return "redirect:/main.html";
}

RememberMe功能实现

 Spring Security 中 Remember Me 为“记住我”功能,用户只需要在登录时添加 remember-me复选框,取值为true。Spring Security 会自动把用户信息存储到数据源中,以后就可以不登录进行访问

添加依赖

 Spring Security 实 现 Remember Me 功 能 时 底 层 实 现 依 赖Spring-JDBC,所以需要导入 Spring-JDBC。以后多使用 MyBatis 框架而很少直接导入 spring-jdbc,所以此处导入 mybatis 启动器同时还需要添加 MySQL 驱动

<!-- mybatis 依赖 -->
<dependency>
   <groupId>org.mybatis.spring.boot</groupId>
   <artifactId>mybatis-spring-boot-starter</artifactId>
   <version>2.1.1</version>
</dependency>
<!-- mysql 数据库依赖 -->
<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>8.0.18</version>
</dependency>

配置数据源

在 application.properties 中配置数据源。请确保数据库中已经存在shop数据库

spring.datasource.driver-class-name= com.mysql.cj.jdbc.Driver
spring.datasource.url= jdbc:mysql://localhost:3306/security?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username= root
spring.datasource.password= root

编写配置

RememberMeConfig.java

package com.yjxxt.springsecuritydemo.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import javax.sql.DataSource;
@Configuration
public class RememberMeConfig {
   @Autowired
   private DataSource dataSource;
   @Bean
   public PersistentTokenRepository getPersistentTokenRepository(){
      JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
      jdbcTokenRepository.setDataSource(dataSource);
      //自动建表,第一次启动时需要,第二次启动时注释掉
      jdbcTokenRepository.setCreateTableOnStartup(true);
      return jdbcTokenRepository;
   }
}

修改SecurityConfig.java

在SecurityConfig中添加RememberMeConfig和UserDetailsService实现类对象,并自动注入。

在 configure 中添加下面配置内容。

http.rememberMe()
      //登录逻辑交给哪个对象
      .userDetailsService(userService)
      // 持久层对象
      .tokenRepository(persistentTokenRepository);

在客户端页面添加复选框

在客户端登录页面中添加 remember-me 的复选框,只要用户勾选了复选框下次就不需要进行登录了。

<form action="/login" method="post">
    用户名:<input type="text" name="username" /><br/>
    密码:<input type="password" name="password" /><br/>
    <input type="checkbox" name="remember-me" value="true"/><br/>
    <input type="submit" value="登录" />
</form>

有效时间

默认情况下重启项目后登录状态失效了。但是可以通过设置状态有效时间,即使项目重新启动下次也可以正常登录。

http.rememberMe()
      //失效时间,单位秒
      .tokenValiditySeconds(120)
      //登录逻辑交给哪个对象
      .userDetailsService(userService)
      // 持久层对象
      .tokenRepository(persistentTokenRepository);

Thymeleaf中SpringSecurity的使用

 Spring Security 可以在一些视图技术中进行控制显示效果。例如:JSPThymeleaf。在非前后端分离且使用 Spring Boot 的项目中多使用 Thymeleaf作为视图展示技术。

 Thymeleaf 对 Spring Security 的 支 持 都 放 在thymeleaf-extras-springsecurityX中,目前最新版本为 5。所以需要在项目中添加此 jar 包的依赖和 thymeleaf 的依赖。。

<!--thymeleaf springsecurity5 依赖-->
<dependency>
   <groupId>org.thymeleaf.extras</groupId>
   <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<!--thymeleaf依赖-->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

在 html 页面中引入 thymeleaf 命名空间和 security 命名空间

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">

获取属性

可以在html页面中通过sec:authentication=""获取

UsernamePasswordAuthenticationToken中所有 getXXX的内容,包含父类中的 getXXX的内容。

根据源码得出下面属性:

  • name:登录账号名称
  • principal:登录主体,在自定义登录逻辑中是 UserDetails
  • credentials:凭证
  • authorities:权限和角色
  • details:实际上是 WebAuthenticationDetails的实例。可以获取remoteAddress(客户端 ip)和 sessionId(当前 sessionId)
新建demo.html

在项目 resources 中新建 templates 文件夹,在 templates 中新建demo.html 页面

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <title>Title</title>
</head>
<body>
    登录账号:<span sec:authentication="name"></span><br/>
    登录账号:<span sec:authentication="principal.username"></span><br/>
    凭证:<span sec:authentication="credentials"></span><br/>
    权限和角色:<span sec:authentication="authorities"></span><br/>
    客户端地址:<span sec:authentication="details.remoteAddress"></span><br/>
    sessionId:<span sec:authentication="details.sessionId"></span><br/>
</body>
</html>
编写Controller

thymeleaf 页面需要控制转发,在控制器类中编写下面方法

@RequestMapping("/demo")
public String demo(){
   return "demo";
}

权限判断

设置用户角色和权限

设定用户具有 admin,/insert,/delete 权限 ROLE_abc 角色。

return new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_abc,/insert,/delete"));
控制页面显示效果

在页面中根据用户权限和角色判断页面中显示的内容

通过权限判断:
<button sec:authorize="hasAuthority('/insert')">新增</button>
<button sec:authorize="hasAuthority('/delete')">删除</button>
<button sec:authorize="hasAuthority('/update')">修改</button>
<button sec:authorize="hasAuthority('/select')">查看</button>
<br/>
通过角色判断:
<button sec:authorize="hasRole('abc')">新增</button>
<button sec:authorize="hasRole('abc')">删除</button>
<button sec:authorize="hasRole('abc')">修改</button>
<button sec:authorize="hasRole('abc')">查看</button>

退出登录

用户只需要向 Spring Security 项目中发送/logout退出请求即可。

退出登录

实现退出非常简单,只要在页面中添加/logout 的超链接即可。

<a href="/logout">退出登录</a>

为了实现更好的效果,通常添加退出的配置。默认的退出 url 为/logout,退出成功后跳转到/login?logout

0bec320689522c1af75ecdcf48b76570_202112081053698.png

如果不希望使用默认值,可以通过下面的方法进行修改。

//退出登录
http.logout()
      //退出登录url
      .logoutUrl("/logout")
      //退出登录成功跳转的url·
      .logoutSuccessUrl("/login.html");

logout其他常用配置源码解读

addLogoutHandler(LogoutHandler)

默认是 contextLogoutHandler

ea3c399d5572c8e6b8bc17b15667963e_202112081053505.png

默认实例内容

541410a32eacf06a18bea49ad2bfe122_202112081053811.png

clearAuthentication(boolean)

是否清除认证状态,默认为 true

86a75a75c0b89748acff09f1e654687d_202112081053501.png

invalidateHttpSession(boolean)

是否销毁 HttpSession 对象,默认为 true

e8817e2dad401e69883055589f66d713_202112081053564.png

logoutSuccessHandler(LogoutSuccessHandler)

退出成功处理器

f879b1ba6c9c0a0c6d4999a1db3de056_202112081053410.png

 也可以自己进行定义退出成功处理器。只要实现了LogoutSuccessHandler接口。与之前讲解的登录成功处理器和登录失败处理器极其类似。

SpringSecurity中的CSRF

 从刚开始学习Spring Security时,在配置类中一直存在这样一行代码:http.csrf().disable();如果没有这行代码导致用户无法被认证。这行代码的含义是:关闭 csrf 防护。

什么是CSRF

 CSRF(Cross-site request forgery)跨站请求伪造,也被称为“OneClick Attack” 或者 Session Riding。通过伪造用户请求访问受信任站点的非法请求访问。

 跨域:只要网络协议,ip 地址,端口中任何一个不相同就是跨域请求。

 客户端与服务进行交互时,由于 http 协议本身是无状态协议,所以引入了cookie进行记录客户端身份。在cookie中会存放session id用来识别客户端身份的。在跨域的情况下,session id 可能被第三方恶意劫持,通过这个 session id 向服务端发起请求时,服务端会认为这个请求是合法的,可能发生很多意想不到的事情。

2、Spring Security中的CSRF

 从 Spring Security4开始CSRF防护默认开启。默认会拦截请求。进行CSRF处理。CSRF为了保证不是其他第三方网站访问,要求访问时携带参数名为_csrf值为token(token 在服务端产生)的内容,如果token和服务端的token匹配成功,则正常访问。

2.1、编写控制器方法

编写控制器方法,跳转到 templates 中 login.html 页面。

@RequestMapping("/showLogin")
public String showLogin(){
   return "login";
}

2.2、新建login.html

红色部分是必须存在的否则无法正常登录。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
>
<head>
    <title>Title</title>
</head>
<body>
<form action="/login" method="post">
    <input type="hidden" th:value="${_csrf.token}" name="_csrf" th:if="${_csrf}"/>
    用户名:<input type="text" name="username" /><br/>
    密码:<input type="password" name="password" /><br/>
    <input type="submit" value="登录" />
</form>
</body>
</html>
修改配置类

在配置类中注释掉 CSRF 防护失效

//关闭csrf防护
// http.csrf().disable();
相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
4月前
|
安全 Java 数据安全/隐私保护
使用Spring Security实现细粒度的权限控制
使用Spring Security实现细粒度的权限控制
|
4月前
|
安全 Java 数据库
实现基于Spring Security的权限管理系统
实现基于Spring Security的权限管理系统
|
4月前
|
安全 Java 数据安全/隐私保护
解析Spring Security中的权限控制策略
解析Spring Security中的权限控制策略
|
5月前
|
JSON 安全 Java
Spring Security 6.x 微信公众平台OAuth2授权实战
上一篇介绍了OAuth2协议的基本原理,以及Spring Security框架中自带的OAuth2客户端GitHub的实现细节,本篇以微信公众号网页授权登录为目的,介绍如何在原框架基础上定制开发OAuth2客户端。
209 4
Spring Security 6.x 微信公众平台OAuth2授权实战
|
5月前
|
存储 安全 Java
Spring Security 6.x OAuth2登录认证源码分析
上一篇介绍了Spring Security框架中身份认证的架构设计,本篇就OAuth2客户端登录认证的实现源码做一些分析。
233 2
Spring Security 6.x OAuth2登录认证源码分析
|
5月前
|
安全 Java 数据安全/隐私保护
Spring Security 6.x 一文快速搞懂配置原理
本文主要对整个Spring Security配置过程做一定的剖析,希望可以对学习Spring Sercurity框架的同学所有帮助。
272 5
Spring Security 6.x 一文快速搞懂配置原理
|
5月前
|
安全 Java API
Spring Security 6.x 图解身份认证的架构设计
【6月更文挑战第1天】本文主要介绍了Spring Security在身份认证方面的架构设计,以及主要业务流程,及核心代码的实现
84 1
Spring Security 6.x 图解身份认证的架构设计
|
4月前
|
安全 Java 数据安全/隐私保护
使用Spring Security实现细粒度的权限控制
使用Spring Security实现细粒度的权限控制
|
4月前
|
安全 Java 数据安全/隐私保护
使用Java和Spring Security实现身份验证与授权
使用Java和Spring Security实现身份验证与授权
|
4月前
|
存储 安全 Java
Spring Security在企业级应用中的应用
Spring Security在企业级应用中的应用