【万字长文】SpringBoot整合SpringSecurity+JWT+Redis完整教程(提供Gitee源码)

前言:最近在学习SpringSecurity的过程中,参考了很多网上的教程,同时也参考了一些目前主流的开源框架,于是结合自己的思路写了一个SpringBoot整合SpringSecurity+JWT+Redis完整的项目,从0到1写完感觉还是收获到不少的,于是我把我完整的笔记写成博客分享给大家,算是比较全的一个项目了,仅供大家参考和学习哦!

目录

一、SpringSecurity简介

二、SpringSecurity认证流程

三、项目核心代码讲解

3.1、导入pom依赖

3.2、yml配置文件

3.3、实体类

3.3.1、登录实体类

3.3.2、角色类

3.3.3、用户类

3.3.4、登录用户信息

3.4、TokenService服务类

3.4.1、生成令牌核心代码

3.4.2、生成令牌关键逻辑

3.4.3、解析令牌核心代码

3.4.4、获取请求头中携带的令牌

3.4.5、获取Redis中存放的令牌Key

3.4.6、刷新令牌有效期

3.4.7、验证令牌有效期

3.4.8、获取用户身份信息

3.5、配置认证失败处理类

3.6、配置Token认证过滤器

3.7、SecurityConfig核心配置类

3.8、线程本地的存储

3.9、查询用户接口

3.10、密码验证服务类 

3.11、认证用户服务类

3.12、登录接口

3.13、登录接口核心逻辑

3.14、测试接口

四、运行项目 

4.1、登录成功

4.2、登录失败 

五、Gitee源码地址

六、总结


一、SpringSecurity简介

SpringSecurity是Spring生态系统中的安全管理框架,提供了一套Web应用安全性的完整解决方案。

它具有以下特点:

1、全面性:SpringSecurity提供了认证、授权、攻击防护等安全管理的全部功能。

2、扩展性:可以通过继承类、实现接口等方式轻松扩展SpringSecurity的功能。

3、与Spring无缝集成:可以与Spring框架完美整合,通过SpringIoC容器管理SpringSecurity组件。

4、防范常见攻击:可以防止脚本注入、会话固定、SQL注入等常见Web攻击。

5、配置简单:通过配置文件可以快速应用SpringSecurity带来的安全功能。

SpringSecurity的主要功能包括:

1、认证(Authentication):验证用户身份信息的合法性。

2、授权(Authorization):验证用户是否有权限执行操作。

3、防护攻击:防御如CSRF、Session固定、SQL注入等攻击。

4、方法安全:实现与系统方法的安全访问控制。

5、安全响应头:添加浏览器安全相关的响应头,提高安全性。

综上,SpringSecurity是一个MVC应用不可或缺的安全防护框架,为Java应用提供全面的安全支持。它与Spring框架集成紧密,配置简单,使用方便。

二、SpringSecurity认证流程

Spring Security认证流程中的几个核心类及其作用如下:

1Authentication:认证信息接口,表示当前用户的认证信息,通常使用UsernamePasswordAuthenticationToken作为实现。

2AuthenticationManager:认证管理器接口,authenticate()方法用来执行认证流程。

3ProviderManager:认证管理器接口常用实现,封装多个AuthenticationProvider

4AuthenticationProvider:具体的认证处理器,由它完成特定的认证机制。

5UserDetailsService:根据用户名加载用户信息,返回UserDetails接口的实现。

6UserDetails:包含用户信息的接口,框架中代表用户信息。

7UsernamePasswordAuthenticationFilter:处理表单登录认证的过滤器。

8AbstractAuthenticationProcessingFilter:认证处理过滤器基类。

9SecurityContextHolder:安全上下文容器,存取Authentication对象。 

这是一个大概的流程:

1、UsernamePasswordAuthenticationFilter拦截登录请求。

2、调用AuthenticationManager的authenticate()方法。

3、AuthenticationManager调用UserDetailsService获取用户信息。

4、用UserDetailsService返回的信息验证密码。

5、如果验证成功,返回一个已填充详细信息的Authentication实例。

6、调用成功后,安全上下文会被更新。

7、最后过滤器验证安全上下文,完成登录流程。

完整流程如图所示:

这是一个更为详细的流程介绍:

1、用户提交用户名和密码请求登录。

2、UsernamePasswordAuthenticationFilter拦截请求

3、封装成UsernamePasswordAuthenticationToken对象

4、调用AuthenticationManager的authenticate()方法

5、AuthenticationManager调用ProviderManager

6、ProviderManager循环调用AuthenticationProvider

7、AuthenticationProvider提取UsernamePasswordAuthenticationToken

8、AuthenticationProvider将Token传给UserDetailsService

9、UserDetailsService根据用户名加载UserDetails

10、AuthenticationProvider拿UserDetails验证Token信息

11、验证成功将返回一个填充了权限信息的Authentication对象

12、调用成功后,将Authentication设置到SecurityContextHolder

13、SecurityContextHolder存储Authentication到SecurityContext

14、UsernamePasswordAuthenticationFilter拿着Context信息进行验证

15、验证通过则登录成功,失败则抛出异常

16、前端通过捕捉异常判断登录结果

三、项目核心代码讲解

因为代码量比较庞大,所以我把整个项目的关键代码单独拿出来进行讲解,其他的次要的就不贴出来了,主要还是为了能够让大家更通俗易懂的去了解SpringSecurity的执行过程,完整的代码我会开源到Gitee,提供在文章的结尾。

3.1、导入pom依赖

完整的依赖都贴出来了。

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- lombok依赖包 --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.10</version><scope>provided</scope></dependency><!-- 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-test</artifactId><scope>test</scope></dependency><!-- jwt --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency><!-- redis依赖 对象池 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- pool 对象池 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId><version>2.11.1</version></dependency><!-- 常用工具类 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.9</version></dependency><!-- 阿里JSON解析器 --><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.34</version></dependency></dependencies>

3.2、yml配置文件

主要配置了一个Redis连接以及Token常量。

spring:redis:host: localhostport: 6379database: 0password:timeout: 10slettuce:pool:min-idle: 0max-idle: 8max-active: 8max-wait: -1mstoken:header: Authorizationsecret: oqwe9sdladwosqwqsexpireTime: 30

3.3、实体类

一共涉及了四个实体类,主要设计了一些关键的字段,并不是非常完整。

3.3.1、登录实体类

这个类主要用于接收前端传递过来的用户名和密码,然后去验证登录信息用的。

package com.example.security.domain;import lombok.Data;@Data
public class LoginBody
{/*** 用户名*/private String username;/*** 用户密码*/private String password;}

3.3.2、角色类

存放每个用户的角色信息,=需要实现序列化接口

package com.example.security.domain;import lombok.AllArgsConstructor;
import lombok.Data;import java.io.Serializable;@Data
@AllArgsConstructor
public class Role implements Serializable {/*** 角色主键*/private Long id;/*** 角色名称*/private String name;}

3.3.3、用户类

主要存放的是用户的信息,需要实现序列化接口

package com.example.security.domain;import lombok.Data;import java.io.Serializable;
import java.util.Set;@Data
public class User implements Serializable {/*** 主键*/private String id;/*** 用户名*/private String username;/*** 密码*/private String password;/*** 角色集合*/private Set<Role> roles;}

3.3.4、登录用户信息

需要实现SpringSecurity自带的UserDetails接口,并实现它所有的方法,在Spring Security中,我们可以通过GrantedAuthority接口来表示一个用户所拥有的权限。

方法解释
isAccountNonExpired()账号是否已过期
isAccountNonLocked()账号是否已锁定
isCredentialsNonExpired()凭(密码)是否已过期
isEnabled()账号是否可用

这些方法返回true的目的是简化逻辑,在没有实现对应状态判断时,默认设置为true,这样可以避免不必要的认证/授权失败。 

package com.example.security.domain;import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;
import java.util.Set;@Data
public class LoginUser implements UserDetails {public LoginUser(User user,Set<GrantedAuthority> authorities){this.user = user;this.authorities = authorities;}/*** 用户信息*/private User user;/*** 权限信息*/private Set<GrantedAuthority> authorities;/*** token信息*/private String token;/*** 登录时间*/private Long loginTime;/*** 过期时间*/private Long expireTime;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return this.authorities;}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUsername();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}

3.4、TokenService服务类

3.4.1、生成令牌核心代码

关键代码:

    private String generateToken(Map<String, Object> claims){String token = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, secret).compact();return token;}

3.4.2、生成令牌关键逻辑

1、首先生成一个随机的UUID作为token的值,设置到LoginUser对象中。

2、设置LoginUser的登录时间和过期时间(当前时间 + 过期时间)。

3、将LoginUser对象存储到Redis中,key为LOGIN_TOKEN_KEY + token,过期时间为expireTime。

4、创建JWT payload,claims中包含了登录用户的token。

5、最终调用generateToken方法生成JWT token,传入claims。

关键代码:

    public String createToken(LoginUser loginUser){String token = UUID.randomUUID().toString();loginUser.setToken(token);loginUser.setLoginTime(System.currentTimeMillis());loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);String userKey = CacheConstants.LOGIN_TOKEN_KEY + token;redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);Map<String, Object> claims = new HashMap<>();claims.put(Constants.LOGIN_USER_KEY, token);return generateToken(claims);}

3.4.3、解析令牌核心代码

关键代码:

 private Claims parseToken(String token){return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();}

3.4.4、获取请求头中携带的令牌

这里的常量是:"Bearer "

1、从请求header中获取指定名称(header)的authorization信息。

2、判断获得的token是否非空且以指定前缀(Constants.TOKEN_PREFIX)开头。

3、如果是,则移除前缀,得到最终的JWT token。

4、返回得到的JWT token字符串。

关键代码:

    private String getToken(HttpServletRequest request){String token = request.getHeader(header);if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)){token = token.replace(Constants.TOKEN_PREFIX, "");}return token;}

3.4.5、获取Redis中存放的令牌Key

关键代码:

    private String getTokenKey(String uuid){return CacheConstants.LOGIN_TOKEN_KEY + uuid;}

3.4.6、刷新令牌有效期

1、参数loginUser是当前登录的用户信息。

2、设置loginUser的新的登录时间为当前时间。

3、重新计算过期时间为当前时间 + 过期时间。

4、根据登录用户的token作为key,存储更新后的loginUser到Redis中,并设置过期时间。

5、这样就相当于刷新了token的过期时间。

关键代码:

    public void refreshToken(LoginUser loginUser){loginUser.setLoginTime(System.currentTimeMillis());loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);// 根据uuid将loginUser缓存String userKey = getTokenKey(loginUser.getToken());redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);}

3.4.7、验证令牌有效期

验证令牌有效期,相差不足20分钟,自动刷新缓存。

关键代码:

    public void verifyToken(LoginUser loginUser){long expireTime = loginUser.getExpireTime();long currentTime = System.currentTimeMillis();if (expireTime - currentTime <= MILLIS_MINUTE_TEN){refreshToken(loginUser);}}

3.4.8、获取用户身份信息

1、先从请求中获取JWT token。

2、如果token不为空,则对token进行解析,获取claims。

3、从claims中取出对应的uuid。

4、根据uuid作为key,从Redis中获取LoginUser对象。

5、如果获取成功,返回LoginUser对象。

6、如果解析token或获取用户失败,则返回null。

其中parseToken方法对JWT token进行校验和解析,获取claims信息。这个过程就是从请求 extracting JWT token,并根据token中的信息从Redis获取用户信息。

关键代码:

    public LoginUser getLoginUser(HttpServletRequest request){// 获取请求携带的令牌String token = getToken(request);if (StringUtils.isNotEmpty(token)){try{Claims claims = parseToken(token);// 解析对应的权限以及用户信息String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);String userKey = getTokenKey(uuid);LoginUser user = redisCache.getCacheObject(userKey);return user;}catch (Exception e){}}return null;}

3.5、配置认证失败处理类

AuthenticationEntryPoint是SpringSecurity中用于处理认证失败的接口,用于未登录或登录过期的情况,会触发commence方法。

1、在方法内部,首先设置了响应状态码为401 Unauthorized。

2、然后使用StringUtils生成了一个错误信息字符串,包含请求访问的接口路径和认证失败的提示。3、最后使用AjaxResult把状态码和错误信息封装成一个结果,通过ServletUtils以JSON格式写入响应中。

4、AjaxResult是一个封装AJAX请求结果的类,可以方便地生成错误或成功的响应结果。

5、ServletUtils是一个工具类,可以方便地将String数据渲染到HttpServletResponse中。

所以这个类的作用就是在认证失败时,以JSON格式返回一个包含错误代码和消息的结果到前端,前端可以根据这个结果显示对应提示或做处理。

常量UNAUTHORIZED:401

关键代码:

@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint
{@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {int code = HttpStatus.UNAUTHORIZED;String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI());ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg)));}
}

3.6、配置Token认证过滤器

OncePerRequestFilter是SpringSecurity提供的一个过滤器基类,主要用于保证过滤器在一个请求内只执行一次,JwtAuthenticationTokenFilter需要继承这个基类并重写doFilterInternal的方法。

1、从请求中获取已登录的用户LoginUser对象。

2、如果用户不为空,则验证JWT token是否有效。

3、创建UsernamePasswordAuthenticationToken用于存入SecurityContext。

4、从LoginUser中获取权限信息,创建authenticationToken。

5、使用WebAuthenticationDetailsSource()获取请求细节。

6、将authenticationToken设置到SecurityContextHolder。

7、执行过滤链。

关键代码:

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{@Value("${token.header}")private String header;@Value("${token.secret}")private String tokenKey;@Resourceprivate TokenService tokenService;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws ServletException, IOException{LoginUser loginUser = tokenService.getLoginUser(request);if (loginUser != null){tokenService.verifyToken(loginUser);UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));SecurityContextHolder.getContext().setAuthentication(authenticationToken);}chain.doFilter(request, response);}
}

3.7、SecurityConfig核心配置类

这是核心代码,注释都在代码上面了,这边就不多做阐述。

关键代码:

@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Resourceprivate UserDetailsService userDetailsService;/*** token认证过滤器*/@Resourceprivate JwtAuthenticationTokenFilter authenticationTokenFilter;/*** 认证失败处理类*/@Resourceprivate AuthenticationEntryPointImpl unauthorizedHandler;@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception{return super.authenticationManagerBean();}@Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception {httpSecurity// CSRF禁用,因为不使用session.csrf().disable()// 认证失败处理类.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()// 基于token,所以不需要session.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()// 过滤请求.authorizeRequests()//允许登录接口匿名访问.antMatchers("/login").permitAll()// 除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated().and().headers().frameOptions().disable();// 添加JWT filterhttpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);}/*** 强散列哈希加密实现*/@Beanpublic BCryptPasswordEncoder bCryptPasswordEncoder(){return new BCryptPasswordEncoder();}/*** 身份认证接口*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception{auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());}
}

3.8、线程本地的存储

主要提供了以下几个静态方法:

1、getContext()获取当前线程的Authentication对象。

2、setContext(Authentication context)设置当前线程的Authentication对象。

3、clearContext()清除当前线程的Authentication对象。

它使用ThreadLocal维护线程隔离,所以每个线程拥有自己的Authentication信息,互不干扰。

在 Spring Security 中,可以通过该类在不同层传递认证信息。

关键代码:

public class AuthenticationContextHolder
{private static final ThreadLocal<Authentication> contextHolder = new ThreadLocal<>();public static Authentication getContext(){return contextHolder.get();}public static void setContext(Authentication context){contextHolder.set(context);}public static void clearContext(){contextHolder.remove();}
}

3.9、查询用户接口

这边我偷懒了没有连接数据库,这个密码是通过如下代码加密获得的

关键代码:

PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String encode = passwordEncoder.encode("123456");
System.out.println(encode);

主要是为了演示,所以这边直接把用户信息和角色信息写死了,实际开发还是需要连接数据库的。

关键代码:

@Service
public class UserServiceImpl {public User selectUserByUsername(String username){User user = new User();user.setId(UUID.randomUUID().toString());user.setUsername(username);user.setPassword("$2a$10$ErrO7WgkEBAWVQwuJtbBve7R2.pSKUrfs7zt8XkASqJKqcetMvAUC");Set<Role> roles = new HashSet<>();Role role1 = new Role(1L, "ROLE_ADMIN");Role role2 = new Role(2L, "ROLE_USER");roles.add(role1);roles.add(role2);user.setRoles(roles);return user;}
}

3.10、密码验证服务类 

1、validate()方法用来验证用户密码。

2、它先从AuthenticationContextHolder中获取当前认证的用户名和密码。

3、然后调用matches()方法来校验密码。

4、matches()方法使用BCryptPasswordEncoder对存储的密文密码进行匹配验证。

5、如果匹配成功则验证成功,失败则验证失败。

这样通过Spring Security的AuthenticationContextHolder可以获取到当前认证principal的信息。

再结合密码加密匹配验证,就可以在服务中方便的实现密码的验证。

关键代码:

@Service
public class PasswordServiceImpl {public void validate(User user){Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext();String AuthUsername = usernamePasswordAuthenticationToken.getName();String AuthPassword = usernamePasswordAuthenticationToken.getCredentials().toString();if (matches(user, AuthPassword)) {System.out.println("验证成功!");} else {System.out.println("验证失败!");}}public boolean matches(User user, String rawPassword){BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();return passwordEncoder.matches(rawPassword, user.getPassword());}
}

3.11、认证用户服务类

1、实现Spring Security的UserDetailsService接口

UserDetailsService是Spring Security用于加载用户信息的核心接口。自定义实现可以灵活控制用户信息加载过程。

2、根据用户名加载用户信息通过userService查询数据库获取用户对象,包含用户信息如用户名、密码、角色等。

3、验证用户密码使用passwordService进行密码验证,校验登录的密码是否正确。

4、构建用户权限信息将用户的角色信息转换成GrantedAuthority授权信息集合。

5、封装用户对象返回将用户信息、权限信息封装到LoginUser对象中返回作为UserDetails。

6、在登录验证时提供用户详细信息Spring Security在登录验证时会调用此服务获取用户详细信息,以进行认证和授权。

关键代码:

@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Resourceprivate PasswordServiceImpl passwordService;@Resourceprivate UserServiceImpl userService;@Overridepublic UserDetails loadUserByUsername(String username) {User user = userService.selectUserByUsername(username);passwordService.validate(user);//取出角色和权限信息Set<Role> roles = user.getRoles();Set<GrantedAuthority> authorities = new HashSet<>();for (Role role : roles) {authorities.add(new SimpleGrantedAuthority(role.getName()));}return new LoginUser(user,authorities);}}

3.12、登录接口

所有的登录都必须走这个接口,登录成功以后会返回给用户一个令牌,以访问系统受保护的资源。

关键代码: 

@RestController
public class LoginController {@Resourceprivate LoginServiceImpl loginService;@PostMapping("/login")public AjaxResult login(@RequestBody LoginBody loginBody){AjaxResult ajax = AjaxResult.success();// 生成令牌String token = loginService.login(loginBody.getUsername(), loginBody.getPassword());ajax.put(Constants.TOKEN, token);return ajax;}}

3.13、登录接口核心逻辑

1、创建UsernamePasswordAuthenticationToken,包含用户名和密码。

2、通过AuthenticationContextHolder设置到SecurityContext。

3、使用AuthenticationManager进行认证(走的是认证用户信息服务类中的loadUserByUsername方法)。

4、从SecurityContext获取结果Authentication。

5、清除SecurityContext。

6、从Authentication获取登录用户信息LoginUser。

7、使用TokenService生成JWT token。

8、返回JWT token。

关键代码:

@Service
public class LoginServiceImpl {@Resourceprivate AuthenticationManager authenticationManager;@Resourceprivate TokenService tokenService;public String login(String username, String password){UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);AuthenticationContextHolder.setContext(authenticationToken);Authentication authentication = authenticationManager.authenticate(authenticationToken);AuthenticationContextHolder.clearContext();LoginUser loginUser = (LoginUser) authentication.getPrincipal();return tokenService.createToken(loginUser);}}

3.14、测试接口

用于验证已登录/未登录时访问的测试接口。

关键代码:

@RestController
public class HelloController {@GetMapping("/hello")private String hello(){return "Hello World!";}
}

四、运行项目 

4.1、登录成功

通过post请求发送json格式的数据进行登录。

登录成功了并返回了Token令牌!

然后把刚才获得的令牌设置到请求头当中,进行访问。

可以看到对系统的受保护资源已经有了权限进行访问! 

4.2、登录失败 

上面登录成功了,下面我们故意输错密码,登录失败!

可以很明显的看到,系统返回了401。

如果我们尝试访问测试接口,因为没有令牌,我强行访问!

很明显,依旧提示权限不足! 

五、Gitee源码地址

因为本篇博客提供的代码不是完整的,所以我把完整的项目开源到了码云上,供大家学习和参考!

项目地址:SpringBoot整合SpringSecurity+JWT+Redis完整教程

六、总结

以上就是我对于SpringSecurity以及如何在实际项目当中开发应用的个人理解,如有问题欢迎评论区留言!

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

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

相关文章

K8s Service网络详解(二)

K8s Service网络详解&#xff08;二&#xff09; Kube Proxy调度模式Kube-proxy IptablesKube-proxy IPVS Service SelectorPod DNS种常见的 DNS 服务Kube-DNSCoreDNSCorefile 配置 DNS 记录DNS 记录 ServiceDNS 记录 PodDNS 配置策略 Pod 的主机名设置优先级 Ingress Kube Pro…

Appium+python自动化(二十五)-获取控件ID(超详解)

简介 在前边的第二十二篇文章里&#xff0c;已经分享了通过获取控件的坐标点来获取点击事件的所需要的点击位置&#xff0c;那么还有没有其他方法来获取控件点击事件所需要的点击位置呢&#xff1f;答案是&#xff1a;Yes&#xff01;因为在不同的大小屏幕的手机上获取控件的坐…

ModStartCMS v6.9.0 后台多标签改进,主题色自动切换修复

ModStart 是一个基于 Laravel 模块化极速开发框架。模块市场拥有丰富的功能应用&#xff0c;支持后台一键快速安装&#xff0c;让开发者能快的实现业务功能开发。 系统完全开源&#xff0c;基于 Apache 2.0 开源协议&#xff0c;免费且不限制商业使用。 功能特性 丰富的模块市…

Redis学习2--使用java操作Redis

1、java操作Redis库的比较 Redis有各种语言的客户端可以来操作redis数据库&#xff0c;其中java语言主要有Jedis与lettuce &#xff0c;Spring Data Redis封装了上边两个客户端&#xff0c;优缺点如下&#xff1a; 2、使用Jedis操作Redis Jedis使用的基本步骤&#xff1a; 引…

企业服务器数据库被360后缀勒索病毒攻击后采取的措施

近期&#xff0c;360后缀勒索病毒的攻击事件频发&#xff0c;造成很多企业的服务器数据库遭受严重损失。360后缀勒索病毒是Beijingcrypt勒索家族中的一种病毒&#xff0c;该病毒的加密形式较为复杂&#xff0c;目前网络上没有解密工具&#xff0c;只有通过专业的技术人员对其进…

原生html—摆脱ps、excel 在线绘制财务表格加水印(html绘制表格js加水印)

文章目录 ⭐前言⭐html标签&#x1f496;table表格的属性&#x1f496;实现财务报表 ⭐结束 ⭐前言 大家好&#xff0c;我是yma16&#xff0c;本文分享原生html——绘制表格报表加水印。 背景&#xff1a;解决没有ps的情况下使用前端html制作表格报表。 html介绍 HTML&#xf…

【Android知识笔记】UI体系(一)

Activity的显示原理 setContentView 首先开发者Activity的onCreate方法中通常调用的setContentView会委托给Window的setContentView方法: 接下来看Window的创建过程: 可见Window的实现类是PhoneWindow,而PhoneWindow是在Activity创建过程中执行attach Context的时候创建的…

Kotlin多平台最佳架构指南

在这篇文章中&#xff0c;我们将对 Kotlin 多平台移动端的最佳架构进行深入探讨。在2023年&#xff0c;作为 Android 开发者&#xff0c;我们会倾向于采用 MVVM 架构&#xff0c;因为它简单、灵活且易于测试。而作为 iOS 开发者&#xff0c;我们可能会选择 MVC、Viper 等架构。…

QT基于TCP协议实现数据传输以及波形绘制

这个玩意我做了两个&#xff0c;一个是安卓app&#xff0c;一个是Windows程序。代码并非全部都是由我从无到有实现&#xff0c;只是实现了我想要的功能。多亏了巨人的肩膀&#xff0c;开源万岁&#xff01;&#xff01;&#xff01; 我把程序放到GitHub上&#xff0c;需要的可…

Vue项目如何生成树形目录结构

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、有兴趣的可以关注一手。 前言 项目的目录结构清晰、可以帮助我们更快理顺项目的整体构成。在写文档之类的时候也比较方便。生成树形目录的方式有多种&#xff0c;我这里简单介绍其中一种较为简单的实现 过…

学习数学助手Schooltech Math Resource Studio 7.0 Crack

数学资源工作室 数学工作表生成器&#xff1a;快速轻松地创建数学工作表 使用易于使用的数学工作表生成器软件创建可打印的数学练习工作表。通过练习、谜题、问题等提高数学技能。 瞄准学习需求并激励学生 Math Resource Studio 是个性化数学教学的理想软件解决方案&#xff0c…

【SDOF振荡器的非线性-非弹性多轴时间响应分析】用于SDOF振荡器非线性非弹性时程分析的鲁棒性分析研究(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f308;4 Matlab代码实现 &#x1f4a5;1 概述 进行SDOF振荡器的非线性非弹性时程分析的鲁棒性分析研究&#xff0c;旨在探究该方法对不同系统参数和分析条件变化的稳定性和可靠性。以下是一…

几百本常用计算机开发语言电子书链接

GitHub - XiangLinPro/IT_book: 本项目收藏这些年来看过或者听过的一些不错的常用的上千本书籍&#xff0c;没准你想找的书就在这里呢&#xff0c;包含了互联网行业大多数书籍和面试经验题目等等。有人工智能系列&#xff08;常用深度学习框架TensorFlow、pytorch、keras。NLP、…

用HTML写一个简单的静态购物网站

实现代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>购物网站</title> &l…

FRR+VPP

安装 三者的结合&#xff0c;实际上编译安装好就行了&#xff0c;不需要做任何代码上的修改&#xff0c;只需要安装和配置&#xff0c;然后你就有了一台路由器。 FRRouting使用frr-8.5.2版本&#xff0c;VPP使用23.06版本&#xff0c;DPDK和lcpng是VPP的插件&#xff0c;安装…

DataEase开源BI工具安装_数据全量_增量同步_大屏拖拽自动生成_多数据源支持_数据血缘分析---大数据工作笔记0183

我这里用的是Centos7.9安装的 可以通过uname -p来查看一下我们的电脑架构,可以看到是x86_64架构的 我们下第一个,这个是x86架构的,第二个arm架构的 然后解压到/opt/module中 然后再去重命名一下文件夹. 推荐200G 本地模式的功能比较多 推荐100G

umy-ui树形结构表格懒加载用法详解

效果图 在做后台时&#xff0c;使用的iview组件库中的树形表格&#xff0c;但数据量过大时会导致页面卡死&#xff0c;借助umy-ui的虚拟表格完美解决了数据量大卡顿的问题。 先放文档&#xff1a;http://www.umyui.com/umycomponent/u-table-column-api 安装 npm install u…

apifox 调用camunda engine-rest接口报错“type“: “NotFoundException“

官方文档在这&#xff1a; https://docs.camunda.org/rest/camunda-bpm-platform/7.19/ 现象 engine-rest本是可以直接请求的&#xff0c;我把openapi导入到apifox之中了&#xff0c;我测试一下接口没有能请求成功的&#xff0c;基本都报以下的错。 报错如下 {"type&qu…

Flutter:flutter_local_notifications——消息推送的学习

前言 注&#xff1a; 刚开始学习&#xff0c;如果某些案例使用时遇到问题&#xff0c;可以自行百度、查看官方案例、官方github。 简介 Flutter Local Notifications是一个用于在Flutter应用程序中显示本地通知的插件。它提供了一个简单而强大的方法来在设备上发送通知&#…

【LeetCode-简单】剑指 Offer 24. 反转链表(详解)

题目 定义一个函数&#xff0c;输入一个链表的头节点&#xff0c;反转该链表并输出反转后链表的头节点。 方法&#xff1a;迭代 思路 定义三个指针&#xff0c;一起往后走&#xff0c;走一步就修改mid指针的next&#xff0c;原本是mid的next 是right&#xff0c;我们修改成l…