java 登录失败后,Spring Boot 安全性显示 Http-Basic-Auth 弹出窗口
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/37763186/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring Boot security shows Http-Basic-Auth popup after failed login
提问by Dennie
I'm currently creating a simple app for a school project, Spring Boot backend and AngularJS frontend, but have a problem with security that I can't seem to solve.
我目前正在为学校项目、Spring Boot 后端和 AngularJS 前端创建一个简单的应用程序,但有一个我似乎无法解决的安全问题。
Logging in works perfectly, but when I enter a wrong password the default login popup shows up, which is kind of annoying. I've tried the annotation 'BasicWebSecurity' and putting httpBassic on disabled, but with no result (meaning, that the login procedure doesn't work at all anymore).
登录工作正常,但是当我输入错误的密码时,会出现默认的登录弹出窗口,这有点烦人。我已经尝试了注释“BasicWebSecurity”并将 httpBassic 置于禁用状态,但没有结果(意思是,登录过程不再起作用)。
My security class:
我的安全课:
package be.italent.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.WebUtils;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
@Override
public void configure(WebSecurity web){
web.ignoring()
.antMatchers("/scripts/**/*.{js,html}")
.antMatchers("/views/about.html")
.antMatchers("/views/detail.html")
.antMatchers("/views/home.html")
.antMatchers("/views/login.html")
.antMatchers("/bower_components/**")
.antMatchers("/resources/*.json");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
.and()
.authorizeRequests()
.antMatchers("/user", "/index.html", "/", "/projects/listHome", "/projects/{id}", "/categories", "/login").permitAll().anyRequest()
.authenticated()
.and()
.csrf().csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class).formLogin();
}
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class
.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null
&& !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
Does anybody have an idea on how to prevent this popup from showing without breaking the rest?
有没有人知道如何在不破坏其余部分的情况下防止此弹出窗口显示?
solution
解决方案
Added this to my Angular config:
将此添加到我的 Angular 配置中:
myAngularApp.config(['$httpProvider',
function ($httpProvider) {
$httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
}
]);
回答by Yannic Klem
Let's start with your problem
让我们从你的问题开始
It is not a "Spring Boot security popup" its a browser popup that shows up, if the response of your Spring Boot app contains the following header:
如果您的 Spring Boot 应用程序的响应包含以下标头,则它不是“Spring Boot 安全弹出窗口”,而是显示的浏览器弹出窗口:
WWW-Authenticate: Basic
In your security configuration a .formLogin()
shows up. This should not be required. Though you want to authenticate through a form in your AngularJS application, your frontend is an independent javascript client, which should use httpBasic instead of form login.
在您的安全配置中.formLogin()
出现了。这不应该是必需的。尽管您想通过 AngularJS 应用程序中的表单进行身份验证,但您的前端是一个独立的 javascript 客户端,它应该使用 httpBasic 而不是表单登录。
How your security config could look like
您的安全配置如何
I removed the .formLogin()
:
我删除了.formLogin()
:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
.authorizeRequests()
.antMatchers("/user", "/index.html", "/", "/projects/listHome", "/projects/{id}", "/categories", "/login").permitAll().anyRequest()
.authenticated()
.and()
.csrf().csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
}
How to deal with the browser popup
如何处理浏览器弹窗
As previously mentioned the popup shows up if the response of your Spring Boot app contains the header WWW-Authenticate: Basic
. This should not be disabled for all requests in your Spring Boot app, since it allows you to explore the api in your browser very easily.
如前所述,如果您的 Spring Boot 应用程序的响应包含 header ,则会显示弹出窗口WWW-Authenticate: Basic
。不应为 Spring Boot 应用程序中的所有请求禁用此功能,因为它允许您非常轻松地在浏览器中浏览 api。
Spring Security has a default configuration that allows you to tell the Spring Boot app within each request not to add this header in the response. This is done by setting the following header to your request:
Spring Security 有一个默认配置,允许您告诉每个请求中的 Spring Boot 应用程序不要在响应中添加此标头。这是通过为您的请求设置以下标头来完成的:
X-Requested-With: XMLHttpRequest
How to add this header to every request made by your AngularJS app
如何将此标头添加到您的 AngularJS 应用程序发出的每个请求中
You can just add a default header in the app config like that:
您可以在应用程序配置中添加一个默认标头,如下所示:
yourAngularApp.config(['$httpProvider',
function ($httpProvider) {
$httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
}
]);
The backend will now respond with a 401-response that you have to handle by your angular app (by an interceptor for example).
后端现在将以 401 响应进行响应,您必须通过 angular 应用程序(例如,通过拦截器)处理该响应。
If you need an example how to do this you could have a look at my shopping list app. Its done with spring boot and angular js.
如果您需要一个如何执行此操作的示例,您可以查看我的购物清单应用程序。它是用 spring boot 和 angular js 完成的。
回答by Masinac
As Yannic Klem already said this is happening because of the header
正如 Yannic Klem 已经说过的,这是因为头球
WWW-Authenticate: Basic
But there is a way in spring to turn it off, and it's really simple. In your configuration just add:
但是春天有办法把它关掉,而且真的很简单。在您的配置中只需添加:
.httpBasic()
.authenticationEntryPoint(authenticationEntryPoint)
and since authenticationEntryPointis not defined yet, autowire it at the beginning:
并且由于authenticationEntryPoint尚未定义,因此在开始时自动装配它:
@Autowired private MyBasicAuthenticationEntryPoint authenticationEntryPoint;
And now create MyBasicAuthenticationEntryPoint.classand paste the following code:
现在创建MyBasicAuthenticationEntryPoint.class并粘贴以下代码:
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.stereotype.Component;
@Component
public class MyBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
/**
* Used to make customizable error messages and codes when login fails
*/
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authEx)
throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter writer = response.getWriter();
writer.println("HTTP Status 401 - " + authEx.getMessage());
}
@Override
public void afterPropertiesSet() throws Exception {
setRealmName("YOUR REALM");
super.afterPropertiesSet();
}
}
Now your app wont send WWW-Authenticate: Basicheader, because of that pop-up windows will not show, and there is no need to mess with headers in Angular.
现在您的应用程序不会发送WWW-Authenticate: Basic标头,因为不会显示弹出窗口,并且无需在 Angular 中弄乱标头。
回答by Davijr
As already explained above, the problem is in the header of response that is set with the values "WWW-Authenticate: Basic".
正如上面已经解释过的,问题出在设置为“WWW-Authenticate: Basic”值的响应标头中。
Another solution that can solve this is to implement the AuthenticationEntryPoint interface (directly) without placing these values in the header:
可以解决此问题的另一种解决方案是(直接)实现 AuthenticationEntryPoint 接口,而无需将这些值放在标头中:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//(....)
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/*.css","/*.js","/*.jsp").permitAll()
.antMatchers("/app/**").permitAll()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/j_spring_security_check")
.defaultSuccessUrl("/", true)
.failureUrl("/login?error=true")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.deleteCookies("JSESSIONID")
.clearAuthentication(true)
.invalidateHttpSession(true)
.and()
.exceptionHandling()
.accessDeniedPage("/view/error/forbidden.jsp")
.and()
.httpBasic()
.authenticationEntryPoint(new AuthenticationEntryPoint(){ //<< implementing this interface
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
//>>> response.addHeader("WWW-Authenticate", "Basic realm=\"" + realmName + "\""); <<< (((REMOVED)))
response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
}
});
}
//(....)
}