我有一个使用 Kerberos 进行 sso 身份验证的 spring boot 微服务。
问题前:
当用户尝试访问应用程序(角度 UI)并遵循以下步骤时,身份验证过程开始:
前端将调用 /login api(身份验证服务)。
使用 Kerberos 和 spring security,我检索触发请求的用户的 id_name:
SecurityContextHolder.getContext().getAuthentication().getName().split("@")[0]
,这个id_name在公司内部是独一无二的
我使用这个 id_name 查询数据库并获取有关该用户的所有其他信息(无密码),然后将它们发送回前端,前端会将它们注入本地存储,因此他/她不需要连接下次再来
在我们收到更改请求以使用 JWT 来保护对我们拥有的所有微服务的访问之前,这个过程一直运行良好。
此时我添加了生成、验证令牌所需的类和拦截请求并检查令牌是否有效的过滤器...
问题:
除了一个用例外,新流程运行良好:
如果第一个连接请求是直接从前端发出的,请求会被阻止,浏览器会显示一个连接弹出窗口。否则,如果我手动调用,来自浏览器的 API 或使用邮递员,我会在响应标头中使用 Token 获得正确的结果。
注 1:如果我从浏览器手动调用 /login API,我可以像往常一样从应用程序连接,我也可以断开连接并再次连接(使用不同的配置文件..)。
注意 2:我尝试使用旧版本的 Web 服务(在添加 JWT 更改之前)并且我正确连接(令牌在请求响应中)
我在
RedirectionTokenFilter
类中添加了一些日志以获取有关请求的更多信息,在结果中我可以看到 UserPrincipal 为空,这与请求来自 Postman 时不同。
日志:
System.out.println("UserPrincipal :" + ((HttpServletRequest) request).getUserPrincipal());
System.out.println("Auth type :" + ((HttpServletRequest) request).getAuthType());
System.out.println("Context path :" + ((HttpServletRequest) request).getContextPath());
Enumeration<String> headers = ((HttpServletRequest) request).getHeaderNames();
while(headers.hasMoreElements()){
String param = headers.nextElement();
System.out.println("HeaderName :" + param);
try{
System.out.println("Param " +param+" :" + ((HttpServletRequest) request).getHeader(param));
}catch (Exception e){
}
}
System.out.println("Method :" + ((HttpServletRequest) request).getMethod());
System.out.println("RemoteUser :" + ((HttpServletRequest) request).getRemoteUser());
System.out.println("ServletPath :" + ((HttpServletRequest) request).getServletPath());
日志输出:
UserPrincipal :null
Auth type :null
Context path :/XXXX-Auth
HeaderName :host
Param host :XXXX:9280
HeaderName :connection
Param connection :keep-alive
HeaderName :sec-ch-ua
Param sec-ch-ua :"Chromium";v="104", " Not A;Brand";v="99", "Google Chrome";v="104"
HeaderName :pragma
Param pragma :no-cache
HeaderName :sec-ch-ua-mobile
Param sec-ch-ua-mobile :?0
HeaderName :authorization
Param authorization :null
HeaderName :accept
Param accept :application/json, text/plain,...
HeaderName :cache-control
Param cache-control :no-cache
HeaderName :user-agent
Param user-agent :Mozilla/5.0 (Windows NT 10.0; Win64; x64)....
HeaderName :sec-ch-ua-platform
Param sec-ch-ua-platform :"Windows"
HeaderName :expires
Param expires :Sat, 01 Jan 2000 00:00:00 GMT
HeaderName :origin
Param origin :https://XXXXXX
HeaderName :sec-fetch-site
Param sec-fetch-site :same-site
HeaderName :sec-fetch-mode
Param sec-fetch-mode :cors
HeaderName :sec-fetch-dest
Param sec-fetch-dest :empty
HeaderName :referer
Param referer :https://XXXXXX
HeaderName :accept-encoding
Param accept-encoding :gzip, deflate, br
HeaderName :accept-language
Param accept-language :en-US,en;q=0.9
Method :GET
RemoteUser :null
ServletPath :/security/login
tocken {null}
token empty trying login
context[auth]=org.springframework.security.authentication.AnonymousAuthenticationToken@274dc7e4: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: xx.xx.xx.xx; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
UserID anonymousUser
userDTO null
我的spring security配置(添加JWT后):
@Override
protected void configure(HttpSecurity http) throws Exception {
log.info("sec configuration..");
http.csrf().disable().cors().and().authorizeRequests().antMatchers("/**").authenticated().and().httpBasic()
.authenticationEntryPoint(restSpenegoEntryPoint()).and()
.addFilterBefore(spnegoAuthenticationProcessingFilter(), BasicAuthenticationFilter.class)
.addFilterAfter(redirectionTokenFilter(), ExceptionTranslationFilter.class).addFilter(filterd)
}
@Bean
public SpnegoAuthenticationProcessingFilter spnegoAuthenticationProcessingFilter() throws Exception {
SpnegoAuthenticationProcessingFilter spnegoAuthenticationProcessingFilter = new SpnegoAuthenticationProcessingFilter();
// spnegoAuthenticationProcessingFilter.setSuccessHandler(customAuthenticationSuccessHandler());
spnegoAuthenticationProcessingFilter.setAuthenticationManager(authenticationManagerBean());
return spnegoAuthenticationProcessingFilter;
}
@Bean
RedirectionTokenFilter redirectionTokenFilter() {
return new RedirectionTokenFilter();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
final CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Collections.singletonList("*"));
configuration.setAllowedMethods(Arrays.asList("HEAD", "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Cache-Control", "Content-Type", "X-Requested-With"
,"Pragma","X-XSS-Protection","X-Frame-Options","X-Content-Type-Options",
"Vary","Transfer-Encoding","Server","Expires","Date",
"Access-Control-Allow-Headers","Access-Control-Allow-Credentials"
));
configuration.setExposedHeaders(Arrays.asList("Content-type", "Authorization"));
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
RestSpenegoEntryPoint:
public class RestSpenegoEntryPoint extends SpnegoEntryPoint{
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException ex)
throws IOException, ServletException {
response.addHeader("WWW-Authenticate", "Negotiate");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
AccessDeniedHandlerImpl:
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.setStatus(401);
response.getWriter().flush();
}
Angular 变化(我不是前端开发人员,我不太了解那些变化):
app.module.ts:
@NgModule({
imports: [
providers: [
...
{ provide : HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi : true},
auth-interceptor.ts:
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Authservice } from '../service/auth.service';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
req = req.clone({
setHeaders: {
'Authorization': `${Authservice.getToken()}`,
},
});
return next.handle(req);
}
}
webservice.config.ts:
import {Authservice} from '../auth/service/auth.service';
export class WebServicesConfig {
@@ -11,12 +12,14 @@
headers.append('Access-Control-Allow-Headers', 'Content-Type');
headers.append('withCredentials', 'true');
headers.append('Access-Control-Allow-Origin', '*');
headers.append('Authorization', Authservice.getToken());
return headers;
}
static getHeaders1(): Headers {
let headers: Headers = new Headers();
headers.append('Accept', 'application/json');
headers.append('Content-Type', 'application/json');
headers.append('Authorization', Authservice.getToken());
return headers;
}
}
如果有什么不清楚或缺少信息,请告诉我。
提前谢谢你。