# 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的版本 ![输入图片说明](image/imageimage.png) 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 getAuthorities() { Collection 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 尝试接口是否能正常访问 ![img.png](image/img.png) 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(); } } ``` 配置登录验证过滤器,配置登录成功处理器,配置登录失败处理器,配置退出登录处理器,配置未登录处理器,配置权限不足处理器 。具体看代码 ![img.png](image/1.png) 万事具备,只欠东风。下面我们配置安全过滤器链 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,使用刚刚注册的用户登录。 ![img.png](image/2.png) 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中添加过滤器 ![img.png](image/4.png) 7.登录成功处理,在CustomAuthenticationSuccessHandler.java 中生成token 8.登录测试 正常返回token内容 ![img.png](image/5.png) 9.使用token访问接口 访问 http://localhost:9999/api/user/info 添加token到header中 ![img.png](image/6.png) #### 参与贡献 ### **_目前更新到这,如有问题可以 issue提问。后面继续更新_** 1. Fork 本仓库 2. 新建 Feat_xxx 分支 3. 提交代码 4. 新建 Pull Request