feat: implement JWT authentication with TokenClient, TokenAuthenticationFilter, and SecurityConfig
This commit is contained in:
@@ -0,0 +1,70 @@
|
|||||||
|
package com.onixbyte.deltaforceguide.client;
|
||||||
|
|
||||||
|
import com.auth0.jwt.JWT;
|
||||||
|
import com.auth0.jwt.JWTVerifier;
|
||||||
|
import com.auth0.jwt.algorithms.Algorithm;
|
||||||
|
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||||
|
import com.onixbyte.deltaforceguide.domain.entity.User;
|
||||||
|
import com.onixbyte.deltaforceguide.properties.TokenProperties;
|
||||||
|
import com.onixbyte.deltaforceguide.utils.DateTimeUtil;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class TokenClient {
|
||||||
|
|
||||||
|
private final Algorithm algorithm;
|
||||||
|
private final TokenProperties tokenProperties;
|
||||||
|
private final JWTVerifier verifier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new TokenClient with the necessary algorithm and token properties.
|
||||||
|
*
|
||||||
|
* @param algorithm the signing algorithm used to secure the JWT
|
||||||
|
* @param tokenProperties the configuration properties for the token, such as issuer and
|
||||||
|
* validity period
|
||||||
|
*/
|
||||||
|
@Autowired
|
||||||
|
public TokenClient(
|
||||||
|
Algorithm algorithm,
|
||||||
|
TokenProperties tokenProperties,
|
||||||
|
JWTVerifier verifier
|
||||||
|
) {
|
||||||
|
this.algorithm = algorithm;
|
||||||
|
this.tokenProperties = tokenProperties;
|
||||||
|
this.verifier = verifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a JSON Web Token to the current user.
|
||||||
|
*
|
||||||
|
* @param user the current user for whom the token is being generated
|
||||||
|
* @return a JWT string
|
||||||
|
*/
|
||||||
|
public String generateToken(User user) {
|
||||||
|
var issuedAt = LocalDateTime.now();
|
||||||
|
var expiresAt = issuedAt.plus(tokenProperties.validTime());
|
||||||
|
|
||||||
|
return JWT.create()
|
||||||
|
.withSubject(user.getUsername())
|
||||||
|
.withIssuer(tokenProperties.issuer())
|
||||||
|
.withIssuedAt(DateTimeUtil.asInstant(issuedAt))
|
||||||
|
.withExpiresAt(DateTimeUtil.asInstant(expiresAt))
|
||||||
|
.sign(algorithm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify and decode token.
|
||||||
|
*
|
||||||
|
* @param token a JWT token
|
||||||
|
* @return information included in the given token
|
||||||
|
* @throws com.auth0.jwt.exceptions.JWTVerificationException if the token is invalid, such as
|
||||||
|
* expired, or not signed by
|
||||||
|
* specific server
|
||||||
|
*/
|
||||||
|
public DecodedJWT verifyToken(String token) {
|
||||||
|
return verifier.verify(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,46 +2,37 @@ package com.onixbyte.deltaforceguide.config;
|
|||||||
|
|
||||||
import com.onixbyte.deltaforceguide.properties.CorsProperties;
|
import com.onixbyte.deltaforceguide.properties.CorsProperties;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.List;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableConfigurationProperties({CorsProperties.class})
|
@EnableConfigurationProperties({CorsProperties.class})
|
||||||
public class CorsConfig implements WebMvcConfigurer {
|
public class CorsConfig {
|
||||||
|
|
||||||
private final CorsProperties properties;
|
@Bean
|
||||||
|
public CorsConfigurationSource corsConfigurationSource(
|
||||||
public CorsConfig(CorsProperties properties) {
|
CorsProperties properties
|
||||||
this.properties = properties;
|
) {
|
||||||
}
|
var corsConfiguration = new CorsConfiguration();
|
||||||
|
corsConfiguration.setAllowCredentials(properties.allowCredentials());
|
||||||
@Override
|
corsConfiguration.setAllowedOrigins(List.of(properties.allowedOrigins()));
|
||||||
public void addCorsMappings(CorsRegistry registry) {
|
corsConfiguration.setAllowedHeaders(List.of(properties.allowedHeaders()));
|
||||||
registry.addMapping("/**")
|
corsConfiguration.setAllowedMethods(Stream.of(properties.allowedMethods())
|
||||||
.allowedOrigins(toSafeArray(properties.allowedOrigins()))
|
|
||||||
.allowedHeaders(toSafeArray(properties.allowedHeaders()))
|
|
||||||
.allowedMethods(toHttpMethodNames(properties.allowedMethods()))
|
|
||||||
.allowCredentials(properties.allowCredentials())
|
|
||||||
.maxAge(properties.maxAge().toSeconds())
|
|
||||||
.exposedHeaders(toSafeArray(properties.exposedHeaders()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String[] toSafeArray(String[] values) {
|
|
||||||
return values == null ? new String[0] : values;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String[] toHttpMethodNames(HttpMethod[] methods) {
|
|
||||||
return Optional.ofNullable(methods)
|
|
||||||
.stream()
|
|
||||||
.flatMap(Stream::of)
|
|
||||||
.map(HttpMethod::name)
|
.map(HttpMethod::name)
|
||||||
.toList()
|
.toList());
|
||||||
.toArray(String[]::new);
|
corsConfiguration.setMaxAge(properties.maxAge());
|
||||||
}
|
corsConfiguration.setAllowPrivateNetwork(properties.allowPrivateNetwork());
|
||||||
|
corsConfiguration.setExposedHeaders(List.of(properties.exposedHeaders()));
|
||||||
|
|
||||||
|
var corsConfigurationSource = new UrlBasedCorsConfigurationSource();
|
||||||
|
corsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
|
||||||
|
return corsConfigurationSource;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package com.onixbyte.deltaforceguide.config;
|
||||||
|
|
||||||
|
import com.auth0.jwt.JWT;
|
||||||
|
import com.auth0.jwt.JWTVerifier;
|
||||||
|
import com.auth0.jwt.algorithms.Algorithm;
|
||||||
|
import com.onixbyte.deltaforceguide.filter.TokenAuthenticationFilter;
|
||||||
|
import com.onixbyte.deltaforceguide.properties.TokenProperties;
|
||||||
|
import com.onixbyte.deltaforceguide.security.provider.UsernamePasswordAuthenticationProvider;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.authentication.ProviderManager;
|
||||||
|
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.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.access.ExceptionTranslationFilter;
|
||||||
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableMethodSecurity
|
||||||
|
@EnableConfigurationProperties({TokenProperties.class})
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain securityFilterChain(
|
||||||
|
HttpSecurity httpSecurity,
|
||||||
|
CorsConfigurationSource corsConfigurationSource,
|
||||||
|
TokenAuthenticationFilter tokenAuthenticationFilter
|
||||||
|
) throws Exception {
|
||||||
|
return httpSecurity
|
||||||
|
.cors((cors) -> cors
|
||||||
|
.configurationSource(corsConfigurationSource))
|
||||||
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
|
.sessionManagement((customiser) -> customiser
|
||||||
|
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
|
.authorizeHttpRequests((customiser) -> customiser
|
||||||
|
.requestMatchers("/error", "/error/**").permitAll()
|
||||||
|
.requestMatchers("/captcha", "/captcha/**").permitAll()
|
||||||
|
.requestMatchers("/auth/**").permitAll()
|
||||||
|
.requestMatchers("/auth/logout").authenticated()
|
||||||
|
.requestMatchers(
|
||||||
|
"/swagger-ui.html",
|
||||||
|
"/swagger-ui",
|
||||||
|
"/swagger-ui/**",
|
||||||
|
"/v3/api-docs",
|
||||||
|
"/v3/api-docs.yaml",
|
||||||
|
"/v3/api-docs/swagger-config"
|
||||||
|
).permitAll()
|
||||||
|
.requestMatchers(HttpMethod.GET,
|
||||||
|
"/firearms", "/firearms/*",
|
||||||
|
"/modifications", "/modifications/*"
|
||||||
|
).permitAll()
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
)
|
||||||
|
.addFilterAfter(tokenAuthenticationFilter, ExceptionTranslationFilter.class)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AuthenticationManager authenticationManager(
|
||||||
|
UsernamePasswordAuthenticationProvider usernamePasswordAuthenticationProvider
|
||||||
|
) {
|
||||||
|
return new ProviderManager(
|
||||||
|
usernamePasswordAuthenticationProvider
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Algorithm algorithm(TokenProperties properties) {
|
||||||
|
return Algorithm.HMAC256(properties.secret());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public JWTVerifier verifier(Algorithm algorithm, TokenProperties tokenProperties) {
|
||||||
|
return JWT.require(algorithm)
|
||||||
|
.withIssuer(tokenProperties.issuer())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.onixbyte.deltaforceguide.exeption;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
|
||||||
|
public class BizException extends RuntimeException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The HTTP status code associated with this business exception.
|
||||||
|
* <p>
|
||||||
|
* This status code indicates the appropriate HTTP response status that should be returned to
|
||||||
|
* clients when this exception occurs. It enables consistent error handling across
|
||||||
|
* REST API endpoints.
|
||||||
|
*/
|
||||||
|
private final HttpStatus status;
|
||||||
|
|
||||||
|
public BizException(String message) {
|
||||||
|
super(message);
|
||||||
|
this.status = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BizException(HttpStatus status, String message) {
|
||||||
|
super(message);
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the HTTP status code associated with this business exception.
|
||||||
|
*
|
||||||
|
* @return the HTTP status code that should be used in the error response
|
||||||
|
*/
|
||||||
|
public HttpStatus getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package com.onixbyte.deltaforceguide.filter;
|
||||||
|
|
||||||
|
import com.auth0.jwt.exceptions.JWTVerificationException;
|
||||||
|
import com.onixbyte.deltaforceguide.client.TokenClient;
|
||||||
|
import com.onixbyte.deltaforceguide.exeption.BizException;
|
||||||
|
import com.onixbyte.deltaforceguide.manager.UserManager;
|
||||||
|
import com.onixbyte.deltaforceguide.security.authentication.UsernamePasswordAuthentication;
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.Cookie;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.jspecify.annotations.NonNull;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
import org.springframework.web.servlet.HandlerExceptionResolver;
|
||||||
|
import org.springframework.web.util.WebUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class TokenAuthenticationFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
private final static Logger log = LoggerFactory.getLogger(TokenAuthenticationFilter.class);
|
||||||
|
|
||||||
|
private final UserManager userManager;
|
||||||
|
private final TokenClient tokenClient;
|
||||||
|
private final HandlerExceptionResolver handlerExceptionResolver;
|
||||||
|
|
||||||
|
public TokenAuthenticationFilter(
|
||||||
|
UserManager userManager,
|
||||||
|
TokenClient tokenClient,
|
||||||
|
HandlerExceptionResolver handlerExceptionResolver
|
||||||
|
) {
|
||||||
|
this.userManager = userManager;
|
||||||
|
this.tokenClient = tokenClient;
|
||||||
|
this.handlerExceptionResolver = handlerExceptionResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(
|
||||||
|
@NonNull HttpServletRequest request,
|
||||||
|
@NonNull HttpServletResponse response,
|
||||||
|
@NonNull FilterChain filterChain
|
||||||
|
) throws ServletException, IOException {
|
||||||
|
var token = Optional.ofNullable(WebUtils.getCookie(request, "AccessToken"))
|
||||||
|
.map(Cookie::getValue)
|
||||||
|
.orElse(null);
|
||||||
|
if (Objects.isNull(token) || token.isBlank()) {
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var decodedToken = tokenClient.verifyToken(token);
|
||||||
|
var username = decodedToken.getSubject();
|
||||||
|
|
||||||
|
var userWrapper = userManager.findByUsername(username);
|
||||||
|
if (userWrapper.isEmpty()) {
|
||||||
|
throw new BizException(HttpStatus.UNAUTHORIZED, "登录已过期,请重新登录");
|
||||||
|
}
|
||||||
|
|
||||||
|
var user = userWrapper.get();
|
||||||
|
var authentication = UsernamePasswordAuthentication.authenticated(user);
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
} catch (JWTVerificationException e) {
|
||||||
|
log.error("JWT verification failed.", e);
|
||||||
|
handlerExceptionResolver.resolveException(request, response, null,
|
||||||
|
new BizException(HttpStatus.UNAUTHORIZED, "登录已过期,请重新登录"));
|
||||||
|
} catch (BizException e) {
|
||||||
|
handlerExceptionResolver.resolveException(request, response, null, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.onixbyte.deltaforceguide.properties;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
@ConfigurationProperties(prefix = "app.jwt")
|
||||||
|
public record TokenProperties(
|
||||||
|
String issuer,
|
||||||
|
String secret,
|
||||||
|
Duration validTime
|
||||||
|
) {
|
||||||
|
}
|
||||||
+76
@@ -0,0 +1,76 @@
|
|||||||
|
package com.onixbyte.deltaforceguide.security.authentication;
|
||||||
|
|
||||||
|
import com.onixbyte.deltaforceguide.domain.entity.User;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.CredentialsContainer;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class UsernamePasswordAuthentication implements Authentication, CredentialsContainer {
|
||||||
|
private final String username;
|
||||||
|
private String password;
|
||||||
|
private boolean authenticated;
|
||||||
|
private User user;
|
||||||
|
|
||||||
|
private UsernamePasswordAuthentication(String username, String password, boolean authenticated, User user) {
|
||||||
|
this.username = username;
|
||||||
|
this.password = password;
|
||||||
|
this.authenticated = authenticated;
|
||||||
|
this.user = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UsernamePasswordAuthentication unauthenticated(String username, String password) {
|
||||||
|
return new UsernamePasswordAuthentication(username, password, false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UsernamePasswordAuthentication authenticated(User user) {
|
||||||
|
return new UsernamePasswordAuthentication(user.getUsername(), null, true, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCredentials() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public User getDetails() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPrincipal() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAuthenticated() {
|
||||||
|
return authenticated;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAuthenticated(boolean authenticated) throws IllegalArgumentException {
|
||||||
|
this.authenticated = authenticated;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eraseCredentials() {
|
||||||
|
this.password = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDetails(User user) {
|
||||||
|
this.user = user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+83
@@ -0,0 +1,83 @@
|
|||||||
|
package com.onixbyte.deltaforceguide.security.provider;
|
||||||
|
|
||||||
|
import com.onixbyte.deltaforceguide.domain.entity.UserCredential;
|
||||||
|
import com.onixbyte.deltaforceguide.exeption.BizException;
|
||||||
|
import com.onixbyte.deltaforceguide.manager.UserManager;
|
||||||
|
import com.onixbyte.deltaforceguide.repository.UserCredentialRepository;
|
||||||
|
import com.onixbyte.deltaforceguide.security.authentication.UsernamePasswordAuthentication;
|
||||||
|
import com.onixbyte.deltaforceguide.shared.CredentialProvider;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.domain.Example;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class UsernamePasswordAuthenticationProvider implements AuthenticationProvider {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(UsernamePasswordAuthenticationProvider.class);
|
||||||
|
private final UserManager userManager;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
private final UserCredentialRepository userCredentialRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public UsernamePasswordAuthenticationProvider(
|
||||||
|
UserManager userManager,
|
||||||
|
PasswordEncoder passwordEncoder,
|
||||||
|
UserCredentialRepository userCredentialRepository
|
||||||
|
) {
|
||||||
|
this.userManager = userManager;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
this.userCredentialRepository = userCredentialRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||||
|
if (!(authentication instanceof UsernamePasswordAuthentication usernamePasswordAuthentication)) {
|
||||||
|
throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR, "用户认证失败,请稍后再试。");
|
||||||
|
}
|
||||||
|
|
||||||
|
// get userContainer from database
|
||||||
|
var userContainer = userManager.findByUsername(usernamePasswordAuthentication.getPrincipal());
|
||||||
|
if (userContainer.isEmpty()) {
|
||||||
|
log.error("User {} is trying to authenticate but no userContainer found.", usernamePasswordAuthentication.getPrincipal());
|
||||||
|
throw new BizException(HttpStatus.UNAUTHORIZED, "用户名或密码错误。");
|
||||||
|
}
|
||||||
|
|
||||||
|
var user = userContainer.get();
|
||||||
|
|
||||||
|
var userCredentialExample = new UserCredential();
|
||||||
|
userCredentialExample.setUserId(user.getId());
|
||||||
|
userCredentialExample.setProvider(CredentialProvider.LOCAL);
|
||||||
|
|
||||||
|
// get userContainer credentials from database
|
||||||
|
var userCredentials = userCredentialRepository.findOne(Example.of(userCredentialExample))
|
||||||
|
.orElseThrow(() -> new BizException(HttpStatus.UNAUTHORIZED, "您还没有配置密码,请联系管理员处理。"));
|
||||||
|
|
||||||
|
// validate password
|
||||||
|
if (!passwordEncoder.matches(usernamePasswordAuthentication.getCredentials(), userCredentials.getCredential())) {
|
||||||
|
log.error("User {} is trying to authenticate but password is incorrect.", usernamePasswordAuthentication.getPrincipal());
|
||||||
|
throw new BizException(HttpStatus.UNAUTHORIZED, "用户名或密码错误。");
|
||||||
|
}
|
||||||
|
|
||||||
|
// erase credentials
|
||||||
|
usernamePasswordAuthentication.eraseCredentials();
|
||||||
|
|
||||||
|
// set values
|
||||||
|
usernamePasswordAuthentication.setAuthenticated(true);
|
||||||
|
usernamePasswordAuthentication.setDetails(user);
|
||||||
|
|
||||||
|
return usernamePasswordAuthentication;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(Class<?> authentication) {
|
||||||
|
return UsernamePasswordAuthentication.class.isAssignableFrom(authentication);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.onixbyte.deltaforceguide.shared;
|
||||||
|
|
||||||
|
public class CredentialProvider {
|
||||||
|
|
||||||
|
public static final String LOCAL = "LOCAL";
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.onixbyte.deltaforceguide.utils;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
|
||||||
|
public class DateTimeUtil {
|
||||||
|
|
||||||
|
public static Instant asInstant(LocalDateTime ldt) {
|
||||||
|
return ldt.atZone(ZoneId.systemDefault())
|
||||||
|
.toInstant();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,4 +43,3 @@ logging:
|
|||||||
level:
|
level:
|
||||||
org.hibernate:
|
org.hibernate:
|
||||||
orm.connections.pooling: off
|
orm.connections.pooling: off
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user