构建安全稳定的应用:Spring Security 实用指南

前言

在现代 Web 应用程序中,安全性是至关重要的一个方面。Spring Security 作为一个功能强大且广泛使用的安全框架,为 Java 应用程序提供了全面的安全解决方案。本文将深入介绍 Spring Security 的基本概念、核心功能以及如何在应用程序中使用它来实现认证和授权。

一、Spring Security

Spring Security 是一个基于 Spring 的安全性框架,用于提供身份验证、授权、攻击防护等安全服务。它构建在 Spring 框架之上,利用依赖注入和 AOP 等功能,使得集成到现有的 Spring 应用程序中非常简单。

在开始深入了解 Spring Security 之前,我们需要了解几个核心概念:

  1. Authentication(认证):验证用户的身份,通常是通过用户名和密码进行。
  2. Authorization(授权):确定用户是否有权限执行特定操作或访问特定资源。
  3. Principal(主体):代表当前用户的抽象概念,通常是一个实现了 UserDetails 接口的对象。
  4. Granted Authority(授权权限):表示用户具有的权限,通常是角色或权限的集合。
  5. Access Control(访问控制):定义了哪些用户可以访问应用程序的哪些部分以及如何限制对资源的访问。

二、快速入门

  1. 添加 Spring Security 依赖项:首先,在 Spring Boot 项目中添加 Spring Security 的依赖项。

    <dependencies><!-- Spring Boot Web Starter 依赖,包含了开发 web 应用所需的所有基础依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Spring Boot Security Starter 依赖,包含了开发安全应用所需的所有基础依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>
    </dependencies><dependencyManagement><dependencies><!-- Spring Boot Dependencies POM,用于管理 Spring Boot 项目的所有依赖的版本 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.7.5</version><type>pom</type><scope>import</scope></dependency></dependencies>
    </dependencyManagement>
    
  2. 配置安全策略:创建一个配置类来配置 Spring Security 的行为。

    @Configuration
    @EnableWebSecurity // 注解启用 Spring Security 的 web 安全支持
    public class SecurityConfig {
    }
    
  3. 定义一个访问端点:定义一个测试使用的访问端点

    @RestController
    @RequestMapping("/test")
    public class TestController {@RequestMapping("/hello")public String hello() {return "hello spring security";}
    }
    
  4. 运行应用程序:运行 Spring Boot 应用程序,并尝试访问端点 localhost:8080/test/hello
    image.png

    默认账号是 user,密码如下图所示:

    image.png

    输入默认账号、密码之后:

    image.png

三、核心功能

3.1 身份验证(Authentication)

Spring Security 提供多种身份验证机制,包括基本认证、表单认证、OAuth、LDAP 等。支持自定义身份验证流程,可以根据应用程序的需求进行定制。开发人员可以根据需要配置请求路径是否需要认证才能访问。例如:

@Configuration
@EnableWebSecurity
public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeRequests().requestMatchers(new AntPathRequestMatcher("/test/hello")).permitAll() // 允许所有用户访问 "/test/hello" 路径.anyRequest().authenticated(); // 表示所有其他的请求都需要经过认证return http.build();}
}

3.2 授权(Authorization)

Spring Security 可以基于角色(Role-Based Access Control)和权限(Permission-Based Access Control)的访问控制。

@Configuration
@EnableWebSecurity
public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeRequests()// 只有具有 "add" 权限的用户才能访问 "/test/hello" 路径.requestMatchers(new AntPathRequestMatcher("/test/hello")).hasAuthority("add");return http.build();}
}

3.3 漏洞防护(Protection Against Exploits)

Spring Security 可以防范常见的攻击,如跨站点请求伪造(CSRF)、点击劫持等。例如,通过如下配置可开启 CSRF 保护:

@Configuration
@EnableWebSecurity
public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {// 配置跨站请求伪造(CSRF)的保护http.csrf();return http.build();}
}

四、高级功能

4.1 会话管理(Session Management)

Spring Security 提供了会话管理的功能,包括会话超时、并发登录限制、会话固定攻击防护等功能。例如,可以通过如下配置开启并发登录限制:

@Configuration
@EnableWebSecurity
public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.formLogin() // 启用了表单登录.and().authorizeRequests().anyRequest().authenticated() // 所有请求都需要经过认证.and().sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 设置会话创建策略为无状态,即 Spring Security 不会创建会话.sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true); // 设置每个用户的最大并发会话数为 1,并且当达到最大并发会话数时,阻止新的登录请求return http.build();}
}

4.2 密码编码(Password Encoding)

Spring Security 提供密码加密和验证机制,确保用户密码的安全性。例如,要使用 BCrypt 加密,只需进行如下配置即可:

@Configuration
@EnableWebSecurity
public class SecurityConfig {@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
}

4.3 基于注解的方法级安全控制

Spring Security 允许在方法上使用注解的方式进行访问控制。

@Secured("ROLE_ADMIN")
public void secureMethod() {// 只有具有 ROLE_ADMIN 角色的用户可以访问
}@PreAuthorize("hasRole('ADMIN')")
public void preAuthorizeMethod() {// 在方法调用之前进行授权检查
}

要使用注解的方式,需要使用 @EnableMethodSecurity 注解开启这一功能

@Configuration
@EnableWebSecurity
@EnableMethodSecurity // 开启基于注解的方法级安全控制
public class SecurityConfig {}

除此之外,Spring Security 支持使用表达式语言 (SpEL) 来定义复杂的访问控制规则。

@PreAuthorize("hasRole('ROLE_USER') and #id == principal.id")
public void updateUser(Long id) {// 只有具有 ROLE_USER 角色且 id 等于当前用户的 principal.id 的用户可以访问
}

4.4 事件监听

Spring Security 允许监听安全事件,例如登录成功、失败、登出等,以便记录日志或执行其他操作。

@Component
public class AuthenticationEventListener implements ApplicationListener<AuthenticationSuccessEvent> {@Overridepublic void onApplicationEvent(AuthenticationSuccessEvent event) {// 处理认证成功事件}
}

五、工作原理

Spring Security 的设计有那么亿点复杂,我们通过分开描述其核心工作流程、认证工作流程、鉴权工作流程来阐述其工作原理。

5.1 核心工作流程

Spring Security 的核心工作流程如下:

  1. 用户发送请求:用户通过浏览器或客户端发送请求到应用程序。
  2. 请求进入过滤器链代理(FilterChainProxy):所有请求首先进入 FilterChainProxy,它是一个标准的 Servlet 过滤器 (javax.servlet.Filter)。FilterChainProxy 的作用是根据请求的路径(URL)匹配合适的 SecurityFilterChain。
  3. 请求进入安全过滤器链(SecurityFilterChain):匹配到相应的 SecurityFilterChain 后,请求进入该安全过滤器链。SecurityFilterChain 是由多个安全过滤器(SecurityFilter)组成的序列,每个安全过滤器按顺序处理请求。
  4. 执行安全过滤器(SecurityFilter):每个安全过滤器 (SecurityFilter) 负责执行特定的安全操作和策略,例如身份认证、授权、会话管理等。
  5. 调用认证管理器(AuthenticationManager)AuthenticationManager 是 Spring Security 的核心接口之一,负责处理认证请求。在认证过程中,AuthenticationManager 通常会使用 AuthenticationProvider 来进行具体的认证操作。
  6. 返回认证结果:AuthenticationManager 返回认证结果给 SecurityFilter
  7. 返回过滤器链:认证完成后,SecurityFilter 可能会进行一些额外的安全处理,并将请求继续传递给下一个安全过滤器或者返回给 SecurityFilterChain
  8. 返回过滤器链代理:处理完所有安全过滤器后,请求最终返回到 FilterChainProxy
  9. 返回响应FilterChainProxy 将最终的响应返回给用户,完成整个请求-响应周期。

在这里插入图片描述

FilterChainProxy

  • 作用:FilterChainProxy 是 Spring Security 中的一个核心组件,它负责管理 Spring Security 中的各种过滤器链。当一个 HTTP 请求到达应用程序时,FilterChainProxy 会将该请求传递给一个或多个 SecurityFilterChain 实例进行处理。如果需要,FilterChainProxy 还可以重定向请求或返回错误信息。
  • 设计目的:FilterChainProxy 的设计目的是提供一个统一的入口点,用于管理和协调 Spring Security 中的所有过滤器链。
  • 例子:假设我们正在开发一个 Web 应用程序,该应用程序有多个端点,如 /public/user/admin。我们可能希望 /public 端点对所有人开放,/user 端点只对已登录用户开放,而 /admin 端点只对管理员开放。在这种情况下,你可以使用 FilterChainProxy 来管理三个不同的过滤器链,每个过滤器链负责一个特定的端点。

SecurityFilterChain

  • 作用:SecurityFilterChain 是一个顶层接口。SecurityFilterChain 和 Servlet 中的 FilterChain 一样,同样维护了很多 Filter,这些 Filter 由 Spring Security 提供,每个 Filter 具有不同的职能。
  • 设计目的:SecurityFilterChain 的设计目的是为了支持添加一个或多个 SecurityFilterChain,每个SecurityFilterChain 负责不同的请求(比如依据请求地址进行区分),这样可以为不同的请求设置不同的认证规则
  • 例子:继续上面的例子,我们可能会为 /public 端点创建一个 SecurityFilterChain,该过滤器链包含一个检查请求是否为 GET 的过滤器。对于 /user 和 /admin 端点,我们可能会创建包含身份验证过滤器的 SecurityFilterChain,该过滤器检查用户是否已登录,并根据用户的角色(用户或管理员)授予相应的权限。

SecurityFilter

  • 作用:SecurityFilter 是 Spring Security 的过滤器,每个过滤器负责处理特定的安全任务。当请求到达应用程序时,它会依次通过过滤器链中的每个过滤器,直到到达目标资源。在过滤器链中,每个过滤器都可以对请求进行拦截、修改或执行其他操作,以确保应用程序的安全性。
  • 设计目的:SecurityFilter 的设计目的是为了提高 Web 应用程序的安全性、可维护性和可扩展性。
  • 例子:在上述 SecurityFilterChain 中,我们可能会使用多个 SecurityFilter。例如,一个 SecurityFilter 可能会检查请求是否为 GET,另一个 SecurityFilter 可能会检查用户是否已登录,还有一个 SecurityFilter 可能会根据用户的角色授予相应的权限。

AuthenticationManager

  • 作用:AuthenticationManager 是 Spring Security 中的认证管理器,用来对登录请求进行处理。当处理用户的登录请求时,例如在使用表单登录时,AuthenticationManager 的 authenticate 方法会被调用来处理请求。
  • 设计目的:AuthenticationManager 这个接口的设计目的是对用户的未授信凭据进行认证,认证通过则返回授信状态的凭据,否则将抛出认证异常 AuthenticationException。
  • 例子:当用户尝试登录时,在登录表单中输入用户名和密码,这些凭据将被传递给 AuthenticationManager。AuthenticationManager 会检查这些凭据是否有效。如果凭据有效,AuthenticationManager 将创建一个已认证的 Authentication 对象,该对象包含用户的详细信息和授权。如果凭据无效,AuthenticationManager 将抛出一个异常。

在这里插入图片描述

5.2 认证工作流程

认证是验证用户身份的过程,通常通过用户名和密码、数字证书或生物特征等手段进行。通过 Spring Security 的核心工作流程我们可以知道:Spring Security 具体的认证工作是交由 AuthenticationManager 执行的

AuthenticationManager 的认证流程如下:

  1. 用户提交凭证:用户向系统提交用户名和密码等凭证信息。
  2. 认证管理器 (AuthenticationManager):接收到凭证后,AuthenticationManager 负责进行认证。
  3. 认证提供者 (AuthenticationProvider):AuthenticationManager 调用认证方法,该方法会委托给配置的 AuthenticationProvider(认证提供者)。
  4. 用户详情服务 (UserDetailsService):认证提供者通过调用 UserDetailsService 加载用户的详细信息,通常是根据用户名加载用户对象。
  5. 用户详情 (UserDetails):UserDetailsService 返回一个实现了 UserDetails 接口的用户详情对象,其中包含了用户的详细信息和权限。
  6. 创建认证信息 (Authentication):认证提供者使用 UserDetails 对象创建一个 Authentication 对象,表示成功的认证。
  7. 安全上下文持有者 (SecurityContextHolder):创建的 Authentication 对象被存储到 SecurityContextHolder 中,以便后续的访问控制和安全操作使用。
  8. 返回认证结果:最终,认证结果以 Authentication 对象的形式返回给用户,表示用户已经成功通过认证。

在这里插入图片描述

Spring Security 的认证流程看似蛮复杂的。其实,它的认证流程和我们常规的认证方式是类似的。

  • AuthenticationManager:不进行具体的认证处理,负责管理多个 AuthenticatonProvider,具体的认证交由能够处理当前认证的 AuthenticationProvider。
  • AuthenticationProvider:进行具体认证,通过调用 UserDetailService 从数据库或者其他地方加载用户信息。之后通过与用户提交的凭证信息进行匹配,若成功则生成 Authentication 对象,表示认证成功,反之,返回认证失败信息。
  • UserDetailsService:它的主要作用是根据用户名加载用户的详细信息。加载的具体逻辑一般由开发人员实现
  • UserDetails:用于表示用户的基本身份和授权信息(代表一个用户)。
  • Authentication:用于表示用户在系统中的身份认证信息。具体来说,Authentication 接口主要用于封装认证过程中的关键信息,如认证的主体(Principal)、凭证(Credentials)、授权信息(Authorities)等。
  • SecurityContextHolder:主要用于存储和访问当前用户的 Authentication 对象,即表示当前用户身份认证信息的实例。

在这里插入图片描述

5.3 鉴权工作流程

鉴权是在确认用户身份后,决定用户是否有权访问特定资源或执行特定操作的过程。Spring Security 鉴权流程通常是在认证完成之后,即生成了 Authentication 对象之后进行的。其具体流程如下:

  1. AuthorizationFilter 授权过滤器:通过认证的请求被送到 AuthorizationFilter,它是 Spring Security 中的一个过滤器,负责处理所有的请求,并开启鉴权过程
  2. 获取 Authentication 认证信息:通过 SecurityContextHolder 从当前的安全上下文中获取用户的认证信息 (Authentication),这包括用户的身份凭证和权限信息。
  3. 调用 AccessDecisionManager 访问决策管理器:AuthorizationFilter 调用 AccessDecisionManager 进行实际的访问决策。AccessDecisionManager 是一个核心组件,负责确定是否允许用户访问请求的资源或操作。AccessDecisionManager 可能会使用多个 AccessDecisionVoter 进行投票。AccessDecisionVoter 是决策的实际执行者,根据用户的认证信息和访问请求,投票是否允许访问资源或执行操作。
  4. 返回投票结果:每个 AccessDecisionVoter 根据自身的逻辑判断是否允许访问。投票结果将汇总给 AccessDecisionManager
  5. 返回访问决策结果AccessDecisionManager 将所有 AccessDecisionVoter 的投票结果综合起来,最终决定是否允许用户访问请求的资源或操作。
  6. 返回响应AuthorizationFilter 将处理的结果返回给用户,响应用户的请求,这可能包括成功的访问授权或者拒绝访问的信息。

在这里插入图片描述

其实,鉴权的逻辑还是比较简单的,只是流程比较多,可以概括为:

  1. 首先,AuthorizationFilter 获取登录用户的认证信息(Authentication
  2. 然后,AuthorizationFilter 调用 AccessDecisionManager 判断权限

六、前后端分离

在之前的快速入门中,我们发现 Spring Security 默认情况下是采用前后端不分离的方式进行认证,而现在我们的项目一般都是前后端分离的方式(即: 前端通过 RESTful API 与后端进行通信,后端负责处理认证和授权,而前端则通过获取后端返回的 JWT(JSON Web Token)来管理用户的身份验证和授权状态)。要实现这一需求,我们可以参考如下步骤:

  1. 添加依赖:首先,在 pom.xml 文件中添加 Spring Security 和 JWT 的依赖:

    <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!-- JWT 依赖 --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version> <!-- 根据需要选择合适的版本 --></dependency>
    </dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.7.5</version><type>pom</type><scope>import</scope></dependency></dependencies>
    </dependencyManagement>
    
  2. 创建用户服务实现类:创建一个实现 UserDetailsService 接口的服务类,用于从数据库加载用户信息,并将其返回给 Spring Security 进行认证和授权。

    @Service
    public class UserDetailsServiceImpl implements UserDetailsService {// 模拟从数据库中查找用户@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 检查传入的用户名是否为"zs"if (!"zs".equals(username)) {// 如果用户名不是"zs",返回 null,表示没有找到对应的用户return null;}// 创建并返回一个 UserDetails 对象,表示用户的详细信息return User.builder().username("zs") // 设置用户名为"zs".password("$2a$16$RBoXNEqVxxtZ5l1QrJaMPub32Z8Q/e01tIG1Irs9ThxfXgeWxV1jq") // 设置加密后的密码.authorities("add") // 设置用户的权限为"add".build();}
    }
    
  3. 创建 JWT 工具类: 创建一个 JWT 工具类来生成和验证 JWT。

    @Component
    public class JwtUtil {@Value("${jwt.secret}")private String secret;@Value("${jwt.expiration}")private long expiration;// 从令牌中提取用户名public String extractUsername(String token) {return extractClaim(token, Claims::getSubject);}// 从令牌中提取过期时间public Date extractExpiration(String token) {return extractClaim(token, Claims::getExpiration);}// 提取令牌中的声明public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {final Claims claims = extractAllClaims(token);return claimsResolver.apply(claims);}// 解析令牌private Claims extractAllClaims(String token) {return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();}// 验证令牌是否过期private Boolean isTokenExpired(String token) {return extractExpiration(token).before(new Date());}// 生成令牌public String generateToken(UserDetails userDetails) {return Jwts.builder().setSubject(userDetails.getUsername()).setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + expiration * 1000)).signWith(SignatureAlgorithm.HS256, secret).compact();}// 验证令牌public Boolean validateToken(String token, UserDetails userDetails) {final String username = extractUsername(token);return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));}
    }
    
  4. 配置 JWT 相关属性:在 application.propertiesapplication.yml 中配置 JWT 的密钥和过期时间:

    jwt: # 配置JWT相关的属性secret: secretKey # 用于签名和验证JWT令牌的密钥expiration: 86400 # JWT令牌的有效期,以秒为单位。这里设置为86400秒(即24小时)
    
  5. 创建 JWT 认证过滤器:创建一个 JWT 认证过滤器来拦截每个请求,并验证 JWT。

    // 这个类继承自 OncePerRequestFilter,确保在每次请求时只调用一次过滤器
    @Component
    public class JwtRequestFilter extends OncePerRequestFilter {// 注入JwtUtil工具类@Resourceprivate JwtUtil jwtUtil;// 注入UserDetailsServiceImpl类,用于加载用户详细信息@Resourceprivate UserDetailsServiceImpl userDetailsService;// 重写OncePerRequestFilter的doFilterInternal方法,用于处理每个HTTP请求@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws ServletException, IOException {// 从请求头中获取Authorization信息final String authorizationHeader = request.getHeader("Authorization");// 初始化用户名和JWT令牌变量String username = null;String jwt = null;// 检查Authorization头是否以"Bearer "开头if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {// 提取JWT令牌(去掉"Bearer "部分)jwt = authorizationHeader.substring(7);// 使用jwtUtil从令牌中提取用户名username = jwtUtil.extractUsername(jwt);}// 如果用户名存在且当前没有已认证的用户if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {// 加载用户详细信息UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);// 验证JWT令牌if (jwtUtil.validateToken(jwt, userDetails)) {// 创建UsernamePasswordAuthenticationToken对象,包含用户详细信息和权限UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());// 将认证信息设置到SecurityContextHolder中SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);}}// 继续过滤链,处理下一个过滤器或目标资源chain.doFilter(request, response);}
    }
    
  6. 配置 Spring Security:开启 Spring Security 验证,配置 SecurityFilterChain。

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig {// 注入JwtRequestFilter对象,用于处理JWT认证@Resourceprivate JwtRequestFilter jwtRequestFilter;@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.csrf().disable() // 禁用CSRF保护.authorizeRequests() // 配置请求授权.antMatchers("/login").permitAll() // 对于"/login"路径,允许所有请求(无需认证).anyRequest().authenticated() // 对于所有其他请求,需要认证.and()// 在UsernamePasswordAuthenticationFilter之前添加JwtRequestFilter.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);return http.build();}@Beanpublic AuthenticationManager authenticationManager(UserDetailsService userDetailsService,PasswordEncoder passwordEncoder) {// 创建一个DaoAuthenticationProviderDaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();// 设置UserDetailsServiceauthenticationProvider.setUserDetailsService(userDetailsService);// 设置PasswordEncoderauthenticationProvider.setPasswordEncoder(passwordEncoder);// 返回包含这个认证提供者的ProviderManagerreturn new ProviderManager(authenticationProvider);}@Beanpublic BCryptPasswordEncoder passwordEncoder() {// 创建一个强度为16的BCryptPasswordEncoderreturn new BCryptPasswordEncoder(16);}
    }
    
  7. 创建认证接口和控制器:创建一个认证控制器来处理用户登录请求,并返回 JWT 给前端。

    @RestController
    public class AuthController {@Resourceprivate AuthenticationManager authenticationManager;@Resourceprivate JwtUtil jwtUtil;@Resourceprivate UserDetailsServiceImpl userDetailsService;@PostMapping("/login")public ResponseEntity<String> createAuthenticationToken(@RequestBody LoginRequest loginRequest) {// 创建一个未认证的UsernamePasswordAuthenticationToken对象UsernamePasswordAuthenticationToken authenticationToken =UsernamePasswordAuthenticationToken.unauthenticated(loginRequest.getUsername(), loginRequest.getPassword());// 调用AuthenticationManager的authenticate方法进行用户认证authenticationManager.authenticate(authenticationToken);// 加载用户详细信息UserDetails userDetails = userDetailsService.loadUserByUsername(loginRequest.getUsername());// 生成JWT令牌String jwt = jwtUtil.generateToken(userDetails);// 返回包含JWT令牌的响应return ResponseEntity.ok(jwt);}
    }
    
  8. 测试效果

    首先,访问 localhost:8080/login 获取到 token

    image.png

    然后,使用 token 访问 localhost:8080/test/hello

    image.png

七、小结

Spring Security 提供了强大而灵活的安全解决方案,可以轻松集成到 Spring 应用程序中。但是我们不难发现 Spring Security 设计得确实复杂了那么亿点点 (¬‿¬)。Spring Security 的使用门槛虽然较高,但是如果明白了它的原理便可以无缝与 Spring 结合使用,在日常的开发中可以极大的提高开发效率,增强应用的安全性。

推荐阅读

  1. 深入探究 Spring Boot Starter:从概念到实践
  2. 深入理解 Java 中的 volatile 关键字
  3. OAuth 2.0:现代应用程序的授权标准
  4. Spring 三级缓存
  5. 深入了解 MyBatis 插件:定制化你的持久层框架

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/39114.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

相比共享代理,为什么要用独享代理IP?

随着互联网的广泛普及和应用&#xff0c;涉及网络隐私、数据安全和网络访问控制的问题变得越来越重要。代理服务器作为一种常见的网络工具&#xff0c;可以在跨境电商、海外社媒、SEO投放、网页抓取等领域发挥作用&#xff0c;实现匿名访问并加强网络安全。在代理服务器类别中&…

CDC模型

引言 聚类是一种强大的机器学习方法&#xff0c;用于根据特征空间中元素的接近程度发现相似的模式。它广泛用于计算机科学、生物科学、地球科学和经济学。尽管已经开发了最先进的基于分区和基于连接的聚类方法&#xff0c;但数据中的弱连接性和异构密度阻碍了其有效性。在这项…

第四篇——作战篇:战争里的激励与成本

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么&#xff1f; 四、总结五、升华 一、背景介绍 前面进行了分析之后&#xff0c;这篇显然又从经济的角度进行了介绍和分析…

STELLA系统动态模拟技术及在农业、生态及环境等科学领域中的应用技术

STELLA是一种用户友好的计算机软件。通过绘画出一个系统的形象图形&#xff0c;并给这个系统提供数学公式和输入数据&#xff0c;从而建立模型。依据专业兴趣&#xff0c;STELLA可以用来建立各种各样的农业、生态、环境等方面的系统动态模型&#xff0c;为科研、教学、管理服务…

用例子和代码了解词嵌入和位置编码

1.嵌入&#xff08;Input Embedding&#xff09; 让我用一个更具体的例子来解释输入嵌入&#xff08;Input Embedding&#xff09;。 背景 假设我们有一个非常小的词汇表&#xff0c;其中包含以下 5 个词&#xff1a; "I""love""machine"&qu…

10 Posix API与网络协议栈

POSIX概念 POSIX是由IEEE指定的一系列标准,用于澄清和统一Unix-y操作系统提供的应用程序编程接口(以及辅助问题,如命令行shell实用程序),当您编写程序以依赖POSIX标准时,您可以非常肯定能够轻松地将它们移植到大量的Unix衍生产品系列中(包括Linux,但不限于此!)。 如…

DeepFaceLive----AI换脸简单使用

非常强大的软件,官方github https://github.com/iperov/DeepFaceLive 百度云链接: 链接&#xff1a;https://pan.baidu.com/s/1VHY-wxqJXSh5lCn1c4whZg 提取码&#xff1a;nhev 1下载解压软件 下载完成后双击.exe文件进行解压.完成后双击.bat文件打开软件 2 视频使用图片换…

k8s部署单机版mysql8

一、创建命名空间 # cat mysql8-namespace.yaml apiVersion: v1 kind: Namespace metadata:name: mysql8labels:name: mysql8# kubectl apply -f mysql8-namespace.yaml namespace/mysql8 created# kubectl get ns|grep mysql8 mysql8 Active 8s二、创建mysql配…

SSM学习4:spring整合mybatis、spring整合Junit

spring整合mybatis 之前的内容是有service层&#xff08;业务实现层&#xff09;、dao层&#xff08;操作数据库&#xff09;&#xff0c;现在新添加一个domain&#xff08;与业务相关的实体类&#xff09; 依赖配置 pom.xml <?xml version"1.0" encoding&quo…

2.2.3 C#中显示控件BDPictureBox 的实现----控件实现

2.2.3 C#中显示控件BDPictureBox 的实现----控件实现 1 界面控件布局 2图片内存Mat类说明 原始图片&#xff1a;m_raw_mat ,Display_Mat()调用时更新或者InitDisplay_Mat时更新局部放大显示图片&#xff1a;m_extract_zoom_mat&#xff0c;更新scale和scroll信息后更新overla…

2024年精选100道软件测试面试题(内含文档)

测试技术面试题 1、我现在有个程序&#xff0c;发现在 Windows 上运行得很慢&#xff0c;怎么判别是程序存在问题还是软硬件系统存在问题&#xff1f; 2、什么是兼容性测试&#xff1f;兼容性测试侧重哪些方面&#xff1f; 3、测试的策略有哪些&#xff1f; 4、正交表测试用…

市场规模5万亿,护理员缺口550万,商业护理企业如何解决服务供给难题?

干货抢先看 1. 据统计&#xff0c;我国失能、半失能老人数量约4400万&#xff0c;商业护理服务市场规模达5万亿。然而&#xff0c;当前养老护理员缺口巨大&#xff0c;人员的供需不匹配是很多养老服务企业需要克服的难题。 2. 当前居家护理服务的主要市场参与者分为两类&…

利用GPT 将 matlab 内置 bwlookup 函数转C

最近业务需要将 matlab中bwlookup 的转C 这个函数没有现成的m文件参考&#xff0c;内置已经打成库了&#xff0c;所以没有参考源代码 但是它的解释还是很清楚的&#xff0c;可以根据这个来写 Nonlinear filtering using lookup tables - MATLAB bwlookup - MathWorks 中国 A…

python请求报错::requests.exceptions.ProxyError: HTTPSConnectionPool

在发送网页请求时&#xff0c;发现很久未响应&#xff0c;最后报错&#xff1a; requests.exceptions.ProxyError: HTTPSConnectionPool(hostsvr-6-9009.share.51env.net, port443): Max retries exceeded with url: /prod-api/getInfo (Caused by ProxyError(Unable to conne…

秒懂设计模式--学习笔记(5)【创建篇-抽象工厂】

目录 4、抽象工厂4.1 介绍4.2 品牌与系列&#xff08;针对工厂泛滥&#xff09;(**分类**)4.3 产品规划&#xff08;**数据模型**&#xff09;4.4 生产线规划&#xff08;**工厂类**&#xff09;4.5 分而治之4.6 抽象工厂模式的各角色定义如下4.7 基于此抽象工厂模式以品牌与系…

vue启动时的错误

解决办法一&#xff1a;在vue.config.js中直接添加一行代码 lintOnSave:false 关闭该项目重新运行就可启动 解决办法二&#xff1a; 修改组件名称

配音软件有哪些?分享五款超级好用的配音软件

随着嫦娥六号的壮丽回归&#xff0c;举国上下都沉浸在这份自豪与激动之中。 在这样一个历史性的时刻&#xff0c;我们何不用声音记录下这份情感&#xff0c;让这份记忆以声音的形式流传&#xff1f; 无论是制作视频分享这份喜悦&#xff0c;还是创作音频讲述探月故事&#xff…

Oracle数据库中RETURNING子句

RETURNING子句允许您检索插入、删除或更新所修改的列&#xff08;以及基于列的表达式&#xff09;的值。如果不使用RETURNING&#xff0c;则必须在DML语句完成后运行SELECT语句&#xff0c;才能获得更改列的值。因此&#xff0c;RETURNING有助于避免再次往返数据库&#xff0c;…

CXL-GPU: 全球首款实现百ns以内的低延迟CXL解决方案

数据中心在追求更高性能和更低总拥有成本&#xff08;TCO&#xff09;的过程中面临三大主要内存挑战。首先&#xff0c;当前服务器内存层次结构存在局限性。直接连接的DRAM与固态硬盘&#xff08;SSD&#xff09;存储之间存在三个数量级的延迟差异。当处理器直接连接的内存容量…

VideoPrism——探索视频分析领域模型的算法与应用

概述 论文地址:https://arxiv.org/pdf/2402.13217.pdf 视频是我们观察世界的生动窗口&#xff0c;记录了从日常瞬间到科学探索的各种体验。在这个数字时代&#xff0c;视频基础模型&#xff08;ViFM&#xff09;有可能分析如此海量的信息并提取新的见解。迄今为止&#xff0c;…