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?

Muss ich re-implementieren Sie mit so etwas wie: @Bean public FilterChainProxy springSecurityFilterChain() throws Exception { // alle die Dinge, die es tut jetzt minus userpassword-und plus-meinen neuen provider }

InformationsquelleAutor Michael Coxon | 2014-06-11

Schreibe einen Kommentar