我对身份验证有相当特殊的要求(作为用户名,密码和设备或只是要输入的设备)。这让我得出结论,通常的UsernamePasswordAuthenticationFilter不起作用,所以我设置了自己的过滤器,提供者和令牌,如下所示。首先是提供者:
@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("/");
}
}
}
最后,测试上下文(注意springSecurityFilterChain自动装配)
@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 :(得分:1)
你有3个过滤链(据我们所知)。一个默认值(外部WebConfigurerAdapter
)和2个自定义值(一个具有显式httpBasic()
,另一个具有formLogin()
)。我认为默认值为order = 0,并保护所有内容,因此如果您将请求发送到整个过滤器,那么您将遇到的那个。所以这可能是一个问题。并且没有一个(据我所知)安装您的设备详细信息过滤器,因此您添加的身份验证提供程序将永远不会被使用。那是另一个问题。
答案 1 :(得分:0)
最终,事实证明,如果要覆盖UsernamePasswordAuthenticationFilter
,Provider
和Token
,则需要返回XML配置。似乎 Spring Security 没有正确地将重写的vanilla spring SecurityFilterChain
作为bean发布,所以无论你尝试配置什么,这都是你得到的vanilla版本。
也许当 Spring Security 转到版本4.0.0时,我们可以通过 Java 配置来完成此任务。