文章目录
前言完整代码pom.xml(引入项目所需依赖)application.properties(配置数据库连接)JwtUtil.java(生成、校验、解析JWT)JsonUtil.java(对象转JSON)User.java(用户实体)UserRepository.java(用户数据接口)UserService(实现UserDetailsService)UserController.java(测试权限)JwtAuthenticationFilter(验证JWT是否合法)SecurityConfig.java(SpringSecurity配置类)Result.java(封装返回信息)JwtAccessDeniedHandler.java(处理请求路径无权限)JwtAuthenticationEntryPoint.java(处理无效JWT/未认证用户)LoginFailureHandler.java(处理登录失败逻辑)LoginSuccessHandler.java(处理登录成功逻辑)
效果检验1.测试用户注册功能2.测试角色user功能3.测试角色admin功能4.测试超时处理5.测试未登录用户
前言
本文默认读者已了解相关知识,所以只介绍代码。
本文尽量使用官方提供的方法尽量简略的实现SpringSecurity+JWT,更多自定义功能需读者扩展。
项目环境:SpringBoot3、JDK21、Mysql8、SpringSecurity6、auth0 JWT
完整代码
pom.xml(引入项目所需依赖)
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!--Json-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.9</version>
</dependency>
<!--Spring Starter-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--JPA-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- SpringSecurity -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- auth0 JWT -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.18.2</version>
</dependency>
<!--开发时用(自动应用程序重启,自动加载静态资源,禁用模板缓存,全局错误页面)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
application.properties(配置数据库连接)
# Mysql数据库连接配置
spring.datasource.url=jdbc:mysql://localhost:3306/windows?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# Hibernate配置
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
123456789
JwtUtil.java(生成、校验、解析JWT)
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import jakarta.servlet.http.HttpServletRequest;
import java.util.*;
import org.springframework.util.StringUtils;
public class JwtUtil {
private static final String jwtKey = "123456";
private static final Integer expiredTimeInSeconds = 60;
public static String generateToken(String username) {
Algorithm algorithm = Algorithm.HMAC256(jwtKey);
Date now = new Date();
Date expiration = new Date(now.getTime() + expiredTimeInSeconds * 1000L);
return JWT.create()
.withClaim("username", username)
.withExpiresAt(expiration)
.withIssuedAt(now)
.sign(algorithm);
}
public static Optional<Map<String, Claim>> getClaims(String token) {
Algorithm algorithm = Algorithm.HMAC256(jwtKey);
JWTVerifier verifier = JWT.require(algorithm).build();
try {
DecodedJWT decodedJWT = verifier.verify(token);
return Optional.of(decodedJWT.getClaims());
} catch (JWTVerificationException e) {
return Optional.empty();
}
}
public static boolean validateToken(String token) {
Algorithm algorithm = Algorithm.HMAC256(jwtKey);
JWTVerifier verifier = JWT.require(algorithm).build();
try {
verifier.verify(token);
return true;
} catch (JWTVerificationException e) {
return false;
}
}
public static String getTokenFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7, bearerToken.length());
}
return null;
}
}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
JsonUtil.java(对象转JSON)
import com.google.gson.Gson;
public class JsonUtil {
public static String ObjectToJson(Object object) {
Gson gson = new Gson();
return gson.toJson(object);
}
}
12345678
User.java(用户实体)
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Data;
@Data
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private String role;
}
1234567891011121314151617
UserRepository.java(用户数据接口)
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.example.windows.windows.enity.User;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
}
1234567891011
UserService(实现UserDetailsService)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import com.example.windows.windows.enity.User;
import com.example.windows.windows.repository.UserRepository;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import com.example.windows.windows.enity.User;
import com.example.windows.windows.repository.UserRepository;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
@Service
public class UserService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username));
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(user.getRole()));
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
authorities);
}
public User register(String username,String password) {
User user = new User();
if (userRepository.findByUsername(username).isPresent()) {
return null;
}
user.setUsername(username);
user.setPassword(new BCryptPasswordEncoder().encode(password));
user.setRole("user");
return userRepository.save(user);
}
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
UserController.java(测试权限)
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
@RestController
@RequestMapping("/api/user")
public class UserController {
@GetMapping("/test")
public String test() {
return "test";
}
@GetMapping("/adminTest")
public String adminTest() {
return "adminTest";
}
@GetMapping("/userTest")
public String userTest() {
return "userTest";
}
}
123456789101112131415161718192021
JwtAuthenticationFilter(验证JWT是否合法)
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import com.auth0.jwt.interfaces.Claim;
import com.example.windows.windows.service.UserService;
import com.example.windows.windows.utils.JwtUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private UserService userService;
public JwtAuthenticationFilter(UserService userService) {
this.userService = userService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String jwt = JwtUtil.getTokenFromRequest(request);
if (StringUtils.hasText(jwt) && JwtUtil.validateToken(jwt)) {
Optional<Map<String, Claim>> claims = JwtUtil.getClaims(jwt);
Claim usernameClaim = claims.get().get("username");
String username = usernameClaim.asString();
UserDetails userDetails = userService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
filterChain.doFilter(request, response);
}
}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
SecurityConfig.java(SpringSecurity配置类)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import com.example.windows.windows.filter.JwtAuthenticationFilter;
import com.example.windows.windows.handle.JwtAccessDeniedHandler;
import com.example.windows.windows.handle.JwtAuthenticationEntryPoint;
import com.example.windows.windows.handle.LoginFailureHandler;
import com.example.windows.windows.handle.LoginSuccessHandler;
import com.example.windows.windows.service.UserService;
import lombok.RequiredArgsConstructor;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
private final UserService userService;
private final LoginFailureHandler loginFailureHandler;
private final LoginSuccessHandler loginSuccessHandler;
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration)
throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.disable())
.addFilterBefore(new JwtAuthenticationFilter(userService), UsernamePasswordAuthenticationFilter.class)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.NEVER))
.formLogin(login -> login
.loginProcessingUrl("/api/user/login")
.usernameParameter("username")
.passwordParameter("password")
.successHandler(loginSuccessHandler)
.failureHandler(loginFailureHandler))
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/api/user/userTest").hasAnyAuthority("user")
.requestMatchers("/api/user/adminTest").hasAnyAuthority("admin")
.anyRequest().authenticated())
.exceptionHandling(exception -> exception
.accessDeniedHandler(jwtAccessDeniedHandler)
.authenticationEntryPoint(jwtAuthenticationEntryPoint));
return http.build();
}
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
Result.java(封装返回信息)
package com.example.windows.windows.enity;
import java.io.Serializable;
import lombok.Data;
@Data
public class Result implements Serializable {
private int code;
private String message;
private Object data;
public Result() {}
public Result(int code, String message, Object data) {
this.code = code;
this.message = message;
this.data = data;
}
}
12345678910111213141516171819
JwtAccessDeniedHandler.java(处理请求路径无权限)
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import com.example.windows.windows.enity.Result;
import com.example.windows.windows.utils.JsonUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
ServletOutputStream outputStream = httpServletResponse.getOutputStream();
Result result = new Result(401, "权限不足", null);
outputStream.write(JsonUtil.ObjectToJson(result).getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
}
}
123456789101112131415161718192021222324252627282930
JwtAuthenticationEntryPoint.java(处理无效JWT/未认证用户)
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import com.example.windows.windows.enity.Result;
import com.example.windows.windows.utils.JsonUtil;
import com.example.windows.windows.utils.JwtUtil;
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
AuthenticationException authenticationException) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
ServletOutputStream outputStream = httpServletResponse.getOutputStream();
String jwt = JwtUtil.getTokenFromRequest(httpServletRequest);
Result result = new Result();
if (StringUtils.hasText(jwt) && JwtUtil.validateToken(jwt)){
result.setCode(401);
result.setMessage("JWT无效");
}
else{
result.setCode(401);
result.setMessage("请先登录");
}
outputStream.write(JsonUtil.ObjectToJson(result).getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
}
}
1234567891011121314151617181920212223242526272829303132333435363738394041
LoginFailureHandler.java(处理登录失败逻辑)
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import com.example.windows.windows.enity.Result;
import com.example.windows.windows.service.UserService;
import com.example.windows.windows.utils.JsonUtil;
import com.example.windows.windows.utils.JwtUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
@Component
@RequiredArgsConstructor
public class LoginFailureHandler implements AuthenticationFailureHandler {
private final UserService userService;
Logger logger = LoggerFactory.getLogger(LoginFailureHandler.class);
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
AuthenticationException authenticationException) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
ServletOutputStream outputStream = httpServletResponse.getOutputStream();
String username = httpServletRequest.getParameter("username");
String password = httpServletRequest.getParameter("password");
Result result = new Result();
if (userService.register(username,password) == null) {
result.setCode(401);
result.setMessage("用户名或密码错误");
}
else{
String jwt = JwtUtil.generateToken(username);
result.setCode(200);
result.setMessage("注册成功");
result.setData(jwt);
}
outputStream.write(JsonUtil.ObjectToJson(result).getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
}
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
LoginSuccessHandler.java(处理登录成功逻辑)
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import com.example.windows.windows.enity.Result;
import com.example.windows.windows.utils.JsonUtil;
import com.example.windows.windows.utils.JwtUtil;
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
Authentication authentication) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
ServletOutputStream outputStream = httpServletResponse.getOutputStream();
String username = httpServletRequest.getParameter("username");
String jwt = JwtUtil.generateToken(username);
Result result = new Result(200, "登录成功", jwt);
outputStream.write(JsonUtil.ObjectToJson(result).getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
}
}
12345678910111213141516171819202122232425262728293031323334
效果检验
以下均使用postman测试
1.测试用户注册功能
代码中设置了用户不存在自动注册的逻辑
user注册成功
admin注册成功
去数据库修改admin用户的权限用于测试
2.测试角色user功能
登录user用户
将返回的jwt(data中的字符串)放到Headers中的Authorization中,并添加前缀((Bearer )有个空格)。
有user权限
没有admin权限
3.测试角色admin功能
admin登录
有admin权限
没有user权限