提前,我为这个looong问题道歉!事实上,问题并没有那么长,但是我发布了很多我的代码片段,因为我真的不知道什么与解决我的问题相关或不相关......
我一直在尝试使用以下内容制作一个简单的poc:-Angular 8前端-用于身份验证的Keycloak服务器-Spring云后端架构:-使用Spring Cloud Security保护的Spring Cloud Gateway-Spring CloudNetflixEureka服务器-Spring Cloud配置服务器-使用Spring Security OAuth2保护的一些Springboot微服务不起作用:我无法设法让我的Angular应用程序访问并从我受保护的后端uris获取任何数据。我收到了401 Un授权
响应。如果我断点到MS Spring secu过滤器,我只是在HttpServletRequest请求
中没有任何令牌
正在工作: - 通过 Angular 使用前端到 Angular 进行身份验证 - Angular 可以从后端未受保护的 uris 获取数据 - Postman 在受保护的后端 uris 上进行测试,OAuth2 授权类型设置为资源所有者密码凭据
我遵循了许多教程,但是我在这个教程中获得了更好的结果:https://blog.jdriven.com/2019/11/spring-cloud-gateway-with-openid-connect-and-token-relay/
以下是我认为相关的代码段:
棱角的
我使用了这个OAuth库:https://www.npmjs.com/package/angular-oauth2-oidc
@NgModule({
declarations: [
AppComponent,
BooksComponent,
HeaderComponent,
SideNavComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,
HttpClientModule,
AppRoutingModule,
ReactiveFormsModule,
OAuthModule.forRoot({
resourceServer: {
allowedUrls: ['http://localhost:4200'],
sendAccessToken: true
}
}),
AuthConfigModule,
MatFormFieldModule,
MatInputModule,
MatButtonModule
],
providers: [
TheLibraryGuard,
{ provide: HTTP_INTERCEPTORS,
useClass: DefaultOAuthInterceptor,
multi: true
}
],
entryComponents: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule {
}
@Injectable()
export class CustomAuthGuard implements CanActivate {
constructor(private oauthService: OAuthService, protected router: Router) {
}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): any {
const hasIdToken = this.oauthService.hasValidIdToken();
const hasAccessToken = this.oauthService.hasValidAccessToken();
if (this.oauthService.hasValidAccessToken()) {
return (hasIdToken && hasAccessToken);
}
this.router.navigate([this.router.url]);
return this.oauthService.loadDiscoveryDocumentAndLogin();
}
}
@Injectable()
export class DefaultOAuthInterceptor implements HttpInterceptor {
constructor(
private authStorage: OAuthStorage,
private oauthService: OAuthService,
private errorHandler: OAuthResourceServerErrorHandler,
@Optional() private moduleConfig: OAuthModuleConfig
) {
}
private checkUrl(url: string): boolean {
const found = this.moduleConfig.resourceServer.allowedUrls.find(u => url.startsWith(u));
return !!found;
}
public intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
console.log('INTERCEPTOR');
const url = req.url.toLowerCase();
if (!this.moduleConfig) { return next.handle(req); }
if (!this.moduleConfig.resourceServer) { return next.handle(req); }
if (!this.moduleConfig.resourceServer.allowedUrls) { return next.handle(req); }
if (!this.checkUrl(url)) { return next.handle(req); }
const sendAccessToken = this.moduleConfig.resourceServer.sendAccessToken;
if (sendAccessToken) {
// const token = this.authStorage.getItem('access_token');
const token = this.oauthService.getIdToken();
const header = 'Bearer ' + token;
console.log('TOKEN in INTERCEPTOR : ' + token);
const headers = req.headers
.set('Authorization', header);
req = req.clone({ headers });
}
return next.handle(req)/*.catch(err => this.errorHandler.handleError(err))*/;
}
}
export const authConfig: AuthConfig = {
issuer: environment.keycloak.issuer,
redirectUri: environment.keycloak.redirectUri,
clientId: environment.keycloak.clientId,
dummyClientSecret: environment.keycloak.dummyClientSecret,
responseType: environment.keycloak.responseType,
scope: environment.keycloak.scope,
requireHttps: environment.keycloak.requireHttps,
// at_hash is not present in JWT token
showDebugInformation: environment.keycloak.showDebugInformation,
disableAtHashCheck: environment.keycloak.disableAtHashCheck
};
export class OAuthModuleConfig {
resourceServer: OAuthResourceServerConfig = {sendAccessToken: false};
}
export class OAuthResourceServerConfig {
/**
* Urls for which calls should be intercepted.
* If there is an ResourceServerErrorHandler registered, it is used for them.
* If sendAccessToken is set to true, the access_token is send to them too.
*/
allowedUrls?: Array<string>;
sendAccessToken = true;
customUrlValidation?: (url: string) => boolean;
}
@Injectable()
export class AuthConfigService {
private decodedAccessToken: any;
private decodedIDToken: any;
constructor(
private readonly oauthService: OAuthService,
private readonly authConfig: AuthConfig
) {
}
async initAuth(): Promise<any> {
return new Promise((resolveFn, rejectFn) => {
// setup oauthService
this.oauthService.configure(this.authConfig);
this.oauthService.setStorage(localStorage);
this.oauthService.tokenValidationHandler = new NullValidationHandler();
// subscribe to token events
this.oauthService.events
.pipe(filter((e: any) => {
return e.type === 'token_received';
}))
.subscribe(() => this.handleNewToken());
// continue initializing app or redirect to login-page
this.oauthService.loadDiscoveryDocumentAndLogin().then(isLoggedIn => {
if (isLoggedIn) {
this.oauthService.setupAutomaticSilentRefresh();
resolveFn();
} else {
this.oauthService.initLoginFlow();
rejectFn();
}
});
});
}
private handleNewToken() {
this.decodedAccessToken = this.oauthService.getAccessToken();
this.decodedIDToken = this.oauthService.getIdToken();
}
}
@NgModule({
imports: [ HttpClientModule, OAuthModule.forRoot() ],
providers: [
AuthConfigService,
{ provide: AuthConfig, useValue: authConfig },
OAuthModuleConfig,
{
provide: APP_INITIALIZER,
useFactory: init_app,
deps: [ AuthConfigService ],
multi: true
}
]
})
export class AuthConfigModule { }
export const environment = {
production: false,
envName: 'local',
baseUrl: 'http://localhost:8081/',
keycloak: {
issuer: 'http://localhost:8080/auth/realms/TheLibrary',
redirectUri: 'http://localhost:4200/',
clientId: 'XXXXXXXXXXX',
dummyClientSecret: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
responseType: 'code',
scope: 'openid profile email',
requireHttps: false,
// at_hash is not present in JWT token
showDebugInformation: true,
disableAtHashCheck: true
}
};
网关
spring:
application:
name: gateway-service
cloud:
config:
uri: http://localhost:8888
discovery:
enabled: true
gateway:
# default-filters:
# - TokenRelay
routes:
- id: THELIBRARY-MS-BOOK
uri: lb://thelibrary-ms-book
predicates:
- Path=/api/**
filters:
- TokenRelay=
globalcors:
corsConfigurations:
'[/**]':
allowedOrigins: "*"
allowedMethods:
- GET
- POST
- DELETE
- PUT
add-to-simple-url-handler-mapping: true
security:
oauth2:
client:
provider:
keycloak:
issuer-uri: http://localhost:8080/auth/realms/TheLibrary
user-name-attribute: preferred_username
registration:
keycloak:
client-id: xxxxxxxxxxxxxxxxxx
client-secret: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXxx
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
management:
endpoints:
web:
exposure:
include: "*"
server:
port: 8081
logging:
level:
org:
springframework:
cloud.gateway: DEBUG
http.server.reactive: DEBUG
web.reactive: DEBUG
@SpringBootApplication
@CrossOrigin("*")
public class GatewayApplication {
// @Autowired
// private TokenRelayGatewayFilterFactory filterFactory;
//
// @Bean
// public RouteLocator myRoutes(RouteLocatorBuilder builder) {
// return builder.routes()
// .route(route -> route
// .path("/api/**")
//// .filters(f -> f.hystrix(config -> config.setName("d").setFallbackUri( "forward:/defaultBook" )))
// .filters(f -> f.filter( filterFactory.apply() ))
// .uri("lb://thelibrary-ms-book")
// .id( "ms-books" ))
// .build();
// }
@Bean
DiscoveryClientRouteDefinitionLocator discoveryClientRouteDefinitionLocator(
ReactiveDiscoveryClient reactiveDiscoveryClient,
DiscoveryLocatorProperties discoveryLocatorProperties ){
return new DiscoveryClientRouteDefinitionLocator(reactiveDiscoveryClient, discoveryLocatorProperties);
}
public static void main( String[] args ) {
SpringApplication.run( GatewayApplication.class, args );
}
@Bean
public SecurityWebFilterChain springSecurityFilterChain( ServerHttpSecurity http,
ReactiveClientRegistrationRepository clientRegistrationRepository) {
// Require authentication for all requests
http.cors().and().authorizeExchange().anyExchange().permitAll();
// Allow showing /home within a frame
// http.headers().frameOptions().mode( XFrameOptionsServerHttpHeadersWriter.Mode.SAMEORIGIN);
// Disable CSRF in the gateway to prevent conflicts with proxied service CSRF
http.csrf().disable();
return http.build();
}
}
微服务
spring:
application:
name: thelibrary-ms-book
cloud:
config:
uri: http://localhost:8888
profile: local, prod
discovery:
enabled: true
data:
rest:
return-body-on-create: true
return-body-on-update: true
rabbitmq:
host: localhost
username: user
password: user
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost:8080/auth/realms/TheLibrary
jwk-set-uri: http://localhost:8080/auth/realms/TheLibrary/.well-known/openid-configuration
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
management:
endpoints:
web:
exposure:
include: "*"
server:
port: 8090
servlet:
context-path: /api/
logging:
level:
org:
hibernate:
SQL: DEBUG
type:
descriptor:
sql:
BasicBinder: TRACE
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@AllArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// Validate tokens through configured OpenID Provider
http.cors().and().oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter());
http.cors().and().authorizeRequests().mvcMatchers("/books").hasRole("admin");
// Allow showing pages within a frame
http.headers().frameOptions().sameOrigin();
}
private JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
// Convert realm_access.roles claims to granted authorities, for use in access decisions
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new KeycloakRealmRoleConverter());
return jwtAuthenticationConverter;
}
@Bean
public JwtDecoder jwtDecoderByIssuerUri( OAuth2ResourceServerProperties properties) {
String issuerUri = properties.getJwt().getIssuerUri();
NimbusJwtDecoder jwtDecoder = ( NimbusJwtDecoder ) JwtDecoders.fromIssuerLocation(issuerUri);
// Use preferred_username from claims as authentication name, instead of UUID subject
jwtDecoder.setClaimSetConverter(new UsernameSubClaimAdapter());
return jwtDecoder;
}
}
class KeycloakRealmRoleConverter implements Converter< Jwt, Collection< GrantedAuthority > > {
@Override
@SuppressWarnings("unchecked")
public Collection<GrantedAuthority> convert(final Jwt jwt) {
final Map<String, Object> realmAccess = (Map<String, Object>) jwt.getClaims().get("realm_access");
return (( List<String> ) realmAccess.get("roles")).stream()
.map(roleName -> "ROLE_" + roleName)
.map( SimpleGrantedAuthority::new)
.collect( Collectors.toList());
}
}
class UsernameSubClaimAdapter implements Converter< Map<String, Object>, Map<String, Object>> {
private final MappedJwtClaimSetConverter delegate = MappedJwtClaimSetConverter.withDefaults( Collections.emptyMap());
@Override
public Map<String, Object> convert(Map<String, Object> claims) {
Map<String, Object> convertedClaims = this.delegate.convert(claims);
String username = (String) convertedClaims.get("preferred_username");
convertedClaims.put("sub", username);
return convertedClaims;
}
}
<springboot-version>2.2.5.RELEASE</springboot-version>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
我有一个非常标准的Cleint钥匙斗篷配置,相关的是: - 访问类型:机密 - 启用标准流:开 - 启用隐式流:关闭 - 启用直接访问授权:开 - 启用服务帐户:开 - 启用授权:开
我真的尝试了很多东西,但我再也不知道了。。。
有人可以看一下,告诉我我做错了什么吗?我将不胜感激!:)
非常感谢你的时间!:)
这就是解决我问题的方法!
1-角度:纠正默认OAuthiInterceptor
删除此部分:
if (!this.moduleConfig) { return next.handle(req); }
if (!this.moduleConfig.resourceServer) { return next.handle(req); }
if (!this.moduleConfig.resourceServer.allowedUrls) { return next.handle(req); }
if (!this.checkUrl(url)) { return next.handle(req); }
无论出于什么原因,这些条件中的一个总是以true结束,然后方法的其余部分永远不会执行。(警告:我真的不知道跳过此代码的后果)
所以最后的拦截是:
js prettyprint-override">@Injectable()
export class DefaultOAuthInterceptor implements HttpInterceptor {
constructor(
private authStorage: OAuthStorage,
private oAuthService: OAuthService,
private errorHandler: OAuthResourceServerErrorHandler,
@Optional() private moduleConfig: OAuthModuleConfig
) {
}
private checkUrl(url: string): boolean {
if (this.moduleConfig.resourceServer.customUrlValidation) {
return this.moduleConfig.resourceServer.customUrlValidation(url);
}
if (this.moduleConfig.resourceServer.allowedUrls) {
return !!this.moduleConfig.resourceServer.allowedUrls.find(u =>
url.startsWith(u)
);
}
return true;
}
public intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
const url = req.url.toLowerCase();
// if (
// !this.moduleConfig ||
// !this.moduleConfig.resourceServer ||
// !this.checkUrl(url)
// ) {
// return next.handle(req);
// }
const sendAccessToken = this.moduleConfig.resourceServer.sendAccessToken;
if (!sendAccessToken) {
return next
.handle(req)
.pipe(catchError(err => this.errorHandler.handleError(err)));
}
return merge(
of(this.oAuthService.getAccessToken()).pipe(
filter(token => (token ? true : false))
),
this.oAuthService.events.pipe(
filter(e => e.type === 'token_received'),
timeout(this.oAuthService.waitForTokenInMsec || 0),
catchError(_ => of(null)), // timeout is not an error
map(_ => this.oAuthService.getAccessToken())
)
).pipe(
take(1),
mergeMap(token => {
if (token) {
const header = 'Bearer ' + token;
const headers = req.headers.set('Authorization', header);
req = req.clone({headers});
}
return next
.handle(req)
.pipe(catchError(err => this.errorHandler.handleError(err)));
})
);
}
}
2-在网关中,添加一个CorsWebFilter在Angular拦截器正常工作的情况下,我仍然有一个CORS问题,不管来自Spring Cloud网关留档的yaml配置如何。
我不得不添加一个简单的CorsWebFilter,因为这个链接说 https://github.com/spring-cloud/spring-cloud-gateway/issues/840:
@Configuration
public class PreFlightCorsConfiguration {
@Bean
public CorsWebFilter corsFilter() {
return new CorsWebFilter(corsConfigurationSource());
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration().applyPermitDefaultValues();
config.addAllowedMethod( HttpMethod.GET);
config.addAllowedMethod( HttpMethod.PUT);
config.addAllowedMethod( HttpMethod.POST);
config.addAllowedMethod(HttpMethod.DELETE);
source.registerCorsConfiguration("/**", config);
return source;
}
}
就是这样!它现在像一个魅力:)希望这有助于:)
我正在寻找一个最佳实践和高效的解决方案,以确保通过REST与Web客户端应用程序通信的多个微服务的安全。 当前设置: 这些微服务是用Java制作的,带有Spring框架,并运行在Docker容器中。 客户端是一个Angular 2应用程序。 我创建了一个新的µ服务,它将充当“网关”,是我的web客户端和其他服务之间的唯一通信点。 我从远程身份验证API检索JWT加密令牌(让我们称之为LOCK) 我
我有一个前端,我通过让我们的登录页面向Keycloak服务器发出POST请求来保护它,这样我就可以获取访问令牌(JWT)。我们不使用kecloak提供的登录页面。此前端对后端(spel-boot)servlet进行REST调用,该servlet使用这些调用来查询数据库并将数据发送回前端。我看到的大多数关于保护spel-boot应用程序的教程都使用spel-boot来服务网页,并使用默认的keycl
我有两个微服务,它们应该相互通信。我的例子是:微服务A向微服务B发出请求。用户不应该有访问权限,只能访问其他微服务。我想,那个解决方案可能是“Http基本身份验证”,所以microservice A还应该向microservice B发送用户名和密码。这很好,但microservice A应该如何找到用户名和密码呢?我正在使用Eureka作为发现服务,但在这种情况下,我没有找到任何可以帮助我的。我
我正按照文章中的说明,尝试使用keydepeat保护Spring BootREST服务https://medium.com/devops-dudes/securing-spring-boot-rest-apis-with-keycloak-1d760b2004e.我以docker服务的形式启动KeyClope和我的服务(docker compose.yml见下文) 然后,我首先 然后在将环境变量$
问题内容: 通常我跑步时总是得到这个输出,我确信每个人在跑步时都会得到。这不是全部输出,而是有关特定语句的。 正如标题所说的,我总是得到输出。如何确保端口ChromeDriver仅使用受保护的端口? 问题答案: 此信息消息… …是 ChromeDriver v2.46* 引发的错误的结果 * 分析 根据讨论,如果启用了详细日志记录,则2.46会生成意外的debug.log文件,在logging.c
问题内容: 我的应用程序使用Express和AngularJS。我正在使用express通过静态处理角度代码的基本网络设置。角度代码使用的服务会影响express托管的API端点。我只希望用户经过身份验证后才能访问API端点。如何通过PassportJS完成此操作? 问题答案: 我已经在github上上传了一个Angular-Express 项目。 仍在进行中。希望对您有所帮助。 它使用Passp