文章目录
- 前言
- 1 技术简介
- 2 项目构建
- 3 项目配置
-
* 3.1 鉴权配置
- 3.2 登录配置
- 3.3 Token如何生成
- 3.4 注册和登录
- 3.5 请求过滤
- 3.6 退出登录
- 4 鉴权
-
* 4.1 controller
- 4.2 配置文件
前言
Spring Security
已经成为java
后台权限校验的第一选择.今天就通过读代码的方式带大家深入了解一下Security,本文主要是基于开源项目[spring-
boot-3-jwt-security](https://github.com/ali-bouali/spring-boot-3-jwt-
security)来讲解Spring Security + JWT(Json Web Token).实现用户鉴权,以及权限校验.
所有代码基于jdk17+
构建.现在让我们开始吧!
1 技术简介
Springboot 3.0
Spring Security
Json Web Token(JWT)
BCrypt
Maven
2 项目构建
- 项目使用
postgresql
数据库来存储用户信息以及Token
(为啥不用Redis?这个先挖个坑),可以按照自己的想法替换成mysql
数据库 - 访问数据库使用的是
jpa
,对于一些简单的sql可以根据方法名自动映射,还是很方便的.没使用过的也没关系.不影响阅读今天的文章,后续可以根据自己的实际需求替换成mybatis-lpus
等 - 本文使用了Lombok来生成固定的模版代码
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.alibou</groupId>
<artifactId>security</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>security</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- spring security 安全框架 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 数据库 -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
</dependency>
<!-- doc 这个不需要的可以去掉 -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.1.0</version>
</dependency>
<!-- 校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
3 项目配置
3.1 鉴权配置
- 当项目引入
Security
依赖后,启动项目会生成一个随机的密码,当我们要访问资源的时候需要使用这个密码登录后才能使用.这会影响我们很多功能的正常使用,比如万恶的swagger
.下面我们来详细了解如何配置我们需要鉴权的路径,以及需要放行的路径
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableMethodSecurity
public class SecurityConfiguration {
private final JwtAuthenticationFilter jwtAuthFilter;
private final AuthenticationProvider authenticationProvider;
private final LogoutHandler logoutHandler;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf()
.disable() //关闭csrf(跨域)
.authorizeHttpRequests()
//配置需要放行的路径
.requestMatchers(
"/api/v1/auth/**",
"/v2/api-docs",
"/v3/api-docs",
"/v3/api-docs/**",
"/swagger-resources",
"/swagger-resources/**",
"/configuration/ui",
"/configuration/security",
"/swagger-ui/**",
"/webjars/**",
"/swagger-ui.html"
)
.permitAll() //放行上述的所有路径
/*
* 权限校验(需要登录的用户有指定的权限才可以)
* requestMatchers: 指定需要拦截的路径
* hasAnyAuthority: 指定需要的权限
*/
.requestMatchers("/api/v1/management/**").hasAnyRole(ADMIN.name(), MANAGER.name())
.requestMatchers(GET, "/api/v1/management/**").hasAnyAuthority(ADMIN_READ.name(), MANAGER_READ.name())
.requestMatchers(POST, "/api/v1/management/**").hasAnyAuthority(ADMIN_CREATE.name(), MANAGER_CREATE.name())
.requestMatchers(PUT, "/api/v1/management/**").hasAnyAuthority(ADMIN_UPDATE.name(), MANAGER_UPDATE.name())
.requestMatchers(DELETE, "/api/v1/management/**").hasAnyAuthority(ADMIN_DELETE.name(), MANAGER_DELETE.name())
.anyRequest()
.authenticated() //设置所有的请求都需要验证
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) //使用无状态Session
.and()
.authenticationProvider(authenticationProvider)
//添加jwt过滤器
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
//设置logout(当调用这个接口的时候, 会调用logoutHandler的logout方法)
.logout()
.logoutUrl("/api/v1/auth/logout")
.addLogoutHandler(logoutHandler)
.logoutSuccessHandler((request, response,authentication) -> SecurityContextHolder.clearContext())
;
return http.build();
}
}
- 上述代码主要实现了四块功能分别是:
* 放行不需要鉴权的路径(注册&登录,swagger)
* 配置访问特定的接口用户需要的权限.(比如想要删除用户必须要有删除用户的权限)
* 添加前置过滤器,用来从Token中判断用户是否合法和获取用户权限:jwtAuthFilter
* 配置退出登录的Handler,以及监听的路径.当访问这个路径的时候会自动调用logoutHandler
中的方法
3.2 登录配置
上面说到了权限和token
校验,我们先来了解一下登录的逻辑是什么样的.在security
中需要一个UserDetails
类来定义用户账户的行为.这个是用户鉴权的关键.主要有账户,密码,权限,用户状态等等.在下面代码中有详细的注释
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "_user")
public class User implements UserDetails {
@Id
@GeneratedValue
private Integer id; //主键ID
private String firstname; //名字
private String lastname; //姓氏
private String email; //邮箱
private String password; //密码
/**
* 角色枚举
*/
@Enumerated(EnumType.STRING)
private Role role;
/**
* 用户关联的Token
* 这里面使用了jpa的一对多映射
*/
@OneToMany(mappedBy = "user")
private List<Token> tokens;
/**
* 获取用户的权限
* 这里是根据角色枚举的权限来获取的(静态的而非从数据库动态读取)
* @return 用户权限列表
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return role.getAuthorities();
}
/**
* 获取用户密码
* 主要是用来指定你的password字段
* @return 用户密码
*/
@Override
public String getPassword() {
return password;
}
/**
* 获取用户账号
* 这里使用email做为账号
* @return 用户账号
*/
@Override
public String getUsername() {
return email;
}
/**
* 账号是否未过期,下面的这个几个方法都是用来指定账号的状态的,因为该项目是一个Demo,所以这里全部返回true
* @return true 未过期
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* 账号是否未锁定
* @return true 未锁定
*/
@Override
public boolean isAccountNonLocked() {
return true;
}
/**
* 密码是否未过期
* @return true 未过期
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 账号是否激活
* @return true 已激活
*/
@Override
public boolean isEnabled() {
return true;
}
}
在了解用户实体之后,我们来看一下是怎么来进行登录配置的.如何使用securty
来帮我们管理用户密码的校验.下面我们来看一下security
的整体配置
@Configuration
@RequiredArgsConstructor
public class ApplicationConfig {
/**
* 访问用户数据表
*/
private final UserRepository repository;
/**
* 获取用户详情Bean
* 根据email查询是否存在用户,如果不存在throw用户未找到异常
*/
@Bean
public UserDetailsService userDetailsService() {
//调用repository的findByEmail方法,来获取用户信息,如果存在则返回,如果不存在则抛出异常
return username -> repository.findByEmail(username)
//这里使用的Option的orElseThrow方法,如果存在则返回,如果不存在则抛出异常
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
}
/**
* 身份验证Bean
* 传入获取用户信息的bean & 密码加密器
* 可以回看一下SecurityConfiguration中 AuthenticationProvider的配置,使用的就是这里注入到容器中的Bean
* 这个bean 主要是用于用户登录时的身份验证,当我们登录的时候security会帮我们调用这个bean的authenticate方法
*/
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
//设置获取用户信息的bean
authProvider.setUserDetailsService(userDetailsService());
//设置密码加密器
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
/**
* 身份验证管理器
*/
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
/**
* 密码加密器
* 主要是用来指定数据库中存储密码的加密方式,保证密码非明文存储
* 当security需要进行密码校验时,会把请求传进来的密码进行加密,然后和数据库中的密码进行比对
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
上述代码主要做了两件事:
- 指定我们如何从数据库中根据用户账号获取用户信息
- 指定用户密码的加密器
passwordEncoder
现在大家可能会存在一个疑问,security
怎么知道User
实体中那个字段是我的账户,那个字段是我的密码?
不知道大家是否记得UserDetails
类,也就是我们的User
类.其中有两个方法getPassword
&
getUsername
.这两个方法返回的就是账号和密码.User
类中的还有几个其他的方法,可以根据我们实际的业务需求来对账号进行禁用
等操作.
3.3 Token如何生成
token
的生成主要是使用工具包来实现,在本项目中Token中主要存储用户信息
&
用户权限
,下面我们先看一下token
工具包的代码.主要包括为: 生成token
,从token
中获取信息,以及验证token
@Service
public class JwtService {
/**
* 加密盐值
*/
@Value("${application.security.jwt.secret-key}")
private String secretKey;
/**
* Token失效时间
*/
@Value("${application.security.jwt.expiration}")
private long jwtExpiration;
/**
* Token刷新时间
*/
@Value("${application.security.jwt.refresh-token.expiration}")
private long refreshExpiration;
/**
* 从Token中获取Username
* @param token Token
* @return String
*/
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
/**
* 从Token中回去数据,根据传入不同的Function返回不同的数据
* eg: String extractUsername(String token)
*/
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
/**
* 生成Token无额外信息
*/
public String generateToken(UserDetails userDetails) {
return generateToken(new HashMap<>(), userDetails);
}
/**
* 生成Token,有额外信息
* @param extraClaims 额外的数据
* @param userDetails 用户信息
* @return String
*/
public String generateToken(
Map<String, Object> extraClaims,
UserDetails userDetails
) {
return buildToken(extraClaims, userDetails, jwtExpiration);
}
/**
* 生成刷新用的Token
* @param userDetails 用户信息
* @return String
*/
public String generateRefreshToken(
UserDetails userDetails
) {
return buildToken(new HashMap<>(), userDetails, refreshExpiration);
}
/**
* 构建Token方法
* @param extraClaims 额外信息
* @param userDetails //用户信息
* @param expiration //失效时间
* @return String
*/
private String buildToken(
Map<String, Object> extraClaims,
UserDetails userDetails,
long expiration
) {
return Jwts
.builder()
.setClaims(extraClaims) //body
.setSubject(userDetails.getUsername()) //主题数据
.setIssuedAt(new Date(System.currentTimeMillis())) //设置发布时间
.setExpiration(new Date(System.currentTimeMillis() + expiration)) //设置过期时间
.signWith(getSignInKey(), SignatureAlgorithm.HS256) //设置摘要算法
.compact();
}
/**
* 验证Token是否有效
* @param token Token
* @param userDetails 用户信息
* @return boolean
*/
public boolean isTokenValid(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername())) && !isTokenExpired(token);
}
/**
* 判断Token是否过去
*/
private boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
/**
* 从Token中获取失效时间
*/
private Date extractExpiration(String token) {
//通用方法,传入一个Function,返回一个T
return extractClaim(token, Claims::getExpiration);
}
/**
* 从Token中获取所有数据
*/
private Claims extractAllClaims(String token) {
return Jwts
.parserBuilder()
.setSigningKey(getSignInKey())
.build()
.parseClaimsJws(token)
.getBody();
}
/**
* 获取签名Key
* Token 加密解密使用
*/
private Key getSignInKey() {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
return Keys.hmacShaKeyFor(keyBytes);
}
}
3.4 注册和登录
token
的生成已经看过了,下面该进入最关键的环节了.用户注册
& 用户登录
- 用户注册: 接收到用户传递过来的信息,在数据库中生成用户信息(密码会通过
passwordEncoder
进行加密).用户信息保存成功后,会根据用户信息创建一个鉴权token
和一个refreshToken
- 用户登录: 获取到用户传递的账号密码后,会创建一个
UsernamePasswordAuthenticationToken
对象.然后通过authenticationManager
的authenticate
方法进行校验,如果出现错误会根据错误的不同抛出不同的异常.在实际开发中可以通过捕获的异常类型不同来创建响应提示.
@RestController
@RequestMapping("/api/v1/auth")
@RequiredArgsConstructor
public class AuthenticationController {
private final AuthenticationService service;
/**
* 注册方法
* @param request 请求体
* @return ResponseEntity
*/
@PostMapping("/register")
public ResponseEntity<AuthenticationResponse> register(
@RequestBody RegisterRequest request
) {
return ResponseEntity.ok(service.register(request));
}
/**
* 鉴权(登录方法)
* @param request 请求体
* @return ResponseEntity
*/
@PostMapping("/authenticate")
public ResponseEntity<AuthenticationResponse> authenticate(
@RequestBody AuthenticationRequest request
) {
return ResponseEntity.ok(service.authenticate(request));
}
/**
* 刷新token
* @param request 请求体
* @param response 响应体
* @throws IOException 异常
*/
@PostMapping("/refresh-token")
public void refreshToken(
HttpServletRequest request,
HttpServletResponse response
) throws IOException {
service.refreshToken(request, response);
}
}
可以看出来controller
中的方法都是对service
方法的调用,我们现在看一下service
中的代码
@Service
@RequiredArgsConstructor
public class AuthenticationService {
private final UserRepository repository; //访问user数据库
private final TokenRepository tokenRepository; //访问token数据库
private final PasswordEncoder passwordEncoder; //密码加密器
private final JwtService jwtService; //JWT 相关方法
private final AuthenticationManager authenticationManager; //Spring Security 认证管理器
/**
* 注册方法
* @param request 请求体
* @return AuthenticationResponse(自己封装的响应结构)
*/
public AuthenticationResponse register(RegisterRequest request) {
//构建用户信息
var user = User.builder()
.firstname(request.getFirstname())
.lastname(request.getLastname())
.email(request.getEmail())
.password(passwordEncoder.encode(request.getPassword()))
.role(request.getRole())
.build();
//将用户信息保存到数据库
var savedUser = repository.save(user);
//通过JWT方法生成Token
var jwtToken = jwtService.generateToken(user);
//生成RefreshToken(刷新Token使用)
var refreshToken = jwtService.generateRefreshToken(user);
//将Token保存到数据库
saveUserToken(savedUser, jwtToken);
//返回响应体
return AuthenticationResponse.builder()
.accessToken(jwtToken)
.refreshToken(refreshToken)
.build();
}
/**
* 鉴权(登录)方法
* @param request 请求体
* @return AuthenticationResponse(自己封装的响应结构)
*/
public AuthenticationResponse authenticate(AuthenticationRequest request) {
//通过Spring Security 认证管理器进行认证
//如果认证失败会抛出异常 eg:BadCredentialsException 密码错误 UsernameNotFoundException 用户不存在
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.getEmail(),
request.getPassword()
)
);
//通过邮箱查询用户信息,当前项目email就是账号
var user = repository.findByEmail(request.getEmail())
.orElseThrow();
//通过JWT方法生成Token
var jwtToken = jwtService.generateToken(user);
//生成RefreshToken(刷新Token使用)
var refreshToken = jwtService.generateRefreshToken(user);
//将之前所有的Token变成失效状态
revokeAllUserTokens(user);
//保存新的Token到数据库
saveUserToken(user, jwtToken);
//封装响应体
return AuthenticationResponse.builder()
.accessToken(jwtToken)
.refreshToken(refreshToken)
.build();
}
/**
* 保存用户Token方法
* 构建Token实体后保存到数据库
* @param user 用户信息
* @param jwtToken Token
*/
private void saveUserToken(User user, String jwtToken) {
var token = Token.builder()
.user(user)
.token(jwtToken)
.tokenType(TokenType.BEARER)
.expired(false)
.revoked(false)
.build();
tokenRepository.save(token);
}
/**
* 将用户所有Token变成失效状态
* @param user 用户信息
*/
private void revokeAllUserTokens(User user) {
//获取用户所有有效的token
var validUserTokens = tokenRepository.findAllValidTokenByUser(user.getId());
if (validUserTokens.isEmpty()){
return;
}
//如果存在还为失效的token,将token置为失效
validUserTokens.forEach(token -> {
token.setExpired(true);
token.setRevoked(true);
});
tokenRepository.saveAll(validUserTokens);
}
/**
* 刷新token方法
* @param request 请求体
* @param response 响应体
* @throws IOException 抛出IO异常
*/
public void refreshToken(
HttpServletRequest request,
HttpServletResponse response
) throws IOException {
//从请求头中获取中获取鉴权信息 AUTHORIZATION
final String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
final String refreshToken;
final String userEmail;
//如果鉴权信息为空或者不是以Bearer 开头的,直接返回
if (authHeader == null ||!authHeader.startsWith("Bearer ")) {
return;
}
//从鉴权信息中获取RefreshToken
refreshToken = authHeader.substring(7);
//从RefreshToken中获取用户信息
userEmail = jwtService.extractUsername(refreshToken);
if (userEmail != null) {
//根据用户信息查询用户,如果用户不存在抛出异常
var user = this.repository.findByEmail(userEmail)
.orElseThrow();
//验证Token是否有效
if (jwtService.isTokenValid(refreshToken, user)) {
//生成新的Token
var accessToken = jwtService.generateToken(user);
revokeAllUserTokens(user);
saveUserToken(user, accessToken);
//生成新的Token和RefreshToken并通过响应体返回
var authResponse = AuthenticationResponse.builder()
.accessToken(accessToken)
.refreshToken(refreshToken)
.build();
new ObjectMapper().writeValue(response.getOutputStream(), authResponse);
}
}
}
}
上述代码主要说明了,注册
& 登录
后返回token
的流程,当前项目中由于token
&
refreshToken
有效期较长所以选择了将token
保存到数据库(个人观点!!!).可以根据自己业务的实际需求来决定是否需要保存到redis
3.5 请求过滤
请求过滤主要是在每次请求的时候动态解析token
来获取用户信息
以及权限
,来保证请求资源的安全性.防止越权访问等.
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserDetailsService userDetailsService;
private final TokenRepository tokenRepository;
@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain
) throws ServletException, IOException {
//判断请求是否为登录请求,如果是登录请求则不进行处理
if (request.getServletPath().contains("/api/v1/auth")) {
filterChain.doFilter(request, response);
return;
}
//从请求头中获取鉴权authHeader
final String authHeader = request.getHeader("Authorization");
final String jwt;
final String userEmail;
//如果不存在Token或者Token不已Bearer开头,则不进行处理
if (authHeader == null ||!authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
//从authHeader中截取出Token信息
jwt = authHeader.substring(7);
//从Token中获取userEmail(账户)
userEmail = jwtService.extractUsername(jwt);
//SecurityContextHolder 中的 Authentication 为空时,才进行处理
if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
//获取用户信息
UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail);
//从数据库中查询Token并判断Token状态是否正常
var isTokenValid = tokenRepository.findByToken(jwt)
.map(t -> !t.isExpired() && !t.isRevoked())
.orElse(false);
//如果Token有效,并且Token状态正常,将用户信息存储到SecurityContextHolder
if (jwtService.isTokenValid(jwt, userDetails) && isTokenValid) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
userDetails, //用户信息
null,
userDetails.getAuthorities() //用户的权限
);
authToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request) //访问信息
);
//将用户信息以及权限保存到 SecurityContextHolder的上下文中,方便后续使用
//eg: 获取当前用户id,获取当前用户权限等等
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
}
上述代码主要逻辑为:
从请求头中获取到token
.验证token
的有效性并解析token
中的信息存储到SecurityContextHolder
上下文中,方便后续的使用.
3.6 退出登录
登录
以及token
的校验已经说过了,现在就差一个退出登录了.大家是否还记得我们之前配置过一个退出登录
的请求路径:
/api/v1/auth/logout
.当我们请求请求这个路径的时候,security
会帮我们找到对应的LogoutHandler
,然后调用logout
方法实现退出登录.
@Service
@RequiredArgsConstructor
public class LogoutService implements LogoutHandler {
private final TokenRepository tokenRepository;
@Override
public void logout(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication
) {
//从请求头中获取鉴权信息
final String authHeader = request.getHeader("Authorization");
final String jwt;
if (authHeader == null ||!authHeader.startsWith("Bearer ")) {
return;
}
//接续出token
jwt = authHeader.substring(7);
//从数据库中查询出token信息
var storedToken = tokenRepository.findByToken(jwt)
.orElse(null);
if (storedToken != null) {
//设置token过期
storedToken.setExpired(true);
storedToken.setRevoked(true);
tokenRepository.save(storedToken);
//清除SecurityContextHolder上下文
SecurityContextHolder.clearContext();
}
}
}
security
帮我们做了很多的事情,我们只需要把token
置为失效状态,然后清除掉SecurityContextHolder
上下文,就解决了全部的问题
4 鉴权
下面通过几个例子,来讲解两种不同的鉴权配置方式
4.1 controller
@RestController
@RequestMapping("/api/v1/admin")
@PreAuthorize("hasRole('ADMIN')") //用户需要ADMIN角色才能访问
public class AdminController {
@GetMapping
@PreAuthorize("hasAuthority('admin:read')") //用户需要admin:read权限才能访问
public String get() {
return "GET:: admin controller";
}
@PostMapping
@PreAuthorize("hasAuthority('admin:create')") //用户需要admin:create权限才能访问
@Hidden
public String post() {
return "POST:: admin controller";
}
@PutMapping
@PreAuthorize("hasAuthority('admin:update')")
@Hidden
public String put() {
return "PUT:: admin controller";
}
@DeleteMapping
@PreAuthorize("hasAuthority('admin:delete')")
@Hidden
public String delete() {
return "DELETE:: admin controller";
}
}
4.2 配置文件
下面贴出SecurityConfiguration
配置类的部分代码
![在这里插入图片描述](https://img-
blog.csdnimg.cn/aaf04e20ba4d421290155cb90f7a0c9c.png#pic_center)
学习计划安排
我一共划分了六个阶段,但并不是说你得学完全部才能上手工作,对于一些初级岗位,学到第三四个阶段就足矣~
这里我整合并且整理成了一份【282G】的网络安全从零基础入门到进阶资料包,需要的小伙伴可以扫描下方CSDN官方合作二维码免费领取哦,无偿分享!!!
如果你对网络安全入门感兴趣,那么你需要的话可以
点击这里👉网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!
①网络安全学习路线
②上百份渗透测试电子书
③安全攻防357页笔记
④50份安全攻防面试指南
⑤安全红队渗透工具包
⑥HW护网行动经验总结
⑦100个漏洞实战案例
⑧安全大厂内部视频资源
⑨历年CTF夺旗赛题解析