# springSecurity
**Repository Path**: zarchary/spring-security
## Basic Information
- **Project Name**: springSecurity
- **Description**: springboot3+springSecurity6+JWT
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2024-03-05
- **Last Updated**: 2024-03-25
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# springSecurity
#### 介绍
springboot3+springSecurity6+JWT
#### 软件架构
理解springSecurity的模式以及过滤链执行的过程
1.系统搭建流程,先导入基础包
本文使用 springboot3.1 为基础搭建。版本按照pom里面提供的版本,否则会出现兼容问题。
可以选择使用spingboot 初始化模板,后面修改 springboot的版本

2.导入依赖包
基础依赖包: spring-boot-starter-web、mysql-connector-java、Mybatis,openapi3
```xml
org.springframework.boot
spring-boot-devtools
runtime
true
org.springframework.boot
spring-boot-configuration-processor
true
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-web
mysql
mysql-connector-java
8.0.33
com.github.xiaoymin
knife4j-openapi3-jakarta-spring-boot-starter
4.4.0
com.baomidou
mybatis-plus-boot-starter
org.mybatis
mybatis-spring
3.5.4.1
org.mybatis
mybatis-spring
3.0.3
```
3.配置文件
```yaml
端口配置 在properties文件中配置
其余配置放在yml文件中,包含数据库配置,mybatis配置,swagger配置,JWT配置
```
4.创建数据库
可简单创建一个用户表,用于测试
创建数据库spring_security
```sql
CREATE TABLE `sys_user` (
`id` int NOT NULL AUTO_INCREMENT,
`account` varchar(32) NOT NULL COMMENT '账号',
`user_name` varchar(32) NOT NULL COMMENT '用户名',
`password` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '用户密码',
`last_login_time` datetime DEFAULT NULL COMMENT '上一次登录时间',
`enabled` tinyint(1) DEFAULT '1' COMMENT '账号是否可用。默认为1(可用)',
`not_expired` tinyint(1) DEFAULT '1' COMMENT '是否过期。默认为1(没有过期)',
`account_not_locked` tinyint(1) DEFAULT '1' COMMENT '账号是否锁定。默认为1(没有锁定)',
`credentials_not_expired` tinyint(1) DEFAULT '1' COMMENT '证书(密码)是否过期。默认为1(没有过期)',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
`create_user` int DEFAULT NULL COMMENT '创建人',
`update_user` int DEFAULT NULL COMMENT '修改人',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';
SET FOREIGN_KEY_CHECKS = 1;
```
5.创建实体类
```java
package com.demo.springSecurity.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.demo.springSecurity.enmus.Role;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
/**
* @pack_name com.demo.springsecurity_demo.entity
* @project_name Java_demo
*
* @author zzz
* @date 2024/2/28 09:02:54
* @Description ${TODO}
*/
/**
* 用户表
*/
@Schema(description="用户表")
@Data
@NoArgsConstructor
@TableName(value = "sys_user")
public class SysUser implements Serializable, UserDetails{
@TableId(value = "id", type = IdType.AUTO)
@Schema(hidden = true)
private Integer id;
/**
* 账号
*/
@TableField(value = "account")
@Schema(description="账号")
private String account;
/**
* 用户名
*/
@TableField(value = "user_name")
@Schema(description="用户名")
private String username;
/**
* 用户密码
*/
@TableField(value = "`password`")
@Schema(description="用户密码")
private String password;
/**
* 上一次登录时间
*/
@TableField(value = "last_login_time")
@Schema(description="上一次登录时间")
private Date lastLoginTime;
/**
* 是否过期。默认为1(没有过期)
*/
@TableField(value = "not_expired")
@Schema(description="是否过期。默认为1(没有过期)")
private Boolean notExpired;
/**
* 账号是否锁定。默认为1(没有锁定)
*/
@TableField(value = "account_not_locked")
@Schema(description="账号是否锁定。默认为1(没有锁定)")
private Boolean accountNotLocked;
/**
* 证书(密码)是否过期。默认为1(没有过期)
*/
@TableField(value = "credentials_not_expired")
@Schema(description="证书(密码)是否过期。默认为1(没有过期)")
private Boolean credentialsNotExpired;
/**
* 创建时间
*/
@TableField(value = "create_time")
@Schema(description="创建时间")
private Date createTime;
/**
* 修改时间
*/
@TableField(value = "update_time")
@Schema(description="修改时间")
private Date updateTime;
/**
* 创建人
*/
@TableField(value = "create_user")
@Schema(description="创建人")
private Integer createUser;
/**
* 修改人
*/
@TableField(value = "update_user")
@Schema(description="修改人")
private Integer updateUser;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
@TableField(exist = false)
@Schema(hidden = true)
private Role role;
// we should return a list of roles
@Override
@Schema(hidden = true)
public Collection extends GrantedAuthority> getAuthorities() {
Collection extends GrantedAuthority> getAuthorities = new ArrayList<>();
return getAuthorities;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
```
6.创建mapper
```java
package com.demo.springSecurity.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.demo.springSecurity.entity.SysUser;
import org.apache.ibatis.annotations.Mapper;
/**
* @pack_name com.demo.springsecurity_demo.mapper
* @project_name Java_demo
*
* @author zzz
* @date 2024/2/28 09:02:54
* @Description ${TODO}
*/
@Mapper
public interface SysUserMapper extends BaseMapper {
}
```
7.创建service
```java
package com.demo.springSecurity.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.demo.springSecurity.common.ApiResult;
import com.demo.springSecurity.entity.SysUser;
/**
* @pack_name com.demo.springsecurity_demo.service
* @project_name Java_demo
*
* @author zzz
* @date 2024/2/28 09:02:54
* @Description ${TODO}
*/
public interface SysUserService extends IService {
ApiResult updateUserPassword(SysUser user);
ApiResult saveUser(SysUser user);
SysUser getByUsername(String username);
}
```
8.创建serviceImpl
```java
package com.demo.springSecurity.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.demo.springSecurity.common.ApiResult;
import com.demo.springSecurity.entity.SysUser;
import com.demo.springSecurity.mapper.SysUserMapper;
import com.demo.springSecurity.service.SysUserService;
import io.micrometer.common.util.StringUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
/**
* @pack_name com.demo.springsecurity_demo.service.impl
* @project_name Java_demo
*
* @author zzz
* @date 2024/2/28 09:02:54
* @Description ${TODO}
*/
@Service
@RequiredArgsConstructor
public class SysUserServiceImpl extends ServiceImpl implements SysUserService {
private final SysUserMapper sysUserMapper;
@Override
public ApiResult updateUserPassword(SysUser user) {
LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
if (user.getId() !=null){
wrapper.eq(SysUser::getId,user.getId() );
}
if (user.getUsername() !=null){
wrapper.eq(SysUser::getUsername,user.getUsername() );
}
SysUser one = this.getOne(wrapper);
if (one ==null){
return ApiResult.fail("修改用户不存在");
}
// PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
// String encode = encoder.encode(user.getPassword());
// user.setPassword(encode);
this.updateById(user);
return ApiResult.success("修改成功");
}
@Override
public ApiResult saveUser(SysUser user) {
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
String encode = encoder.encode(user.getPassword());
user.setAccount(user.getUsername());
user.setPassword(encode);
this.save(user);
return ApiResult.success("添加成功");
}
@Override
public SysUser getByUsername(String username) {
if (StringUtils.isNotBlank(username)){
return this.getOne(new LambdaQueryWrapper().eq(SysUser::getUsername,username));
}
return null;
}
}
```
9.创建controller
```java
package com.demo.springSecurity.controller;
import com.demo.springSecurity.common.ApiResult;
import com.demo.springSecurity.entity.SysUser;
import com.demo.springSecurity.service.SysUserService;
import io.swagger.v3.oas.annotations.security.SecurityRequirements;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;
/**
* @author zzz
* @pack_name com.demo.springsecurity_demo2.controller
* @project_name Java_demo
* @date 2024/3/4 11:03:29
* @Description
*/
@Tag(name = "Authentication", description = "The Authentication API. Contains operations like login, logout, refresh-token etc.")
@RestController
@RequestMapping("/user")
@SecurityRequirements() /*
This API won't have any security requirements. Therefore, we need to override the default security requirement configuration
with @SecurityRequirements()
*/
@RequiredArgsConstructor
public class AuthenticationController {
private final SysUserService sysUserService;
@PostMapping("/register")
public ApiResult register( @RequestBody SysUser user) {
String password = user.getPassword();
return sysUserService.saveUser(user);
}
@GetMapping("info")
public ApiResult info() {
// 从上下文获取用户信息
UserDetails principal = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
SysUser byUsername = sysUserService.getByUsername(principal.getUsername());
return ApiResult.success(byUsername);
}
}
```
10.确认openapi3配置. 确认packages-to-scan 为controller的包路径
```yaml
# springdoc-openapi项目配置
springdoc:
swagger-ui:
path: /swagger-ui.html
tags-sorter: alpha
operations-sorter: alpha
api-docs:
path: /v3/api-docs
group-configs:
- group: 'default'
paths-to-match: '/**'
packages-to-scan: com.demo.springSecurity.controller
# knife4j的增强配置,不需要增强可以不配
knife4j:
enable: true
setting:
language: zh_cn
```
检查是否有swagger-ui.html页面,此时可以访问http://localhost:9999/api/doc.html
尝试接口是否能正常访问

11.创建security配置类
如果能正常访问,下面继续添加springSecurity内容。先添加依赖
```xml
org.springframework.boot
spring-boot-starter-security
```
由于security默认使用form表单登录,下面我们将禁用原有方式。使用自定义的方式验证登录
禁用原有方式,需要自定义过滤器,并且添加自己的配置类;代码如下
```java
package com.demo.springSecurity.filter;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.SneakyThrows;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* @author zzz
* @pack_name com.demo.springsecurity_demo2.filter
* @project_name Java_demo
* @date 2024/3/4 17:03:46
* @Description 登录 json 格式
*/
public class JsonUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
Pair pair = obtainUsernameAndPassword(request);
String username = pair.getLeft();
username = (username != null) ? username.trim() : "";
String password = pair.getRight();
password = (password != null) ? password : "";
UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
@SneakyThrows
private Pair obtainUsernameAndPassword(HttpServletRequest request) {
JSONObject map = JSON.parseObject(request.getInputStream(), JSONObject.class);
return Pair.of(map.getString(getUsernameParameter()), map.getString(getPasswordParameter()));
}
}
```
配置登录过滤器
```java
package com.demo.springSecurity.config;
import com.demo.springSecurity.filter.JsonUsernamePasswordAuthenticationFilter;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
/**
* @author zzz
* @pack_name com.demo.springsecurity_demo2.config
* @project_name Java_demo
* @date 2024/3/5 10:03:33
* @Description 自定义验证配置
*/
public class JsonLoginConfigurer> extends
AbstractAuthenticationFilterConfigurer, JsonUsernamePasswordAuthenticationFilter> {
public JsonLoginConfigurer() {
super(new JsonUsernamePasswordAuthenticationFilter(), null);
}
@Override
protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
return new AntPathRequestMatcher(loginProcessingUrl, "POST");
}
}
```
完成上述步骤后,我们需要配置安全过滤器链,代码如下
其中,登录验证需要
1.SysUser实现UserDetails接口,重写方法,返回用户信息。
2.定义一个用户操作类ApplicationSecurityConfig,注入UserDetailsService实现用户验证,注入PasswordEncoder实现密码加密,注入AuthenticationManager实现登录验证,注入AuthenticationProvider。
3.配置登录验证过滤器,配置登录成功处理器,配置登录失败处理器,配置退出登录处理器,配置未登录处理器,配置权限不足处理器。
4.配置安全过滤器链
定义一个用户操作类ApplicationSecurityConfig
```java
package com.demo.springSecurity.config;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.demo.springSecurity.entity.SysUser;
import com.demo.springSecurity.service.SysUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @author zzz
* @pack_name com.demo.springsecurity_demo1.config
* @project_name Java_demo
* @date 2024/3/4 09:03:39
* @Description 应用配置
*/
@Configuration
@RequiredArgsConstructor
//@EnableMethodSecurity
public class ApplicationSecurityConfig {
private final SysUserService sysUserService;
@Bean
public UserDetailsService userDetailsService(){
return username -> {
SysUser user = sysUserService.getOne(new LambdaQueryWrapper().eq(SysUser::getUsername, username));
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
return new User(user.getUsername(), user.getPassword(), user.isEnabled(), user.isAccountNonExpired(), user.isCredentialsNonExpired(), user.isAccountNonLocked(), user.getAuthorities());
};
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService());
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
```
配置登录验证过滤器,配置登录成功处理器,配置登录失败处理器,配置退出登录处理器,配置未登录处理器,配置权限不足处理器
。具体看代码

万事具备,只欠东风。下面我们配置安全过滤器链
SecurityConfiguration 包含内容:允许放行的url,跨域配置,异常处理,禁用表单登录,添加自定义登录,session管理,添加自定义过滤器
```java
package com.demo.springSecurity.config;
import com.demo.springSecurity.filter.JwtAuthenticationFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS;
/**
* @author zzz
* @pack_name com.demo.springsecurity_demo2.config
* @project_name Java_demo
* @date 2024/3/4 11:03:53
* @Description
*/
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableMethodSecurity
public class SecurityConfiguration {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final AuthenticationProvider authenticationProvider;
private final CustomAccessDeniedHandler customAccessDeniedHandler;
private final Http401UnauthorizedEntryPoint http401UnauthorizedEntryPoint;
private final CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;
private final CustomAuthenticationFailHandler customAuthenticationFailHandler;
// 配置白名单
public static final String[] URL_WHITELIST = {
"/favicon.ico",
"/webjars/**",
"/img.icons/**",
"/swagger-resources/**",
"/v3/api-docs/**",
"/swagger-ui/**",
"/doc.html",
"/login",
"/user/register",
};
// 配置安全过滤器链
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
//关闭csrf
http.csrf(AbstractHttpConfigurer::disable);
//跨域
http.cors(c -> c.configurationSource(request -> {
var cors = new org.springframework.web.cors.CorsConfiguration();
cors.setAllowedMethods(java.util.List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
cors.setAllowedHeaders(java.util.List.of("*"));
cors.setAllowCredentials(true);
cors.addAllowedOriginPattern("*");
return cors;
}));
//允许访问的url
http.authorizeHttpRequests(a -> a
.requestMatchers(URL_WHITELIST).permitAll()
.requestMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().authenticated()
);
//异常处理
http.exceptionHandling(e -> e
.accessDeniedHandler(customAccessDeniedHandler)
.authenticationEntryPoint(http401UnauthorizedEntryPoint)
);
//禁用表单登录
http.formLogin(AbstractHttpConfigurer::disable).apply(new JsonLoginConfigurer<>())
.loginProcessingUrl("/login")
.successHandler(customAuthenticationSuccessHandler)
.failureHandler(customAuthenticationFailHandler);
//添加自定义登录
//session管理
http.sessionManagement(manager -> manager.sessionCreationPolicy(STATELESS)
);
http.authenticationProvider(authenticationProvider)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
```
至此,我们的springSecurity配置完成。下面我们测试一下。
1.启动项目,访问http://localhost:9999/api/doc.html,注册一个用户
2.使用postman访问http://localhost:9999/login,使用刚刚注册的用户登录。

3.登录成功至此结束
下面我们来看一下JWT的配置
1.添加依赖
```xml
io.jsonwebtoken
jjwt-api
0.11.5
io.jsonwebtoken
jjwt-impl
0.11.5
io.jsonwebtoken
jjwt-jackson
0.11.5
com.alibaba.fastjson2
fastjson2
2.0.37
```
2.创建JWT工具类
JWT 工具类包含内容:生成token,解析token,获取token中的用户名,验证token是否有效,生成cookie,获取cookie中的token,获取清除cookie
```java
package com.demo.springSecurity.service;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.ResponseCookie;
/**
* @author zzz
* @pack_name com.demo.springsecurity_demo1.service
* @project_name Java_demo
* @date 2024/3/4 08:03:46
* @Description jwt服务
*/
public interface JwtService {
String extractUserName(String token);
String generateToken(String username);
boolean isTokenValid(String token, String username);
ResponseCookie generateJwtCookie(String jwt);
String getJwtFromCookies(HttpServletRequest request);
ResponseCookie getCleanJwtCookie();
}
```
3.创建JWT工具类实现类
```java
package com.demo.springSecurity.service.impl;
import com.demo.springSecurity.service.JwtService;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseCookie;
import org.springframework.stereotype.Service;
import org.springframework.web.util.WebUtils;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
/**
* @author zzz
* @pack_name com.demo.springsecurity_demo1.service.impl
* @project_name Java_demo
* @date 2024/3/4 09:03:44
* @Description
*/
@Service
@Slf4j
public class JwtServiceImpl implements JwtService {
@Value("${application.security.jwt.secret-key}")
private String secretKey;
@Value("${application.security.jwt.expiration}")
private long jwtExpiration;
@Value("${application.security.jwt.refresh-token.expiration}")
private long refreshExpiration;
@Value("${application.security.jwt.cookie-name}")
private String jwtCookieName;
@Override
public String extractUserName(String token) {
return getUsername(token);
}
@Override
public String generateToken(String userDetails) {
return generateToken(new HashMap<>(), userDetails);
}
@Override
public boolean isTokenValid(String token, String username) {
final String userName =getUsername(token);
return (userName.equals(username)) && !isTokenExpired(token);
}
private String getUsername(String token){
return Jwts
.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody()
.getSubject();
}
@Override
public ResponseCookie generateJwtCookie(String jwt) {
return ResponseCookie.from(jwtCookieName, jwt)
.path("/")
.maxAge(24 * 60 * 60) // 24 hours
.httpOnly(true)
.secure(true)
.sameSite("Strict")
.build();
}
@Override
public String getJwtFromCookies(HttpServletRequest request) {
Cookie cookie = WebUtils.getCookie(request, jwtCookieName);
if (cookie != null) {
return cookie.getValue();
} else {
return null;
}
}
@Override
public ResponseCookie getCleanJwtCookie() {
return ResponseCookie.from(jwtCookieName, "")
.path("/")
.httpOnly(true)
.maxAge(0)
.build();
}
private boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
private Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
private String generateToken(Map extraClaims, String username) {
return buildToken(extraClaims, username,jwtExpiration);
}
private String buildToken(
Map extraClaims,
String username,
long expiration
) {
return Jwts
.builder()
.signWith(getSigningKey(),SignatureAlgorithm.HS256)
.setSubject(username)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis()+expiration))
.compact();
}
private T extractClaim(String token, Function claimsResolvers) {
final Claims claims = extractAllClaims(token);
String subject = claims.getSubject();
log.info("subject: {}", subject);
return claimsResolvers.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
}
private Key getSigningKey() {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
return Keys.hmacShaKeyFor(keyBytes);
}
}
```
4.配置JWT
```yaml
## jwt 设置
application:
security:
jwt:
secret-key: 586B633834416E396D7436753879382F423F4428482B4C6250655367566B5970
expiration: 900000 # 15 min in ms
cookie-name: jwt-cookie
refresh-token:
expiration: 1296000000 # 15 days in ms
cookie-name: refresh-jwt-cookie
```
5.配置JWT过滤器
```java
package com.demo.springSecurity.filter;
import com.demo.springSecurity.service.JwtService;
import io.micrometer.common.util.StringUtils;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
/**
* @author zzz
* @pack_name com.demo.springsecurity_demo2.filter
* @project_name Java_demo
* @date 2024/3/4 13:03:52
* @Description JWT过滤器
*/
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String jwt = jwtService.getJwtFromCookies(request);
final String authHeader = request.getHeader("Authorization");
if((jwt == null && (authHeader == null || !authHeader.startsWith("Bearer "))) || request.getRequestURI().contains("/user/register") ){
//登录
filterChain.doFilter(request, response);
return;
}
// If the JWT is not in the cookies but in the "Authorization" header
if (jwt == null && authHeader.startsWith("Bearer ")) {
jwt = authHeader.substring(7); // after "Bearer "
}
final String userEmail =jwtService.extractUserName(jwt);
/*
SecurityContextHolder: is where Spring Security stores the details of who is authenticated.
Spring Security uses that information for authorization.*/
if(StringUtils.isNotEmpty(userEmail)
&& SecurityContextHolder.getContext().getAuthentication() == null){
UserDetails userDetails = userDetailsService.loadUserByUsername(userEmail);
if(jwtService.isTokenValid(jwt, userDetails.getUsername())){
//update the spring security context by adding a new UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request,response);
}
}
```
6.配置JWT过滤器
在SecurityConfiguration中添加过滤器

7.登录成功处理,在CustomAuthenticationSuccessHandler.java 中生成token
8.登录测试
正常返回token内容

9.使用token访问接口
访问 http://localhost:9999/api/user/info
添加token到header中

#### 参与贡献
### **_目前更新到这,如有问题可以 issue提问。后面继续更新_**
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码
4. 新建 Pull Request