我在控制器中使用@InitBinder
:
@InitBinder
public void binder(WebDataBinder binder) {
binder.addValidators(new CompoundValidator(new Validator[] {
new UserAccountValidator()}));
}
@Override
public UserAccountEntity login(@RequestBody UserAccountEntity userAccount,
HttpServletResponse response) throws InvalidCredentialsException, InactiveAccountException {
return userAccountService.authenticateUserAndSetResponsenHeader(
account.getUsername(), account.getPassword(), response);
}
@Override
public UserAccountEntity create(@Valid @RequestBody UserAccountEntity userAccount,
HttpServletResponse response) throws EntityExistsException, InvalidCredentialsException, InactiveAccountException {
String username = userAccount.getUsername();
String password = userAccount.getPassword();
userAccountService.saveIfNotExistsOrExpired(username, password);
return userAccountService.authenticateUserAndSetResponsenHeader(
username, password, response);
}
我希望验证器仅验证登录端点的传入userAccount
,而不验证create方法。现在,它在两种方法上都可以验证。
更新1
CompoundValidator
的代码:
public final class CompoundValidator implements Validator {
private final Validator[] validators;
public CompoundValidator(final Validator[] validators) {
super();
this.validators=validators;
}
@Override
public boolean supports(final Class<?> clazz) {
for (Validator v : validators) {
if (v.supports(clazz)) {
return true;
}
}
return false;
}
@Override
public void validate(Object target, Errors errors) {
for (Validator v : validators) {
if (v.supports(target.getClass())) {
v.validate(target, errors);
}
}
}
}
更新2
一些配置文件:
@EnableWebSecurity
public class AppSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserAccountService userAccountService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// TODO re-enable csrf after dev is done
.csrf()
.disable()
// we must specify ordering for our custom filter, otherwise it
// doesn't work
.addFilterAfter(jwtAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class)
// we don't need Session, as we are using jwt instead. Sessions
// are harder to scale and manage
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
/*
* Ignores the authentication endpoints (signup and login)
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api/authentication/**").and().ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**");
}
/*
* Set user details services and password encoder
*/
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.userDetailsService(userAccountService).passwordEncoder(
passwordEncoder());
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
/*
* By default, spring boot adds custom filters to the filter chain which
* affects all requests this should be disabled.
*/
@Bean
public FilterRegistrationBean<JwtAuthenticationFilter> rolesAuthenticationFilterRegistrationDisable(
JwtAuthenticationFilter filter) {
FilterRegistrationBean<JwtAuthenticationFilter> registration = new FilterRegistrationBean<JwtAuthenticationFilter>(
filter);
registration.setEnabled(false);
return registration;
}
}
-
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/public/")
.resourceChain(false).addResolver(new CustomResourceResolver());
}
private class CustomResourceResolver implements ResourceResolver {
private Resource index = new ClassPathResource("/public/index.html");
private List<String> handledExtensions = Arrays.asList("css", "png",
"svg", "jpg", "jpeg", "gif", "ico", "js");
private List<String> ignoredPaths = Arrays.asList("^api\\/.*$");
@Override
public Resource resolveResource(HttpServletRequest request,
String requestPath, List<? extends Resource> locations,
ResourceResolverChain chain) {
return resolve(requestPath, locations);
}
@Override
public String resolveUrlPath(String resourcePath,
List<? extends Resource> locations, ResourceResolverChain chain) {
Resource resolvedResource = resolve(resourcePath, locations);
if (resolvedResource == null) {
return null;
}
try {
return resolvedResource.getURL().toString();
} catch (IOException e) {
return resolvedResource.getFilename();
}
}
private Resource resolve(String requestPath,
List<? extends Resource> locations) {
if (isIgnored(requestPath)) {
return null;
}
if (isHandled(requestPath)) {
return locations
.stream()
.map(loc -> createRelative(loc, requestPath))
.filter(resource -> resource != null
&& resource.exists()).findFirst()
.orElseGet(null);
}
return index;
}
private Resource createRelative(Resource resource, String relativePath) {
try {
return resource.createRelative(relativePath);
} catch (IOException e) {
return null;
}
}
private boolean isIgnored(String path) {
return !ignoredPaths.stream().noneMatch(
rgx -> Pattern.matches(rgx, path));
}
private boolean isHandled(String path) {
String extension = StringUtils.getFilenameExtension(path);
return handledExtensions.stream().anyMatch(
ext -> ext.equals(extension));
}
}
// TODO remove this after active development of the front-end
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.exposedHeaders("Authorization", "Content-Type")
.allowedMethods("*");
}
}