我對身份驗證(作爲用戶名,密碼和設備或只是設備進入)有比較特殊的要求。這使我得出結論:通常的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;
覆蓋嗎?有誰知道爲什麼?
我需要重新實現類似的東西:@Bean public FilterChainProxy springSecurityFilterChain()拋出異常{//現在所做的所有東西減去用戶密碼並加上我的新提供者} –