add jwk and jwts support
This commit is contained in:
parent
90f6e8d05a
commit
d5dc891d30
18
pom.xml
18
pom.xml
@ -108,6 +108,24 @@
|
||||
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
|
||||
<version>2.6.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
189
src/main/java/com/example/demo/config/SecurityConfig.java
Normal file
189
src/main/java/com/example/demo/config/SecurityConfig.java
Normal file
@ -0,0 +1,189 @@
|
||||
package com.example.demo.config;
|
||||
|
||||
import com.example.demo.exception.CustomAuthenticationEntryPoint;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||
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.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
@Getter
|
||||
@Setter
|
||||
private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
|
||||
|
||||
@Autowired
|
||||
public SecurityConfig(CustomAuthenticationEntryPoint customAuthenticationEntryPoint) {
|
||||
this.customAuthenticationEntryPoint = customAuthenticationEntryPoint;
|
||||
}
|
||||
|
||||
|
||||
private static final String[] WHITE_LIST = {"/swagger-ui/**", "/v3/api-docs/**", "/swagger/**"};
|
||||
|
||||
// @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
|
||||
// String issuerUri;
|
||||
|
||||
@Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")
|
||||
private String jwkSetUri;
|
||||
|
||||
String jwtTokenType = "id_token";
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http.exceptionHandling(
|
||||
exception -> exception.authenticationEntryPoint(customAuthenticationEntryPoint))
|
||||
.authorizeHttpRequests(authorizeRequests ->
|
||||
authorizeRequests.requestMatchers(WHITE_LIST).permitAll().anyRequest().authenticated()
|
||||
)
|
||||
.sessionManagement(sessionManagement ->
|
||||
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
)
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.cors(AbstractHttpConfigurer::disable)
|
||||
.oauth2ResourceServer(oauth2ResourceServerConfigurer ->
|
||||
oauth2ResourceServerConfigurer.jwt(jwtConfigurer -> {
|
||||
jwtConfigurer.decoder(customJwtDecoder());
|
||||
jwtConfigurer.jwtAuthenticationConverter(jwtAuthenticationConverter());
|
||||
})
|
||||
);
|
||||
;
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JwtDecoder customJwtDecoder() {
|
||||
// 使用NimbusJwtDecoder从JWKS URL读取密钥
|
||||
return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).jwtProcessorCustomizer(jwtProcessor -> {
|
||||
jwtProcessor.setJWETypeVerifier((header, context) -> {
|
||||
if (!header.getType().equals(jwtTokenType)) {
|
||||
throw new AuthenticationServiceException("Invalid token type: " + header.getType());
|
||||
}
|
||||
});
|
||||
|
||||
jwtProcessor.setJWSTypeVerifier((joseObjectType, securityContext) -> {
|
||||
if (!joseObjectType.getType().equals(jwtTokenType)) {
|
||||
throw new AuthenticationServiceException("Invalid token type: " + joseObjectType);
|
||||
}
|
||||
});
|
||||
}).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JwtAuthenticationConverter jwtAuthenticationConverter() {
|
||||
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
|
||||
// 提取权限
|
||||
converter.setJwtGrantedAuthoritiesConverter(this::extractAuthoritiesFromJwt);
|
||||
return converter;
|
||||
}
|
||||
|
||||
private Collection<GrantedAuthority> extractAuthoritiesFromJwt(Jwt jwt) {
|
||||
// 从JWT的claims中提取角色和权限信息
|
||||
Map<String, Object> claims = jwt.getClaims();
|
||||
List<GrantedAuthority> authorities = new ArrayList<>();
|
||||
|
||||
// 提取角色
|
||||
if (claims.containsKey("roles") && claims.get("roles") instanceof List) {
|
||||
List<String> roles = (List<String>) claims.get("roles");
|
||||
authorities.addAll(roles.stream()
|
||||
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
|
||||
.toList());
|
||||
}
|
||||
|
||||
// 提取权限
|
||||
if (claims.containsKey("permissions") && claims.get("permissions") instanceof List) {
|
||||
List<String> permissions = (List<String>) claims.get("permissions");
|
||||
authorities.addAll(permissions.stream()
|
||||
.map(SimpleGrantedAuthority::new)
|
||||
.toList());
|
||||
}
|
||||
|
||||
return authorities;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// @Bean
|
||||
// public JwtDecoder customJwtDecoder() {
|
||||
// return token -> {
|
||||
// try {
|
||||
// // 使用Nimbus库解析JWT
|
||||
// JWT jwt = JWTParser.parse(token);
|
||||
// String typ = jwt.getHeader().getType().toString();
|
||||
// // 检查JWT的typ是否为id_token
|
||||
// if (!typ.equals(jwtTokenType)) {
|
||||
// throw new AuthenticationServiceException("Invalid token type: " + typ);
|
||||
// }
|
||||
//
|
||||
// JWTClaimsSet claims = jwt.getJWTClaimsSet();
|
||||
//
|
||||
//// Date exp = claims.getExpirationTime();
|
||||
//// Date now = new Date();
|
||||
// // 检测是否过期
|
||||
// if (claims.getExpirationTime().before(new Date())) {
|
||||
// throw new AuthenticationServiceException("Token has expired");
|
||||
// }
|
||||
//
|
||||
// // 将JWT转换为Spring Security的Jwt对象
|
||||
// return new Jwt(token, jwt.getJWTClaimsSet().getIssueTime().toInstant(), jwt.getJWTClaimsSet().getExpirationTime().toInstant(), jwt.getHeader().toJSONObject(), jwt.getJWTClaimsSet().toJSONObject());
|
||||
// } catch (AuthenticationServiceException | ParseException e) {
|
||||
// // 401 了
|
||||
// throw new AuthenticationServiceException(e.getMessage());
|
||||
// }
|
||||
// };
|
||||
//
|
||||
//
|
||||
// }
|
||||
//
|
||||
// @Bean
|
||||
// public JwtAuthenticationConverter jwtAuthenticationConverter() {
|
||||
// JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
|
||||
// // 提取权限
|
||||
// converter.setJwtGrantedAuthoritiesConverter(this::extractAuthoritiesFromJwt);
|
||||
//
|
||||
// return converter;
|
||||
// }
|
||||
//
|
||||
//
|
||||
// private Collection<GrantedAuthority> extractAuthoritiesFromJwt(Jwt jwt) {
|
||||
// // 从JWT的claims中提取角色和权限信息
|
||||
// Map<String, Object> claims = jwt.getClaims();
|
||||
// List<GrantedAuthority> authorities = new ArrayList<>();
|
||||
//
|
||||
// // 提取角色
|
||||
// if (claims.containsKey("roles") && claims.get("roles") instanceof List) {
|
||||
// List<String> roles = (List<String>) claims.get("roles");
|
||||
// authorities.addAll(roles.stream()
|
||||
// .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
|
||||
// .toList());
|
||||
// }
|
||||
//
|
||||
// // 提取权限
|
||||
// if (claims.containsKey("permissions") && claims.get("permissions") instanceof List) {
|
||||
// List<String> permissions = (List<String>) claims.get("permissions");
|
||||
// authorities.addAll(permissions.stream()
|
||||
// .map(SimpleGrantedAuthority::new)
|
||||
// .toList());
|
||||
// }
|
||||
//
|
||||
// return authorities;
|
||||
// }
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package com.example.demo.exception;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
|
||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||
response.setHeader(HttpHeaders.WWW_AUTHENTICATE, "Bearer error=\"invalid_token\", error_description=\"" + authException.getMessage() + "\"");
|
||||
response.getWriter().write("Unauthorized: " + authException.getMessage());
|
||||
}
|
||||
}
|
@ -34,7 +34,7 @@ public class GlobalHttpExceptionHandlerAdvice {
|
||||
}
|
||||
|
||||
// get type of e
|
||||
logger.error("{}, Error: {}", e.getClass().getName(), e.getMessage());
|
||||
logger.error("Server Error, {}, Error: {}", e.getClass().getName(), e.getMessage());
|
||||
|
||||
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
SPRING_DATASOURCE_URL=jdbc:mysql://localhost:3306/demo?serverTimezone=UTC
|
||||
SPRING_DATASOURCE_USERNAME=root
|
||||
SPRING_DATASOURCE_PASSWORD=Qwerty123...
|
||||
JWK_URL=https://auth.leaflow.cn/.well-known/jwks
|
||||
#JWK_ISSUER_URL=https://auth.leaflow.cn
|
@ -1,25 +0,0 @@
|
||||
spring.application.name=demo
|
||||
|
||||
spring.jpa.hibernate.ddl-auto=none
|
||||
|
||||
spring.datasource.url=${SPRING_DATASOURCE_URL}
|
||||
spring.datasource.username=${SPRING_DATASOURCE_USERNAME}
|
||||
spring.datasource.password=${SPRING_DATASOURCE_PASSWORD}
|
||||
|
||||
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||
spring.jpa.properties.hibernate.format_sql=true
|
||||
spring.jpa.show-sql=true
|
||||
|
||||
#spring.shell.script.enabled=true
|
||||
#spring.shell.interactive.enabled=true
|
||||
|
||||
server.port=8088
|
||||
|
||||
# 阻止启动时执行 flyway
|
||||
spring.flyway.baseline-on-migrate=false
|
||||
spring.flyway.locations=classpath:migrations
|
||||
|
||||
springdoc.api-docs.enabled=true
|
||||
springdoc.api-docs.path=/v3/api-docs
|
||||
springdoc.swagger-ui.enabled=true
|
||||
springdoc.swagger-ui.path=/swagger
|
41
src/main/resources/application.yml
Normal file
41
src/main/resources/application.yml
Normal file
@ -0,0 +1,41 @@
|
||||
logging:
|
||||
level:
|
||||
org.springframework.web.client.RestTemplate: DEBUG
|
||||
spring:
|
||||
application:
|
||||
name: demo
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: none
|
||||
properties:
|
||||
hibernate:
|
||||
format_sql: true
|
||||
datasource:
|
||||
url: ${SPRING_DATASOURCE_URL}
|
||||
username: ${SPRING_DATASOURCE_USERNAME}
|
||||
password: ${SPRING_DATASOURCE_PASSWORD}
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
flyway:
|
||||
baseline-on-migrate: false # 阻止启动时执行 flyway
|
||||
locations: classpath:migrations
|
||||
doc:
|
||||
api-docs:
|
||||
enabled: true
|
||||
path: /v3/api-docs
|
||||
swagger-ui:
|
||||
enabled: true
|
||||
path: /swagger
|
||||
shell:
|
||||
interactive:
|
||||
enabled: false
|
||||
security:
|
||||
oauth2:
|
||||
resourceserver:
|
||||
jwt:
|
||||
jwk-set-uri: ${JWK_URL}
|
||||
# issuer-uri: ${JWK_ISSUER_URL}
|
||||
jackson:
|
||||
time-zone: PRC
|
||||
server:
|
||||
port: 8088
|
||||
|
Loading…
Reference in New Issue
Block a user