我正在尝试创建一个应用程序,主要使用Spring访问REST API,并尝试配置安全性。尝试使用此图片呈现应用程序的实际结构:
And我有两种身份验证Binder,即基于令牌和基于用户名的身份验证。 基于令牌的:
问题: 每当我加载我的应用程序时,它会进入基于用户的身份验证,然后尝试从令牌中获取用户名,而不是从数据库中进行验证。但是,此时还没有令牌,因为这是我从UI发出的第一个POST请求。然后它会将我重定向回相同的登录页面。
日志:
细节: / 位于附加过滤器链中的第1个位置; 触发过滤器: 'WebAsyncManagerIntegrationFilter' 细节: / 位于附加过滤器链中的第2个位置; 触发过滤器: 'SecurityContextPersistenceFilter' 细节: / 位于附加过滤器链中的第3个位置; 触发过滤器: 'HeaderWriterFilter' 细节:
由于它未匹配请求匹配器org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@a837508,因此不注入HSTS标头 细节: / 位于附加过滤器链中的第4个位置; 触发过滤器: 'LogoutFilter' 细节: 检查请求的匹配项:'/'; 对 'http://localhost/web/ui/#access/signin' 进行检查 细节: / 位于附加过滤器链中的第5个位置; 触发过滤器: 'StatelessAuthenticationFilter' 细节: / 位于附加过滤器链中的第6个位置; 触发过滤器: 'UsernamePasswordAuthenticationFilter' 细节: 请求 'GET /' 不匹配 'POST http://localhost/web/ui/#access/signin 细节: / 位于附加过滤器链中的第7个位置; 触发过滤器: 'RequestCacheAwareFilter' 细节: / 位于附加过滤器链中的第8个位置; 触发过滤器: 'SecurityContextHolderAwareRequestFilter' 细节: / 位于附加过滤器链中的第9个位置; 触发过滤器: 'AnonymousAuthenticationFilter' 细节: 使用匿名令牌填充SecurityContextHolder:'org.springframework.security.authentication.AnonymousAuthenticationToken@9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS' 细节: / 位于附加过滤器链中的第10个位置; 触发过滤器: 'SessionManagementFilter' 细节: 请求的会话ID 3e2c15a2a427bf47e51496d2a186无效。 细节: / 位于附加过滤器链中的第11个位置; 触发过滤器: 'ExceptionTranslationFilter' 细节: / 位于附加过滤器链中的第12个位置; 触发过滤器: 'FilterSecurityInterceptor' 细节: 安全对象: FilterInvocation: URL: /; 属性: [authenticated] 细节: 先前验证: org.springframework.security.authentication.AnonymousAuthenticationToken@9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS 细节: 选民: org.springframework.security.web.access.expression.WebExpressionVoter@2ac71565,返回值:-1 细节: 拒绝访问(用户为匿名用户); 重定向到认证入口点org.springframework.security.access.AccessDeniedException: Access is denied
![enter image description here](https://istack.dev59.com/M9afo.webp)
- 请求可以来自任何平台到“abc.com/rest_api/”
- 请求将被发送到点3或点5。如果用户已通过用户名和密码进行身份验证,则请求将针对令牌进行验证,否则将被重定向到数据库。
- 如果必须通过数据库对用户名和密码进行身份验证,则将生成令牌并在响应中发送回。
- 之后只有基于令牌的身份验证才能工作。
@Configuration
@EnableWebSecurity
@EnableWebMvcSecurity
@EnableGlobalMethodSecurity(securedEnabled=true, prePostEnabled=true)
public class UserDetailsSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private NSecurityContextHolder securityContextHolder;
@Autowired
private NHttpServletRequestBinder<Authentication> authenticationBinder;
public static final String DEF_USERS_BY_USERNAME_QUERY
= "SELECT user ";
public static final String GROUPS_BY_USERNAME_QUERY =
"SELECT groups by user";
public static final String DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY =
"SELECT authorities";
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(getDataSourceFromJndi())
.usersByUsernameQuery(DEF_USERS_BY_USERNAME_QUERY).
authoritiesByUsernameQuery(DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY).
groupAuthoritiesByUsername(GROUPS_BY_USERNAME_QUERY);
}
private DataSource getDataSourceFromJndi() {
try {
DataSource dataSource = (DataSource) new InitialContext().lookup("DS");
return dataSource;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(getDataSourceFromJndi())
.usersByUsernameQuery(DEF_USERS_BY_USERNAME_QUERY).
authoritiesByUsernameQuery(DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY).
groupAuthoritiesByUsername(GROUPS_BY_USERNAME_QUERY);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// The http.formLogin().defaultSuccessUrl("/path/") method is required when using stateless Spring Security
// because the session cannot be used to redirect to the page that was requested while signed out. Unfortunately
// using this configuration method will cause our custom success handler (below) to be overridden with the
// default success handler. So to replicate the defaultSuccessUrl("/path/") configuration we will instead
// correctly configure and delegate to the default success handler.
final SimpleUrlAuthenticationSuccessHandler delegate = new SimpleUrlAuthenticationSuccessHandler();
delegate.setDefaultTargetUrl("/api/");
// Make Spring Security stateless. This means no session will be created by Spring Security, nor will it use any
// previously existing session.
http.sessionManagement().sessionCreationPolicy(STATELESS);
// Disable the CSRF prevention because it requires the session, which of course is not available in a
// stateless application. It also greatly complicates the requirements for the sign in POST request.
http.csrf().disable();
// Viewing any page requires authentication.
http.authorizeRequests().anyRequest().authenticated();
http
.formLogin().loginPage("http://localhost/web/ui/#access/signin")
.permitAll()
// Override the sign in success handler with our stateless implementation. This will update the response
// with any headers and cookies that are required for subsequent authenticated requests.
.successHandler(new NStatelessAuthenticationSuccessHandler(authenticationBinder, delegate));
http.logout().logoutUrl("http://localhost/web/ui/#access/signin").logoutSuccessUrl("http://localhost/web/ui/#access/signin");
// Add our stateless authentication filter before the default sign in filter. The default sign in filter is
// still used for the initial sign in, but if a user is authenticated we need to acknowledge this before it is
// reached.
http.addFilterBefore(
new StatelessAuthenticationFilter(authenticationBinder, securityContextHolder),
UsernamePasswordAuthenticationFilter.class
);
}
}
And我有两种身份验证Binder,即基于令牌和基于用户名的身份验证。 基于令牌的:
@Component
public class NXAuthTokenHttpServletRequestBinder implements NHttpServletRequestBinder<String> {
private static final String X_AUTH_TOKEN = "X-AUTH-TOKEN";
private final NTokenFactory tokenFactory;
@Autowired
public NXAuthTokenHttpServletRequestBinder(NTokenFactory tokenFactory) {
this.tokenFactory = tokenFactory;
}
@Override
public void add(HttpServletResponse response, String username) {
final String token = tokenFactory.create(username);
response.addHeader(X_AUTH_TOKEN, token);
response.addCookie(new Cookie(X_AUTH_TOKEN, token));
}
@Override
public String retrieve(HttpServletRequest request) {
final String cookieToken = findToken(request);
if (cookieToken != null) {
return tokenFactory.parseUsername(cookieToken);
}
return null;
}
private static String findToken(HttpServletRequest request) {
Enumeration<String> it = request.getHeaderNames();
while(it.hasMoreElements()){
System.out.println(it.nextElement());
}
final String headerToken = request.getHeader(X_AUTH_TOKEN);
if (headerToken != null) {
return headerToken;
}
final Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (X_AUTH_TOKEN.equals(cookie.getName())) {
return cookie.getValue();
}
}
}
return null;
}
}
基于用户:
@Component
@Primary
public class NUserAuthenticationFactory implements NHttpServletRequestBinder<Authentication> {
private final NHttpServletRequestBinder<String> httpServletRequestBinder;
@Autowired
public NUserAuthenticationFactory(NHttpServletRequestBinder<String> httpServletRequestBinder) {
this.httpServletRequestBinder = httpServletRequestBinder;
}
@Override
public void add(HttpServletResponse response, Authentication authentication) {
httpServletRequestBinder.add(response, authentication.getName());
}
@Override
public UserAuthentication retrieve(HttpServletRequest request) {
final String username = httpServletRequestBinder.retrieve(request);
if (username != null) {
return new UserAuthentication(new CustomJDBCDaoImpl().loadUserByUsername(username));
}
return null;
}
}
问题: 每当我加载我的应用程序时,它会进入基于用户的身份验证,然后尝试从令牌中获取用户名,而不是从数据库中进行验证。但是,此时还没有令牌,因为这是我从UI发出的第一个POST请求。然后它会将我重定向回相同的登录页面。
日志:
细节: / 位于附加过滤器链中的第1个位置; 触发过滤器: 'WebAsyncManagerIntegrationFilter' 细节: / 位于附加过滤器链中的第2个位置; 触发过滤器: 'SecurityContextPersistenceFilter' 细节: / 位于附加过滤器链中的第3个位置; 触发过滤器: 'HeaderWriterFilter' 细节:
由于它未匹配请求匹配器org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@a837508,因此不注入HSTS标头 细节: / 位于附加过滤器链中的第4个位置; 触发过滤器: 'LogoutFilter' 细节: 检查请求的匹配项:'/'; 对 'http://localhost/web/ui/#access/signin' 进行检查 细节: / 位于附加过滤器链中的第5个位置; 触发过滤器: 'StatelessAuthenticationFilter' 细节: / 位于附加过滤器链中的第6个位置; 触发过滤器: 'UsernamePasswordAuthenticationFilter' 细节: 请求 'GET /' 不匹配 'POST http://localhost/web/ui/#access/signin 细节: / 位于附加过滤器链中的第7个位置; 触发过滤器: 'RequestCacheAwareFilter' 细节: / 位于附加过滤器链中的第8个位置; 触发过滤器: 'SecurityContextHolderAwareRequestFilter' 细节: / 位于附加过滤器链中的第9个位置; 触发过滤器: 'AnonymousAuthenticationFilter' 细节: 使用匿名令牌填充SecurityContextHolder:'org.springframework.security.authentication.AnonymousAuthenticationToken@9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS' 细节: / 位于附加过滤器链中的第10个位置; 触发过滤器: 'SessionManagementFilter' 细节: 请求的会话ID 3e2c15a2a427bf47e51496d2a186无效。 细节: / 位于附加过滤器链中的第11个位置; 触发过滤器: 'ExceptionTranslationFilter' 细节: / 位于附加过滤器链中的第12个位置; 触发过滤器: 'FilterSecurityInterceptor' 细节: 安全对象: FilterInvocation: URL: /; 属性: [authenticated] 细节: 先前验证: org.springframework.security.authentication.AnonymousAuthenticationToken@9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS 细节: 选民: org.springframework.security.web.access.expression.WebExpressionVoter@2ac71565,返回值:-1 细节: 拒绝访问(用户为匿名用户); 重定向到认证入口点org.springframework.security.access.AccessDeniedException: Access is denied