2014-06-11 100 views
1

我對身份驗證(作爲用戶名,密碼和設備或只是設備進入)有比較特殊的要求。這使我得出結論:通常的UsernamePasswordAuthenticationFilter不起作用,所以我設置了自己的過濾器,提供程序和令牌,如下所示。首先,供應商:春季開機測試:彈簧安全與自定義身份驗證提供程序不可見springsecurityfilterchain

@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; 
    } 

} 

令牌:

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 + '\'' + 
       '}'; 
    } 
} 

和過濾器:

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; 
    } 
} 

現在,我有一個安全的配置看起來像這樣:

@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("/"); 
     } 
    } 
} 

最後,測試上下文(注意springSecurity FilterChain自動裝配)

@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)); 
    } 

} 

什麼基本情況是,登錄請求被通過正常UsernamePasswordAuthenticationFilter,而不是我的自定義驗證攔截。我本來以爲在SecurityConfig會確保正確的換人被做了,但似乎的用法:

@Autowired 
private FilterChainProxy springSecurityFilterChain; 

覆蓋嗎?有誰知道爲什麼?

+0

我需要重新實現類似的東西:@Bean public FilterChainProxy springSecurityFilterChain()拋出異常{//現在所做的所有東西減去用戶密碼並加上我的新提供者} –

回答

0

事實證明,如果您覆蓋UsernamePasswordAuthenticationFilter,ProviderToken,則需要返回XML配置。看起來Spring Security沒有正確地將重寫的香草彈簧SecurityFilterChain作爲一個bean發佈,所以無論您嘗試配置什麼,這都是您獲得香草版本的原因。

也許當彈簧安全轉到版本4.0.0,我們將能夠通過Java配置。

1

你有3個過濾鏈(據我們所知)。一個默認的(外部WebConfigurerAdapter)和2個自定義的(一個有明確的httpBasic(),另一個有formLogin())。默認的順序= 0我認爲,並保護所有內容,所以如果您向整個過濾器發送請求,那麼您將會遇到這種情況。所以這可能是一個問題。並且他們中沒有一個(就我所見)安裝設備詳細信息過濾器,因此您添加的身份驗證提供程序將永遠不會被使用。這是另一個問題。

+0

哦,好的,所以你說通過:public class SecurityConfig擴展WebSecurityConfigurerAdapter,我設置了一個默認的(order(0))過濾器鏈?那麼不擴展WebSecurityConfigurerAdapter可能會有所幫助?你也說過,他們都沒有啓動設備細節過濾器。我認爲「auth.authenticationProvider(customAuthenticationProvider);」正在安裝它,或者至少這是各種文檔似乎對我說的。如果沒有,我該怎麼做? –

+0

是的,沒有擴展'WebSecurityConfigurerAdapter'可能會幫助你,如果你不想要一個默認鏈。不,添加身份驗證提供程序不會安裝篩選器(兩個不同的對象,一起工作,但都必須安裝)。查看'HttpSecurity'中的'addFilter()'方法。也許Spring Security文檔可以幫助你理解基本步驟。 –

+0

是的,我在看這個,不幸的是,春季安全文檔實際上告訴你如何做到這一點。如果我只是添加一個過濾器,那麼我最終會得到兩個認證過濾器,因爲原來的過濾器仍然存在。我曾想過在當前鏈中添加所有過濾器,並減去usernamepasswordauthfilter,但這似乎很費力,可能是錯誤的。這可能是回到xml配置的情況,因爲在該機制下這相對容易。順便說一句,謝謝你的幫助。 –

相關問題