add JWT security config
This commit is contained in:
parent
af0213bb4e
commit
fbaee8aa4c
@ -1,11 +1,13 @@
|
|||||||
package com.example.springdemo.security;
|
package com.example.springdemo.security.config;
|
||||||
|
|
||||||
import com.example.springdemo.security.jwt.JwtAuthenticationFilter;
|
import com.example.springdemo.security.jwt.JwtAuthenticationFilter;
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.config.Customizer;
|
import org.springframework.security.config.Customizer;
|
||||||
|
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||||
import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
|
import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
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.builders.HttpSecurity;
|
||||||
@ -14,32 +16,41 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHt
|
|||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
|
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
|
||||||
import org.springframework.security.web.authentication.AuthenticationFilter;
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity // Enable Spring Security
|
@EnableWebSecurity // Enable Spring Security
|
||||||
@EnableGlobalAuthentication // Enable Spring Security's global authentication configuration
|
@EnableGlobalAuthentication // Enable Spring Security's global authentication configuration
|
||||||
@EnableMethodSecurity(prePostEnabled = true) // Enable Spring Security's method security
|
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = false) // Enable Spring Security's method security
|
||||||
public class SecurityFilterChainConfig {
|
public class SecurityFilterChainConfig implements InitializingBean {
|
||||||
@Resource
|
|
||||||
AuthenticationFilter authenticationFilter;
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain SecurityFilterChain(@NotNull HttpSecurity http) throws Exception {
|
public SecurityFilterChain SecurityFilterChain(@NotNull HttpSecurity http,
|
||||||
var ignoreUrls = new String[]{"/login", "/logout", "/error"};
|
JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception {
|
||||||
|
var ignoreUrls = new String[]{"/auth/**"};
|
||||||
var authedUrls = new String[]{"/users/*/**"};
|
var authedUrls = new String[]{"/users/*/**"};
|
||||||
http
|
http
|
||||||
.authorizeHttpRequests(
|
.authorizeHttpRequests(
|
||||||
(request) -> request
|
(request) -> request
|
||||||
.requestMatchers(authedUrls).authenticated() // authenticate all requests to authedUrls
|
.requestMatchers(authedUrls).authenticated() // authenticate all requests to authedUrls
|
||||||
.requestMatchers(ignoreUrls).permitAll() // permit all requests to ignoreUrls
|
.requestMatchers(ignoreUrls).permitAll() // permit all requests to ignoreUrls
|
||||||
|
.anyRequest().authenticated() // authenticate all other requests
|
||||||
)
|
)
|
||||||
.httpBasic(Customizer.withDefaults())
|
.httpBasic(Customizer.withDefaults())
|
||||||
.csrf(AbstractHttpConfigurer::disable)
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
.sessionManagement(a -> a.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
.formLogin(AbstractHttpConfigurer::disable)
|
.formLogin(AbstractHttpConfigurer::disable)
|
||||||
.logout(AbstractHttpConfigurer::disable)
|
.logout(AbstractHttpConfigurer::disable)
|
||||||
.addFilterBefore(authenticationFilter, AnonymousAuthenticationFilter.class); // jwt filter;
|
.addFilterBefore(jwtAuthenticationFilter, AnonymousAuthenticationFilter.class); // jwt filter;
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AuthenticationManager authenticationManager
|
||||||
|
(@NotNull AuthenticationConfiguration configuration) throws Exception {
|
||||||
|
return configuration.getAuthenticationManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterPropertiesSet() throws Exception {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package com.example.springdemo.security.dto;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
@Getter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class JwtAuthResponse {
|
||||||
|
private String accessToken;
|
||||||
|
private String tokenType;
|
||||||
|
|
||||||
|
public JwtAuthResponse(String accessToken) {
|
||||||
|
this.accessToken = accessToken;
|
||||||
|
this.tokenType = "Bearer ";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
|||||||
|
package com.example.springdemo.security.dto;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class LoginDto {
|
||||||
|
private String username;
|
||||||
|
private String password;
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
package com.example.springdemo.security.events;
|
||||||
|
|
||||||
|
import com.example.springdemo.security.utils.JwtTokenProvider;
|
||||||
|
import com.example.springdemo.utils.verificationAnnotation.Result;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class AuthSuccess {
|
||||||
|
@Resource
|
||||||
|
private JwtTokenProvider JwtTokenProvider;
|
||||||
|
|
||||||
|
public void onAuthSuccess(HttpServletRequest request,
|
||||||
|
@NotNull HttpServletResponse response,
|
||||||
|
@NotNull Authentication authentication) throws IOException, ServletException {
|
||||||
|
Result<Object> result = Result.of(200, "Login success");
|
||||||
|
|
||||||
|
log.info("User {} login success", authentication.getName());
|
||||||
|
|
||||||
|
String token = JwtTokenProvider.generateToken(authentication);
|
||||||
|
|
||||||
|
result.setData(token);
|
||||||
|
|
||||||
|
String responseJson = result.toString();
|
||||||
|
|
||||||
|
response.setCharacterEncoding("UTF-8");
|
||||||
|
response.setContentType("application/json; charset=utf-8");
|
||||||
|
response.getWriter().println(responseJson);
|
||||||
|
response.getWriter().flush();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -1,22 +0,0 @@
|
|||||||
package com.example.springdemo.security.events;
|
|
||||||
|
|
||||||
import com.example.springdemo.entities.Users;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.springframework.context.PayloadApplicationEvent;
|
|
||||||
import org.springframework.core.ResolvableType;
|
|
||||||
|
|
||||||
public class LoginSuccess extends PayloadApplicationEvent<Users> {
|
|
||||||
public LoginSuccess(Object source, Users payload) {
|
|
||||||
super(source, payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ResolvableType getResolvableType() {
|
|
||||||
return ResolvableType.forRawClass(LoginSuccess.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Users getPayload() {
|
|
||||||
return super.getPayload();
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,21 @@
|
|||||||
|
package com.example.springdemo.security.jwt;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@Component("JwtAuthenticationEntryPoint")
|
||||||
|
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||||
|
@Override
|
||||||
|
public void commence(HttpServletRequest request,
|
||||||
|
@NotNull HttpServletResponse response,
|
||||||
|
@NotNull AuthenticationException authException) throws IOException, ServletException {
|
||||||
|
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
|
||||||
|
}
|
||||||
|
}
|
@ -1,23 +1,66 @@
|
|||||||
package com.example.springdemo.security.jwt;
|
package com.example.springdemo.security.jwt;
|
||||||
|
|
||||||
|
import com.example.springdemo.security.utils.JwtTokenProvider;
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
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.security.web.authentication.WebAuthenticationDetailsSource;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@Slf4j
|
@Component
|
||||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||||
|
private final JwtTokenProvider jwtTokenProvider;
|
||||||
|
private final UserDetailsService userDetailsService;
|
||||||
|
|
||||||
|
public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider, UserDetailsService userDetailsService) {
|
||||||
|
this.jwtTokenProvider = jwtTokenProvider;
|
||||||
|
this.userDetailsService = userDetailsService;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doFilterInternal(@NotNull HttpServletRequest request,
|
protected void doFilterInternal(@NotNull HttpServletRequest request,
|
||||||
@NotNull HttpServletResponse response,
|
@NotNull HttpServletResponse response,
|
||||||
@NotNull FilterChain filterChain)
|
@NotNull FilterChain filterChain) throws ServletException, IOException {
|
||||||
throws ServletException, IOException {
|
// 从 request 获取 JWT token
|
||||||
|
String token = getTokenFromRequest(request);
|
||||||
|
// 校验 token
|
||||||
|
if (StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) {
|
||||||
|
// 从 token 获取 username
|
||||||
|
String username = jwtTokenProvider.getUsername(token);
|
||||||
|
// 加载与令 token 关联的用户
|
||||||
|
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||||
|
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
|
||||||
|
userDetails,
|
||||||
|
null,
|
||||||
|
userDetails.getAuthorities()
|
||||||
|
);
|
||||||
|
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||||
|
// 获取安全上下文
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable String getTokenFromRequest(@NotNull HttpServletRequest request) {
|
||||||
|
|
||||||
|
String bearerToken = request.getHeader("Authorization");
|
||||||
|
|
||||||
|
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
|
||||||
|
return bearerToken.substring(7);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,30 +0,0 @@
|
|||||||
package com.example.springdemo.security.jwt.token;
|
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@EqualsAndHashCode(callSuper = false)
|
|
||||||
public class RequestAuthToken extends UsernamePasswordAuthenticationToken {
|
|
||||||
|
|
||||||
private String userId;
|
|
||||||
|
|
||||||
private String secret;
|
|
||||||
|
|
||||||
private TokenType tokenType;
|
|
||||||
|
|
||||||
public RequestAuthToken(Object principal, Object credentials, TokenType tokenType) {
|
|
||||||
this(principal, credentials, null, null, tokenType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RequestAuthToken(Object principal, Object credentials, final String userId, String secret) {
|
|
||||||
this(principal, credentials, userId, secret, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RequestAuthToken(Object principal, Object credentials, final String userId, String secret, TokenType tokenType) {
|
|
||||||
super(principal, credentials);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package com.example.springdemo.security.jwt.token;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public enum TokenType {
|
|
||||||
|
|
||||||
STRING(1, "String"),//string
|
|
||||||
INFO(3, "INFO");//json
|
|
||||||
|
|
||||||
private final Integer id;
|
|
||||||
private final String name;
|
|
||||||
|
|
||||||
TokenType(Integer id, String name) {
|
|
||||||
this.id = id;
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,88 @@
|
|||||||
|
package com.example.springdemo.security.utils;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.*;
|
||||||
|
import io.jsonwebtoken.io.Decoders;
|
||||||
|
import io.jsonwebtoken.security.Keys;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jetbrains.annotations.Contract;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.security.Key;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class JwtTokenProvider {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(JwtTokenProvider.class);
|
||||||
|
|
||||||
|
private String jwtSecret;
|
||||||
|
|
||||||
|
@Value("${app.jwt-expiration-milliseconds}")
|
||||||
|
private long jwtExpirationDate;
|
||||||
|
|
||||||
|
private void setJwtSecret() {
|
||||||
|
this.jwtSecret = Keys.secretKeyFor(SignatureAlgorithm.HS512).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成 JWT token
|
||||||
|
public String generateToken(@NotNull Authentication authentication) {
|
||||||
|
// 用户名
|
||||||
|
String username = authentication.getName();
|
||||||
|
|
||||||
|
// 当前时间
|
||||||
|
Date currentDate = new Date();
|
||||||
|
|
||||||
|
// 过期时间
|
||||||
|
Date expireDate = new Date(currentDate.getTime() + jwtExpirationDate);
|
||||||
|
|
||||||
|
return Jwts.builder()
|
||||||
|
.setSubject(username)
|
||||||
|
.setIssuedAt(new Date())
|
||||||
|
.setExpiration(expireDate)
|
||||||
|
.signWith(key())
|
||||||
|
.compact();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Contract(" -> new")
|
||||||
|
private @NotNull Key key() {
|
||||||
|
if (jwtSecret == null) {
|
||||||
|
this.setJwtSecret();
|
||||||
|
}
|
||||||
|
return Keys.hmacShaKeyFor(jwtSecret.getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从 Jwt token 获取用户名
|
||||||
|
public String getUsername(String token) {
|
||||||
|
Claims claims = Jwts.parserBuilder()
|
||||||
|
.setSigningKey(key())
|
||||||
|
.build()
|
||||||
|
.parseClaimsJws(token)
|
||||||
|
.getBody();
|
||||||
|
return claims.getSubject();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证 Jwt token
|
||||||
|
public boolean validateToken(String token) {
|
||||||
|
try {
|
||||||
|
Jwts.parserBuilder()
|
||||||
|
.setSigningKey(key())
|
||||||
|
.build()
|
||||||
|
.parse(token);
|
||||||
|
return true;
|
||||||
|
} catch (MalformedJwtException e) {
|
||||||
|
logger.error("Invalid JWT token: {}", e.getMessage());
|
||||||
|
} catch (ExpiredJwtException e) {
|
||||||
|
logger.error("JWT token is expired: {}", e.getMessage());
|
||||||
|
} catch (UnsupportedJwtException e) {
|
||||||
|
logger.error("JWT token is unsupported: {}", e.getMessage());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
logger.error("JWT claims string is empty: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user