Spring – 权限管理 – Spring Security

简介

Spring 是非常流行和成功的 Java 应用开发框架,Spring Security 正是 Spring 家族中的成员。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。
正如你可能知道的关于安全方面的两个核心功能是“认证”和“授权”,一般来说,Web 应用的安全性包括 用户认证(Authentication)和用户授权(Authorization)两个部分,这两点也是 SpringSecurity 重要核心功能。

(1)用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码,系统通过校验用户名和密码来完成认证过程。

通俗点说就是系统认为用户是否能登录

(2)用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

通俗点讲就是系统判断用户是否有权限去做某些事情。

完整项目下载

https://www.tzming.com/wp-content/uploads/2023/filesdown/SpringSecurityDemo.rar

Spring Security 基本信息

官方网站:https://spring.io/projects/spring-security

通过提供完整可扩展的认证和授权支持保护你的应用程序。

SpringSecurity 特点:

⚫ 和 Spring 无缝整合。

⚫ 全面的权限控制。

⚫ 专门为 Web 开发而设计。

​ ◼旧版本不能脱离 Web 环境使用。

​ ◼新版本对整个框架进行了分层抽取,分成核心模块和 Web 模块。单独引入核心模块就可以脱离 Web 环境。

⚫重量级。

Spring Security 整合 Spring Boot

要对Web资源进行保护,最好的办法莫过于Filter 要想对方法调用进行保护,最好的办法莫过于AOP。

Spring Security进行认证和鉴权的时候,就是利用的一系列的Filter来进行拦截的。

一个请求想要访问到API就会从左到右经过蓝线框里的过滤器,其中绿色部分是负责认证的过滤器,蓝色部分是负责异常处理,橙色部分则是负责授权。进过一系列拦截最终访问到我们的API。

1.FilterSecuritylnterceptor:是一个方法级的权限过滤器,基本位于过滤链的最底部。

2.ExceptionTranslationFilter:是个异常过滤器,用来处理在认证授权过程中抛出的异常。

3.UsernamePasswordAuthenticationFilter:对/loqin的 POST请求做拦截,校验表单中用户名,密码。

除了以上三个过滤器外,实际上Spring中有15个过滤器,如下图

Spring Security的核心逻辑全在这一套过滤器中,过滤器里会调用各种组件完成功能。这个框架的使用方式就是对这些过滤器和组件进行扩展。

UsernamePasswordAuthenticationFilter 用户密码验证过滤器

以下是用户密码验证过滤器的执行图:

1.SpringSecurity 的验证接口 AbstractAuthenticationProcessingFilter 的实现类
UsernamePasswordAuthenticationFilter 在接收到账号和密码时,会把账号和密码封装成 Authentication 对象

2.UsernamePasswordAuthenticationFilter 调用 Authentication.authenticate 方法进行认证

3.AuthenticationManager 接口的 ProviderManager 实现类被执行,调用 authenticate 方法

4.AbstractUserDetailsAuthenticationProvider 接口的 DaoAuthenticationProvider 实现类被执行

5.DaoAuthenticationProvider 调用 loadUserByUsername 方法查询用户

6.UserDetailsService 接口的 ImMemoryUserDetailsManager 实现类调用方法,返回一个 UserDetails(接口) 对象

7.DaoAuthenticationProvider 调用 PasswordEncoder 对比 UserDetails中的密码和 Authentication 的密码是否正确
如果正确就把 UserDetails 中的权限信息设置到 Authentication 对象中。

8.UsernamePasswordAuthenticationFilter 收到 带有权限信息设置的 Authentication 对象.

Authentication接口: 它的实现类,表示当前访问系统的用户,封装了用户相关信息。

AuthenticationManager接口:定义了认证Authentication的方法

UserDetailsService接口:加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法。

UserDetails接口:提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中。

 

实现自定义用户密码验证功能

从上一章节中的 UsernamePasswordAuthenticationFilter 流程图可以看出,如果需要自定义改造我们的用户密码验证方式,

1.需要重写 UserDetailsService 接口返回 UserDetails 对象,重写 UserDetailsService 的 loadUserByUsername 方法

 

因为 UserDetails 是用于保存用户密码数据的接口方法,因此也需要创建UserDetails的实现类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails {

    private User user;


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUserName();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

 

安全授权

权限系统的作用

例如一个学校图书馆的管理系统,如果是普通学生登录就能看到借书还书相关的功能,不可能让他看到并且去使用添加书籍信息,删除书籍信息等功能。但是如果是一个图书馆管理员的账号登录了,应该就能看到并使用添加书籍信息,删除书籍信息等功能。

​ 总结起来就是**不同的用户可以使用不同的功能**。这就是权限系统要去实现的效果。

​ 我们不能只依赖前端去判断用户的权限来选择显示哪些菜单哪些按钮。因为如果只是这样,如果有人知道了对应功能的接口地址就可以不通过前端,直接去发送请求来实现相关功能操作。

​ 所以我们还需要在后台进行用户权限的判断,判断当前用户是否有相应的权限,必须具有所需权限才能进行相应的操作。

 

授权基本流程

​ 在SpringSecurity中,会使用默认的FilterSecurityInterceptor来进行权限校验。在FilterSecurityInterceptor中会从SecurityContextHolder获取其中的Authentication,然后获取其中的权限信息。当前用户是否拥有访问当前资源所需的权限。

​ 所以我们在项目中只需要把当前登录用户的权限信息也存入Authentication。

​ 然后设置我们的资源所需要的权限即可。

 

限制访问资源所需权限

SpringSecurity为我们提供了基于注解的权限控制方案,这也是我们项目中主要采用的方式。我们可以使用注解去指定访问对应的资源所需的权限。

但是要使用它我们需要先开启相关配置。

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {}

然后就可以使用对应的注解。@PreAuthorize

@RestController
public class HelloController {

    @RequestMapping("/hello")
    @PreAuthorize("hasAuthority('test')")
    public String hello(){
        return "hello";
    }
}

 

RBAC 角色表模型

RBAC权限模型(Role-Based Access Control)即:基于角色的权限控制。这是目前最常被开发者使用也是相对易用、通用权限模型。

 

认证和授权失败异常时处理

我们还希望在认证失败或者是授权失败的情况下也能和我们的接口一样返回相同结构的json,这样可以让前端能对响应进行统一的处理。要实现这个功能我们需要知道SpringSecurity的异常处理机制。

​ 在SpringSecurity中,如果我们在认证或者授权的过程中出现了异常会被ExceptionTranslationFilter捕获到。在ExceptionTranslationFilter中会去判断是认证失败还是授权失败出现的异常。

​ 如果是认证过程中出现的异常会被封装成AuthenticationException然后调用AuthenticationEntryPoint对象的方法去进行异常处理。

​ 如果是授权过程中出现的异常会被封装成AccessDeniedException然后调用AccessDeniedHandler对象的方法去进行异常处理。

​ 所以如果我们需要自定义异常处理,我们只需要自定义AuthenticationEntryPoint和AccessDeniedHandler然后配置给SpringSecurity即可。

/**
 * SpringSecurity 认证失败时的异常处理方法
 * 认证失败是指用户账号密码不正确时所产生的异常处理,Spring 会调用 AuthenticationEntryPoint 接口的实现方法
 */
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        // HttpStatus 是 Spring 提供的请求状态码枚举,包含200到500状态码
        ResponseResult result = new ResponseResult<>(HttpStatus.UNAUTHORIZED.value(), "用户账号或密码不正确");
        String json = JSON.toJSONString(result);
        WebUtils.renderString(httpServletResponse, json);
    }
}

 

 

 

/**
 * SpringSecurity 授权失败时的异常处理方法
 * 授权失败是指用户账号密码正确时,但所请求的没有权限,或者说该登陆用户不具备该请求的权限
 * Spring 会调用 AccessDeniedHandler 接口的实现方法
 */
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        ResponseResult result = new ResponseResult<>(HttpStatus.FORBIDDEN.value(), "用户没有权限");
        String json = JSON.toJSONString(result);
        WebUtils.renderString(httpServletResponse,json);
    }
}

 

 

完成了两个异常处理实现类后,应该要把这两个异常处理加载到SpringSecurity中,所以我们同样在 SecurityConfig的
configure 里,配置这两个异常处理,当以后出现账号密码不正确,或该账号访问不该访问的api请求时,就会调用指定的异常

// 配置认证和授权失败时的异常处理方法
        http.exceptionHandling()
                // 处理认证失败时的异常
                .authenticationEntryPoint(authenticationEntryPoint)
                // 处理授权失败时的异常
                .accessDeniedHandler(accessDeniedHandler);

 

跨域问题cors

浏览器出于安全的考虑,使用 XMLHttpRequest对象发起 HTTP请求时必须遵守同源策略,否则就是跨域的HTTP请求,默认情况下是被禁止的。 同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致。

前后端分离项目,前端项目和后端项目一般都不是同源的,所以肯定会存在跨域请求的问题。

所以我们就要处理一下,让前端能进行跨域请求。

对于跨域的问题,需要在后端进行配置,创建一个用于配置跨域的配置类 CorsConfig,该类需要重写 WebMvcConfigurer
以方便重写 Web 中的对于跨域请求的问题

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
      // 设置允许跨域的路径
        registry.addMapping("/**")
                // 设置允许跨域请求的域名
                .allowedOriginPatterns("*")
                // 是否允许cookie
                .allowCredentials(true)
                // 设置允许的请求方式
                .allowedMethods("GET", "POST", "DELETE", "PUT")
                // 设置允许的header属性
                .allowedHeaders("*")
                // 跨域允许时间
                .maxAge(3600);
    }
}

然后在 SpringSecurity 中的 configure 里对 cors 进行开启即可

http.cors();

 

权限校验方法

我们前面都是使用@PreAuthorize注解,然后在在其中使用的是hasAuthority方法进行校验。SpringSecurity还为我们提供了其它方法例如:hasAnyAuthority,hasRole,hasAnyRole等。

hasAuthority方法实际是执行到了SecurityExpressionRoot的hasAuthority,大家只要断点调试既可知道它内部的校验原理。

它内部其实是调用authentication的getAuthorities方法获取用户的权限列表。然后判断我们存入的方法参数数据在权限列表中。

 

hasAnyAuthority

hasAnyAuthority方法可以传入多个权限,只有用户有其中任意一个权限都可以访问对应资源。

@PreAuthorize("hasAnyAuthority('admin','test','system:dept:list')")
    public String hello(){
        return "hello";
    }

 

hasRole

hasRole要求有对应的角色才可以访问,但是它内部会把我们传入的参数拼接上 ROLE_ 后再去比较。所以这种情况下要用用户对应的权限也要有 ROLE_ 这个前缀才可以。

@PreAuthorize("hasRole('system:dept:list')")
    public String hello(){
        return "hello";
    }

 

hasAnyRole

hasAnyRole 有任意的角色就可以访问。它内部也会把我们传入的参数拼接上 ROLE_后再去比较。所以这种情况下要用用户对应的权限也要有 ROLE_ 这个前缀才可以。

@PreAuthorize("hasAnyRole('admin','system:dept:list')")
    public String hello(){
        return "hello";
    }

 

自定义权限校验方法

对于SpringSecurity所提供的权限校验方法而言,已经可以用在大多数项目中,但如果我希望通过自定义编写权限校验方法,可以通过自定义创建一个类,创建一个方法,其返回值是boolean即可。

 

 

基于配置的权限控制

权限校验中,除了可以在每个请求方法体前加上 @PreAuthorize 注解的方式定义权限范围外,还可以在 SpringConfig 类中的 configure 方法中定义,如下代码

// 用配置的方式配置某个请求的权限校验功能 -> 对 /test 请求的权限控制范围要求必须有 system::user::list 的用户才可以访问
        http.authorizeRequests().antMatchers("/test").hasAnyAuthority("system::user::list");

 

认证成功处理器

实际上在UsernamePasswordAuthenticationFilter进行登录认证的时候,如果登录成功了是会调用AuthenticationSuccessHandler的方法进行认证成功后的处理的。AuthenticationSuccessHandler就是登录成功处理器。

​ 我们也可以自己去自定义成功处理器进行成功后的相应处理。

/**
 * 认证成功处理器
 * 认证成功与否与 UsernamePasswordAuthenticationFilter 有关,如果使用前后端分离的项目的话
 * 那么 UsernamePasswordAuthenticationFilter 是被弃用的,就不会有认证成功处理器的说法
 * 本类是用在当使用非前后端分离项目的情况下,也就是在有使用 UsernamePasswordAuthenticationFilter
 * 作为认证的情况下,才会有认证成功处理器
 *
 * 然后需要在 SecurityConfig 中配置 http.formLogin().successHandler() 认证成功配置
 */
@Component
public class UnSoftAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    // 认证成功后的回调方法
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        ResponseResult result = new ResponseResult<>(200, "登陆成功");
        String json = JSON.toJSONString(result);
        WebUtils.renderString(httpServletResponse, json);
    }
}

 

 

然后需要在 SecurityConfig 中配置 http.formLogin().successHandler() 认证成功配置

/**
     * 自定义认证成功处理器
     */
    @Autowired
    private AuthenticationSuccessHandler authenticationSuccessHandler;


@Override
protected void configure(HttpSecurity http) throws Exception {

    http.formLogin().successHandler(authenticationSuccessHandler)

}

 

认证失败处理器

/**
 * 认证失败处理器
 * 认证成功与否与 UsernamePasswordAuthenticationFilter 有关,如果使用前后端分离的项目的话
 * 那么 UsernamePasswordAuthenticationFilter 是被弃用的,就不会有认证失败处理器的说法
 * 本类是用在当使用非前后端分离项目的情况下,也就是在有使用 UsernamePasswordAuthenticationFilter
 * 作为认证的情况下,才会有认证失败处理器
 *
 * 然后需要在 SecurityConfig 中配置 http.formLogin().failureHandler() 认证失败配置
 */
@Component
public class UnSoftAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        ResponseResult result = new ResponseResult<>(401, "登陆失败");
        String json = JSON.toJSONString(result);
        WebUtils.renderString(httpServletResponse, json);
    }
}

 

然后需要在 SecurityConfig 中配置 http.formLogin().failureHandler() 认证失败配置

/**
     * 自定义认证失败处理器
     */
    @Autowired
    private AuthenticationFailureHandler authenticationFailureHandler;


@Override
protected void configure(HttpSecurity http) throws Exception {

    http.formLogin().failureHandler(authenticationFailureHandler);

}

 

 

用户注销成功处理器

/**
 * 注销成功处理器
 * 注销成功与 UsernamePasswordAuthenticationFilter 有关,如果使用前后端分离的项目的话
 * 那么 UsernamePasswordAuthenticationFilter 是被弃用的,就不会有注销成功处理器的说法
 * 本类是用在当使用非前后端分离项目的情况下,也就是在有使用 UsernamePasswordAuthenticationFilter
 * 作为认证的情况下,才会有注销成功处理器
 *
 * 然后需要在 SecurityConfig 中配置 http.logout().logoutSuccessHandler() 注销成功配置
 */
@Component
public class UnSoftLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        ResponseResult result = new ResponseResult<>(200, "退出登陆成功");
        String json = JSON.toJSONString(result);
        WebUtils.renderString(httpServletResponse, json);
    }
}

 

然后需要在 SecurityConfig 中配置 http.logout().logoutSuccessHandler() 注销成功配置

/**
     * 注销用户成功处理器
     */
    @Autowired
    private LogoutSuccessHandler logoutSuccessHandler;


@Override
protected void configure(HttpSecurity http) throws Exception {

    http.logout().logoutSuccessHandler(logoutSuccessHandler);

}

 

 

配置 Spring Security

创建用于配置 Spring Security 的模块

1.添加依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>cn.unsoft</groupId>
        <artifactId>common</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>spring-security</artifactId>
    
    <dependencies>

        <!-- Spring Security依赖 -->
        <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>
		<scope>provided </scope>
	</dependency>
    </dependencies>

</project>

 

2.创建配置文件

创建一个用于导入Spring Bean 的配置文件类,专用于配置 spring security

package cn.unsoft.security.config;

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是开启SpringSecurity的默认行为
@EnableWebSecurity 
// 配置类继承 WebSecurityConfigurerAdapter 
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

}

 

3.引入配有Spring Security的模块进行使用

<dependency>
    <groupId>cn.unsoft</groupId>
    <artifactId>spring-security</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

 

4.关于 Spring Security 运行时的认证密码

该密码是 Spring Security 提供的用户登陆验证临时密码,当我们请求时,Spring Security会自动验证该请求是否有权限访问,如果没有权限,Spring Security默认跳转到它自带的登陆验证页面,用户名默认为 user ,密码则为临时生成。

5.如果出现引用循环报错,可加入以下配置项

spring:
  main:
    allow-bean-definition-overriding: true # 允许bean定义覆盖
    allow-circular-references: true # 允许循环引用

 

用户认证流程

我们系统中会有许多用户,确认当前是哪个用户正在使用我们系统就是登录认证的最终目的。这里我们就提取出了一个核心概念:当前登录用户/当前认证用户。整个系统安全都是围绕当前登录用户展开的,这个不难理解,要是当前登录用户都不能确认了,那A下了一个订单,下到了B的账户上这不就乱套了。这一概念在Spring Security中的体现就是 Authentication,它存储了认证信息,代表当前登录用户。

我们在程序中如何获取并使用它呢?我们需要通过 SecurityContext 来获取AuthenticationSecurityContext就是我们的上下文对象!这个上下文对象则是交由 SecurityContextHolder 进行管理,你可以在程序任何地方使用它:

如果您喜欢本站,点击这儿不花一分钱捐赠本站

这些信息可能会帮助到你: 下载帮助 | 报毒说明 | 进站必看

修改版本安卓软件,加群提示为修改者自留,非本站信息,注意鉴别

THE END
分享
二维码
打赏
海报
Spring – 权限管理 – Spring Security
简介 Spring 是非常流行和成功的 Java 应用开发框架,Spring Security 正是 Spring 家族中的成员。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。 正如你可……
<<上一篇
下一篇>>