在使用 Spring 和 Axios 进行基本身份验证时,我失去了试图处理此类问题的生活欲望。
在尝试了几乎所有方法之后,甚至像您的配置一样,我找到了适合我的用例的解决方案,也可以是您的。
Axios 请求
我用这样的axios发出请求,注意有一个auth参数:
async removeAnexo(uuidAnexo, successCallback, errorCallback) {
const credentials = JSON.parse(Cookies.get("credentials"));
const axiosRequest = axios.create({
baseURL: serverUrl,
auth: {
username: credentials.username,
password: credentials.password
},
headers: {
"Content-Type": "application/json"
}
});
await axiosRequest
.post("processos/anexo/" + uuidAnexo + "/remove")
.then(res => {
if (res.status === 200) {
successCallback();
return res;
}
})
.catch(error => {
errorCallback(error);
});
}
弹簧配置
- 在发出您请求的实际请求之前,您需要有一个身份验证入口点以在预检向端点触发 OPTIONS 请求时公开一些必要的标头。
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.stereotype.Component;
@Component
public class AuthEntryPoint extends BasicAuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
org.springframework.security.core.AuthenticationException authException)
throws IOException, ServletException {
if (HttpMethod.OPTIONS.matches( request.getMethod() )) {
response.setStatus( HttpServletResponse.SC_OK );
response.setHeader( HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, request.getHeader( HttpHeaders.ORIGIN ) );
response.setHeader( HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, request.getHeader( HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS ) );
response.setHeader( HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, request.getHeader( HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD ) );
response.setHeader( HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*" );
response.setHeader( HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true" );
} else {
response.sendError( HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage() );
}
}
@Override
public void afterPropertiesSet() throws Exception {
this.setRealmName( "simpleRealm" );
super.afterPropertiesSet();
}
}
- 然后你需要提供一个自定义过滤器,一个 cors 过滤器,也暴露
一些标头,是 SessionManagementFilter 所必需的。
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
@Component
public class CorsFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpServletRequest request = (HttpServletRequest) servletRequest;
response.setHeader( "Access-Control-Allow-Origin", request.getHeader( "Origin" ) );
response.setHeader( "Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS" );
response.setHeader( "Access-Control-Allow-Headers", "*" );
response.setHeader( "Access-Control-Allow-Credentials", "true" );
response.setHeader( "Access-Control-Expose-Headers", "Content-Disposition,X-Suggested-Filename,X-Auth-Token,Authorization" );
response.setHeader( "Access-Control-Max-Age", "180" );
filterChain.doFilter( servletRequest, servletResponse );
}
@Override
public void destroy() {
}
}
- 现在,您必须提供一个 AuthenticationProvider,在我的情况下,我
配置了一个基于 BasicAuth 的 CustomAuthenticationProvider。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import com.pontosistemas.clif.entities.system.Login;
import com.pontosistemas.clif.repositories.system.LoginRepository;
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Autowired
private LoginRepository loginRepository;
@Autowired
private Environment env;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication auth) throws AuthenticationException {
boolean notProductionProfile = env.acceptsProfiles( Profiles.of( "default", "homolog" ) );
String username = auth.getName();
String password = auth.getCredentials().toString();
if (notProductionProfile && "admin".equals( username ) && "admin".equals( password )) {
return new UsernamePasswordAuthenticationToken( username, password, Arrays.asList( new SimpleGrantedAuthority( "ADMIN" ), new SimpleGrantedAuthority( "protected" ) ) );
} else {
boolean usernameActiveExists = loginRepository.existsLoginByUsernameAndActiveIsTrue( username );
if (usernameActiveExists) {
Login login = loginRepository.findByUsernameAndActiveIsTrue( username );
String hashPassword = login.getPassword();
boolean matches = passwordEncoder.matches( password, hashPassword );
if (matches)
return new UsernamePasswordAuthenticationToken( username, hashPassword, Arrays.asList( new SimpleGrantedAuthority( login.getRole().toString() ), new SimpleGrantedAuthority( "protected" ) ) );
else
throw new BadCredentialsException( "Senha inválida!" );
}
}
throw new UsernameNotFoundException( "Nome de usuário não encontrado" );
}
@Override
public boolean supports(Class<?> auth) {
return auth.equals( UsernamePasswordAuthenticationToken.class );
}
}
- 最后,你需要用胶水把所有这些部件粘在一起,这个
胶水是一个WebSecurityConfigurerAdapter,您可以仔细查看更多细节,尝试注释每个配置行,看看会发生什么。就我而言,我配置了登录成功、注销或登录失败时发生的情况。对于每种情况,都有一个要实现的处理程序。
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.session.SessionManagementFilter;
import com.fasterxml.jackson.databind.ObjectMapper;
@Configuration
@EnableWebSecurity
public class BasicSecurityAdapter extends WebSecurityConfigurerAdapter {
@Autowired
private AuthEntryPoint authEntryPoint;
@Autowired
private CustomAuthenticationProvider customAuthProvider;
@Autowired
private CorsFilter corsFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.addFilterBefore( corsFilter, SessionManagementFilter.class );
http.headers().frameOptions().disable();
http.httpBasic().authenticationEntryPoint( authEntryPoint );
http.authenticationProvider( customAuthProvider );
http
.authorizeRequests()
.antMatchers( "/public/**" ).permitAll()
.antMatchers( HttpMethod.OPTIONS, "/**" ).permitAll()
.antMatchers( "/**" ).hasAuthority( "protected" )
.anyRequest().authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy( SessionCreationPolicy.STATELESS );
http.formLogin().loginProcessingUrl( "/login" );
http.formLogin().successHandler( new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
response.addHeader( "Access-Control-Expose-Headers", "role" );
response.setHeader( "Access-Control-Allow-Origin", request.getHeader( "Origin" ) );
response.setHeader( "Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS" );
response.setHeader( "Access-Control-Allow-Headers", "*" );
response.setHeader( "Access-Control-Allow-Credentials", "true" );
response.setHeader( "Access-Control-Expose-Headers", "Content-Disposition,X-Suggested-Filename,X-Auth-Token,Authorization" );
response.setHeader( "Access-Control-Max-Age", "180" );
String role = authentication.getAuthorities().stream().findFirst().get().toString();
response.addHeader( "role", role );
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> data = new HashMap<String, Object>();
data.put( "role", role );
response.getWriter().print( objectMapper.writeValueAsString( data ) );
}
} );
http.formLogin().failureHandler( new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
response.addHeader( "Access-Control-Expose-Headers", "role" );
response.setHeader( "Access-Control-Allow-Origin", request.getHeader( "Origin" ) );
response.setHeader( "Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS" );
response.setHeader( "Access-Control-Allow-Headers", "*" );
response.setHeader( "Content-type", "application/json; charset=UTF-8" );
response.setCharacterEncoding( "UTF-8" );
response.setStatus( HttpServletResponse.SC_UNAUTHORIZED );
ObjectMapper objectMapper = new ObjectMapper();
Map<String, String> data = new HashMap<String, String>();
data.put( "message", exception.getMessage() );
response.getWriter().print( objectMapper.writeValueAsString( data ) );
}
} );
http.logout().logoutSuccessHandler( (request, response, authentication) -> {
response.setHeader( "Access-Control-Allow-Origin", request.getHeader( "Origin" ) );
response.setHeader( "Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS" );
response.setHeader( "Access-Control-Allow-Headers", "*" );
response.setStatus( HttpServletResponse.SC_OK );
} );
}
}
我的用例
在我的场景中,我在用户登录时将用户登录名和密码存储在 cookie 中,并在调用 API 时携带这些凭据。
我相信您可以从基本身份验证更改为尝试更改 CustomAuthenticationProvider 的另一个选项。
对你来说可能有点晚了,但我希望这对某人有所帮助。