Spring Boot-Test: Spring Security mit custom-auth-provider nicht sichtbar auf springsecurityfilterchain
Habe ich eher spezielle Anforderungen für die Authentifizierung (Benutzername, Kennwort und Geräte-oder gerade-Gerät eingeben). Das machte mich zu dem Schluss, dass die üblichen UsernamePasswordAuthenticationFilter nicht funktionieren würde, also habe ich meinen eigenen filter -, provider-und token, die sind unten gezeigt. Ersten Anbieter:
@Service(value="customAuthenticationProvider")
public class DeviceUsernamePasswordAuthenticationProvider implements AuthenticationProvider {
private static final Logger LOG = LoggerFactory.getLogger(DeviceUsernamePasswordAuthenticationProvider.class);
@Autowired
private CustomUserDetailsService customUserDetailsService;
@Autowired
private DeviceDetailsService deviceDetailsService;
@Override
public boolean supports(Class<? extends Object> authentication) {
return authentication.equals(DeviceUsernamePasswordAuthenticationToken.class);
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
LOG.info("Authenticating device and user - assigning authorities...");
DeviceUsernamePasswordAuthenticationToken auth = (DeviceUsernamePasswordAuthenticationToken) authentication;
String name = auth.getName();
String password = auth.getCredentials().toString();
boolean isDeviceRequest = (name == null && password == null);
LOG.debug("name is {}, password is {}", name, password);
//(a) nothing, (b) hasToken|<token encoding>, or (c) getToken|<base64 encoded device request>
String deviceToken = auth.getDeviceAuthorisation();
if (deviceToken == null) {
//very bad - set as anonymous
LOG.error("missing.device.token");
throw new BadCredentialsException("missing.device.token");
}
LOG.debug("deviceToken is {}", deviceToken);
String[] deviceInformation = StringUtils.split(deviceToken,"|");
DeviceDetails device = null;
if(deviceInformation[0].equals("getToken")) {
LOG.debug("getToken");
//we expect the array to be of length 3, if not, the request is malformed
if (deviceInformation.length < 3) {
LOG.error("malformed.device.token");
throw new BadCredentialsException("malformed.device.token");
}
device = deviceDetailsService.loadDeviceByDeviceId(deviceInformation[1]);
if (device == null) {
LOG.error("missing.device");
throw new BadCredentialsException("missing.device");
} else {
//otherwise, get the authorities
auth = new DeviceUsernamePasswordAuthenticationToken(null, null,
device.getDeviceId(), device.getAuthorities());
//also we need to set a new token into the database
String newToken = Hashing.sha256()
.hashString("your input", Charsets.UTF_8)
.toString();
deviceDetailsService.setToken(device.getDeviceId(),newToken);
//and put it into the response headers
auth.setDeviceTokenForHeaders(newToken);
}
} else if(deviceInformation[0].equals("hasToken")) {
LOG.debug("hasToken");
if (deviceInformation.length < 3) {
LOG.error("malformed.device.token");
throw new BadCredentialsException("malformed.device.token");
}
//check that there is a token and that the token has not expired
String token = deviceDetailsService.getToken(deviceInformation[1]);
if (token == null) {
//we got a token in the request but the token we have no stored token
LOG.error("mismatched.device.token");
throw new BadCredentialsException("mismatched.device.token");
} else if(!token.equals(deviceInformation[2])) {
//we got a token in the request and its not the same as the token we have stored
LOG.error("mismatched.device.token");
throw new BadCredentialsException("mismatched.device.token");
} else if ( deviceDetailsService.hasTokenExpired(deviceInformation[1])) {
//we got a token in the request and its not the same as the token we have stored
LOG.error("expired.device.token");
throw new BadCredentialsException("expired.device.token");
} else {
//token was in the request, correctly formed, and matches out records
device = deviceDetailsService.loadDeviceByDeviceId(deviceInformation[1]);
auth = new DeviceUsernamePasswordAuthenticationToken(null, null,
device.getDeviceId(), device.getAuthorities());
}
} else {
LOG.error("malformed.device.token");
throw new BadCredentialsException("malformed.device.token");
}
if (!isDeviceRequest) {
UserDetails user = customUserDetailsService.loadUserByUsername(name);
auth = new DeviceUsernamePasswordAuthenticationToken(name, password, device.getDeviceId(), device.getAuthorities());
}
return auth;
}
}
token:
public class DeviceUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken {
private String deviceAuthorisation;
private String deviceTokenForHeaders;
public DeviceUsernamePasswordAuthenticationToken(Object principal, Object credentials, String deviceAuthorisation) {
super(principal, credentials);
this.deviceAuthorisation = deviceAuthorisation;
}
public DeviceUsernamePasswordAuthenticationToken(Object principal, Object credentials, String deviceAuthorisation, List<GrantedAuthority> authorities) {
super(principal, credentials, authorities);
this.deviceAuthorisation = deviceAuthorisation;
}
public String getDeviceAuthorisation() {
return deviceAuthorisation;
}
public void setDeviceAuthorisation(String deviceAuthorisation) {
this.deviceAuthorisation = deviceAuthorisation;
}
public String getDeviceTokenForHeaders() {
return deviceTokenForHeaders;
}
public void setDeviceTokenForHeaders(String deviceTokenForHeaders) {
this.deviceTokenForHeaders = deviceTokenForHeaders;
}
@Override
public String toString() {
return "DeviceUsernamePasswordAuthenticationToken{" +
"deviceAuthorisation='" + deviceAuthorisation + '\'' +
'}';
}
}
und die filter:
public class DeviceUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
public static final String SPRING_SECURITY_FORM_DEVICE_KEY = "device";
private String deviceParameter = SPRING_SECURITY_FORM_DEVICE_KEY;
private boolean postOnly = true;
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
String password = obtainPassword(request);
String device = obtainDevice(request);
if(username != null) {
username = username.trim();
}
DeviceUsernamePasswordAuthenticationToken authRequest = new DeviceUsernamePasswordAuthenticationToken(username, password, device);
//TODO: check an see if I need to do any additional work here.
setDetails(request, authRequest);
response.addHeader("X-AUTH-TOKEN", authRequest.getDeviceTokenForHeaders());
return this.getAuthenticationManager().authenticate(authRequest);
}
protected String obtainDevice(HttpServletRequest request) {
String token = "hasToken|" + request.getHeader("X-AUTH-TOKEN");
if(token == null) {
String deviceInformation = request.getParameter(deviceParameter);
if(deviceInformation != null) {
token = "getToken|" + StringUtils.newStringUtf8(
Base64.decodeBase64(deviceInformation));
}
}
return token;
}
}
Nun, ich habe ein security-config wie folgt aussieht:
@Configuration
@EnableWebMvcSecurity
@ComponentScan({
"com.xxxxxcorp.xxxxxpoint.security",
"com.xxxxxcorp.xxxxxpoint.service",
"com.xxxxxcorp.xxxxxpoint.model.dao"})
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
DeviceUsernamePasswordAuthenticationProvider customAuthenticationProvider;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
System.out.println( "we are getting the custom config right?" );
auth
.authenticationProvider(customAuthenticationProvider);
}
@Configuration
@Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**")
.authorizeRequests()
.anyRequest().hasRole("ADMIN")
.and()
.httpBasic();
}
}
@Order(2)
@Configuration
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.failureUrl("/login?error=1")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/");
}
}
}
- und schließlich der test-Kontext (Hinweis: die springSecurityFilterChain autowiring)
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestApplicationConfig.class,TestPersistenceConfig.class,MvcConfig.class,SecurityConfig.class},loader=AnnotationConfigWebContextLoader.class)
@WebAppConfiguration
@Transactional
public class ApplicationIntegrationTest {
MockMvc mockMvc;
@Autowired
private WebApplicationContext wac;
@Autowired
private FilterChainProxy springSecurityFilterChain;
@Autowired
private UserDao userDao;
@Autowired
private ClientDao clientDao;
@Autowired
private RoleDao roleDao;
UUID key = UUID.fromString("f3512d26-72f6-4290-9265-63ad69eccc13");
@Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).addFilter(springSecurityFilterChain).build();
List<Client> clients = new ArrayList<Client>();
List<Role> roles = new ArrayList<Role>();
Role roleUser = new Role();
roleUser.setRole("user");
Role roleUserDomain = roleDao.save(roleUser);
roles.add(roleUserDomain);
Role roleAdmin = new Role();
roleAdmin.setRole("admin");
Role roleAdminDomain = roleDao.save(roleAdmin);
roles.add(roleAdminDomain);
Client clientEN = new Client();
clientEN.setDeviceId("444444444");
clientEN.setLanguage("en-EN");
clientEN.setAgentId("444444444|68:5b:35:8a:7c:d0");
clientEN.setRoles(roles);
Client clientENDomain = clientDao.save(clientEN);
clients.add(clientENDomain);
User user = new User();
user.setLogin("user");
user.setPassword("password");
user.setClients(clients);
user.setRoles(roles);
userDao.save(user);
}
@Test
public void thatViewBootstrapUsesHttpNotFound() throws Exception {
MvcResult result = mockMvc.perform(post("/login")
.param("username", "user").param("password", "password")
.header("X-AUTH-TOKEN","NDQ0NDQ0NDQ0fDY4OjViOjM1OjhhOjdjOmQw")).andReturn();
Cookie c = result.getResponse().getCookie("my-cookie");
Cookie[] cookies = result.getResponse().getCookies();
for (int i = 0; i < cookies.length; i++) {
System.out.println("cookie " + i + " name: " + cookies[i].getName());
System.out.println("cookie " + i + " value: " + cookies[i].getValue());
}
//assertThat(c.getValue().length(), greaterThan(10));
//No cookie; 401 Unauthorized
mockMvc.perform(get("/")).andExpect(status().isUnauthorized());
//With cookie; 200 OK
mockMvc.perform(get("/").cookie(c)).andExpect(status().isOk());
//Logout, and ensure we're told to wipe the cookie
result = mockMvc.perform(delete("/session")).andReturn();
c = result.getResponse().getCookie("my-cookie");
assertThat(c.getValue().length(), is(0));
}
}
Was ist im Grunde passiert, ist, dass die login-Anfrage wird abgefangen durch den normalen UsernamePasswordAuthenticationFilter und nicht meine benutzerdefinierte Authentifizierung. Ich hätte gedacht, dass die SecurityConfig hätte sichergestellt, dass die richtigen Auswechslungen vorgenommen wurden, aber es scheint, dass die Nutzung von:
@Autowired
private FilterChainProxy springSecurityFilterChain;
überschreibt? weiß jemand, warum?
InformationsquelleAutor Michael Coxon | 2014-06-11
Du musst angemeldet sein, um einen Kommentar abzugeben.
Haben Sie 3-filter-Ketten (soweit man das sagen kann). Ein Standard (der äußeren
WebConfigurerAdapter
), und 2 benutzerdefinierte (hat man explizitehttpBasic()
und der andere hatformLogin()
). Der Standard hat order=0 denke ich, und schützt alles, das ist also die eine, die Sie treffen, wenn Sie senden Sie eine Anfrage in das gesamte filter. Also das ist eher ein problem. Und keiner von Ihnen (soweit ich das sehen kann) installiert Ihre Geräte-details filter, so dass die Authentifizierungsanbieter, die Sie Hinzugefügt wird nie verwendet werden. Das ist ein weiteres problem.Ja, nicht zu verlängern
WebSecurityConfigurerAdapter
ist wahrscheinlich zu helfen, wenn Sie nicht wollen, eine Standard-Kette. Und Nein, das hinzufügen eines authentifizierungsanbieters nicht, installieren Sie einen filter (zwei verschiedene Objekte, Sie arbeiten zusammen, aber beide müssen installiert sein). Blick auf dieaddFilter()
Methoden inHttpSecurity
. Vielleicht ist die Spring Security Dokumentation könnte Ihnen helfen zu verstehen, die grundlegenden Schritte.Ja, ich war auf der Suche an, leider ist die spring-security-doc fällt etwas kurz, tatsächlich zeigt Ihnen, wie Sie dies tun. Wenn ich einfach einen filter hinzufügen, dann ich am Ende mit zwei Authentifizierungs-Filter, weil das original ist immer noch da. Ich hatte einen Gedanken an das hinzufügen der Filter in der aktuellen chain an minus der usernamepasswordauthfilter, aber dies schien mühsam und wahrscheinlich falsch. Es kann ein Fall von zurück zu xml-Konfiguration, da war das relativ einfach unter diesem regime. Danke, übrigens, für deine Hilfe.
InformationsquelleAutor Dave Syer
Letztlich, es stellt sich heraus, dass, wenn Sie das überschreiben der
UsernamePasswordAuthenticationFilter
,Provider
undToken
müssen Sie gehen zurück auf XML-Konfiguration. Es scheint, dass Spring Security nicht richtig, die veröffentlichen die überschriebenen vanilla FrühjahrSecurityFilterChain
wie eine Bohne, also das ist, was Sie erhalten, die Vanille-version zurück, egal was Sie versuchen, zu konfigurieren.Vielleicht, wenn Spring Security geht zu version 4.0.0, wir werden in der Lage sein, dies zu tun durch Java Konfiguration.
InformationsquelleAutor Michael Coxon