I need to send a websocket message to the user after the handshake, without waiting the user to send something.
I tried to use afterHandshake in a HandshakeInterceptor, however possibly the user is not identified yet.
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
private final ApplicationContext applicationContext;
WebSocketConfig(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/reply");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setHandshakeHandler(new UserHandshakeHandler())
.addInterceptors(new MessagesHandshakeInterceptor(applicationContext));
}
}
public record MessagesHandshakeInterceptor(
ApplicationContext applicationContext) implements HandshakeInterceptor {
#Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler handler, Map<String, Object> attributes) {
return true;
}
#Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler handler, Exception exception) {
//send the message to the user
applicationContext.getBean(SimpMessagingTemplate.class)
.convertAndSendToUser(user, "/reply", message);
}
}
public class UserHandshakeHandler extends DefaultHandshakeHandler {
#Override
protected Principal determineUser(ServerHttpRequest request, WebSocketHandler handler,
Map<String, Object> attributes) {
return () -> userId;
}
}
Related
Trying to implement oAuth2 Authentication in Springboot Websocket(Without STOMP and SOCKETJS) in before handshake
.
#Configuration
#EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
private final Logger LOG = LoggerFactory.getLogger(getClass());
#Autowired
private NotificationService notificationService;
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(notificationService, "/notification").addInterceptors(new HttpSessionHandshakeInterceptor()
{
#Override
public void afterHandshake(ServerHttpRequest request,
ServerHttpResponse response, WebSocketHandler wsHandler,
#Nullable Exception ex) {
super.afterHandshake(request, response, wsHandler, ex);
}
#Override
public boolean beforeHandshake(ServerHttpRequest request,
ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {
boolean b = super.beforeHandshake(request, response,
wsHandler, attributes) &&
((PreAuthenticatedAuthenticationToken)
request.getPrincipal()).isAuthenticated();
return b;
}
}).setAllowedOrigins("*");
Above code throws null pointer exception
I am trying to use WebRequestInterceptor but i don't know how can i configure it in spring boot, as if I implement WebMvcConfigurer interface it requires a HandlerInterceptor object so i cannot assign my interceptor to it. Any help would be highly appreciated.
Interceptor class:
public class CustomerStateInterceptor implements WebRequestInterceptor {
#Resource(name = "customerStateRequestProcessor")
private CustomerStateRequestProcessor customerStateRequestProcessor;
#Override
public void preHandle(WebRequest webRequest) {
customerStateRequestProcessor.process(webRequest);
}
#Override
public void postHandle(WebRequest webRequest, ModelMap modelMap) {
//unimplemented
}
#Override
public void afterCompletion(WebRequest webRequest, Exception e) {
//unimplemented
}
}
and config class:
#Configuration
public class InterceptorConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CustomerStateInterceptor()); // <-- Error here.
}
}
You supposed to implement HandlerInterceptor from org.springframework.web.servlet package and not WebRequestInterceptor.
Update
You can just wrap with WebRequestHandlerInterceptorAdapter:
#Configuration
public class InterceptorConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(
new WebRequestHandlerInterceptorAdapter(
new CustomerStateInterceptor()));
}
}
Add filter class to your package and please try the code below -
public class RequestValidateFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
try {
request = new RequestWrapper(httpServletRequest);
chain.doFilter(request, response);
} catch (Exception e) {
throw new ServletException();
}
}
}
FilterClass :
#Configuration
public class CustomWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.addFilterBefore(requestValidateFilter(), BasicAuthenticationFilter.class);
http.authorizeRequests().antMatchers("/projectname/**").authenticated();
http.addFilterAfter(responseValidateFilter(), BasicAuthenticationFilter.class);
}
private RequestValidateFilter requestValidateFilter() {
return new RequestValidateFilter();
}
private ReponseValidateFilter responseValidateFilter() {
return new ReponseValidateFilter();
}
}
I'm using #InitBinder in a controller:
#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);
}
I want the validator to only validate the incoming userAccount for the login endpoint, and not the create method. Right now, it validates on both methods.
Update 1
Code for the 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);
}
}
}
}
Update 2
Some config files:
#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("*");
}
}
I already found questions with answers but they don't help me.
I have a web servlet project where I use a Spring Controller (4.2.5) and Spring Security (4.0.2). I don't use Spring Boot.
My project works fine.
But now my task is this:
Make #RequestMapping(value={"auth/**"} configurable (replace "auth/**" with ${dm.filterPattern})
Problem: in #RequestMapping ${dm.filterPattern} isn't resolved, although #PropertySource is processed.
This is the entry dm.filterPattern in dmConfig.properties:
dm.filterPattern=/auth/*
Here is some essential code, with all Spring annotations.
Controller:
The output of the method init() shows me that #PropertySource is processed correctly. env.getProperty("...") returns correct values.
#Controller
#PropertySource("classpath:/dmConfig.properties")
#RequestMapping(value ={ "${dm.filterPattern}"})
public class DmProxyController implements ApplicationContextAware
{
private Environment env;
#Autowired
public DmProxyController(Environment env)
{
this.env = env;
}
#RequestMapping(path={"${dm.filterPattern}"} ,method = RequestMethod.POST)
protected void doPost(HttpServletRequest customerRequest, HttpServletResponse response)
throws ServletException, IOException, DmException
{
// code for POST request
}
#RequestMapping(path={"${dm.filterPattern}"} ,method = RequestMethod.GET)
protected void doGet(HttpServletRequest customerRequest, HttpServletResponse response)
throws ServletException, IOException, DmException
{
// code for GET request
}
#PostConstruct
public void init() throws ServletException
{
RequestMappingHandlerMapping requestMapping=
(RequestMappingHandlerMapping) appContext.getBean("requestMappingHandlerMapping");
Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMapping.getHandlerMethods();
logger.debug("RequestMapping via dm.filterPattern: {}",
env.getProperty("dm.filterPattern"));
logger.debug("Handler Methods: {}", handlerMethods.size());
for (RequestMappingInfo mapInfo : handlerMethods.keySet())
{
logger.debug(" Mappinginfo: {} --> {}", mapInfo, handlerMethods.get(mapInfo));
}
}
}
Class with bean definitions
#Configuration
#PropertySource("classpath:/dmConfig.properties")
#ComponentScan(basePackages = "com.dm.filter, com.dm.controller")
#EnableTransactionManagement(mode = AdviceMode.PROXY, proxyTargetClass = false)
#Import({DmSecurityConfigurer.class, DmWebConfigurer.class})
public class DmRoot
{
}
DispatcherServletInitializer
public class DmDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer
{
#Override
protected Class<?>[] getRootConfigClasses()
{ return new Class[]{DmRoot.class}; }
#Override
protected Class<?>[] getServletConfigClasses()
{ return null; }
#Override
protected String[] getServletMappings()
{ return new String[]{"/"}; }
#Override
protected String getServletName()
{ return "dmDispatcherServlet"; }
#Override
protected void customizeRegistration(ServletRegistration.Dynamic registration)
{
super.customizeRegistration(registration);
registration.setLoadOnStartup(1);
}
}
WebConfigurer
public class DmWebConfigurer extends WebMvcConfigurerAdapter
{
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry)
{
super.addResourceHandlers(registry);
registry.addResourceHandler("/index.html").addResourceLocations("/");
registry.setOrder(Integer.MAX_VALUE-5);
}
}
SecurityWebApplicationInitializer
public class DmSecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer
{
public DmSecurityWebApplicationInitializer()
{
// some logging
}
#Override
protected void beforeSpringSecurityFilterChain(ServletContext servletContext)
{ // adding own filters }
#Override
protected void afterSpringSecurityFilterChain(ServletContext servletContext)
{ // adding own filters }
}
SecurityConfigurer
#EnableWebMvc
#EnableWebSecurity
#PropertySource("classpath:dmConfig.properties")
public class DmSecurityConfigurer extends WebSecurityConfigurerAdapter
{
private static Logger logger = LogManager.getLogger(DmSecurityConfigurer.class.getName());
#Autowired
private Environment env;
#Autowired
private UserDetailsService dmUserDetailsService;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception
{
String urlPattern = env.getProperty("dm.springSecurityPattern");
String realmName = env.getProperty("dm.springSecurityRealm");
httpSecurity.httpBasic().realmName(realmName)
.and().userDetailsService(dmUserDetailsService)
.authorizeRequests()
.antMatchers(urlPattern).authenticated()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable();
}
}
There is a possibility that PropertySourcesPlaceholderConfigurer is being initialised later in the spring context than your controller and hence the values are not resolved. try adding explicit bean definition for PropertySourcesPlaceholderConfigurer in one of the root configuration file as below;
#PropertySource("classpath:/dmConfig.properties")
public class DmWebConfigurer extends WebMvcConfigurerAdapter
{
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry)
{
super.addResourceHandlers(registry);
registry.addResourceHandler("/index.html").addResourceLocations("/");
registry.setOrder(Integer.MAX_VALUE-5);
}
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
The reason you can see the values properly in your init() method is because it is called after all the beans are initialised including PropertySourcesPlaceholderConfigurer.
This question already has answers here:
Why is my Spring #Autowired field null?
(21 answers)
Closed 7 years ago.
I want to make a check in database when i receive a request. So i did a Interceptor like below,
CustomInterceptor.java
#Component
public class CustomInterceptor extends HandlerInterceptorAdapter {
#Autowired
private DatabaseService databaseService;
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//Set Request Attribute(TODO)
LogService.info(this.getClass().getName(), "New Request URI is:" + request.getRequestURI());
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
String authToken = request.getHeader("AuthToken");
boolean isValidRequest = databaseService.checkIfTokenIsValid(authToken);
}
}
Application.class:
#SpringBootApplication
public class Application extends SpringBootServletInitializer {
// protected Properties props = new Properties();
//
// public Application() {
// props.setProperty("error.path", "/error");
//// props.setProperty("error.whitelabel.enabled", "false");
//// props.setProperty("org.springframework.web", "DEBUG");
// }
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
// application.properties(props);
return application.sources(Application.class);
}
#Override
public void onStartup(final ServletContext servletContext) throws ServletException {
LogService.info(Application.class.getName(), "Loading Service...");
super.onStartup(servletContext);
LogService.info(Application.class.getName(), "Service Started");
}
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Application.class, args);
}
DatabasService.java
#Service
public class DatabaseService {
#Autowired
private ApplicationProperties properties;
private final JdbcTemplate defaultJdbcTemplate;
#Autowired
public DatabaseService(
#Qualifier("dataSource") DataSource dataSource) {
defaultJdbcTemplate = new JdbcTemplate(dataSource);
}
public boolean checkIfTokenIsValid() {
//Perform Check
}
}
CustomWebConfiguration.java
#Configuration
#EnableWebMvc
public class CustomWebConfiguration extends WebMvcConfigurerAdapter {
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
"classpath:/META-INF/resources/", "classpath:/resources/",
"classpath:/static/", "classpath:/public/"};
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!registry.hasMappingForPattern("/**")) {
registry.addResourceHandler("/**").addResourceLocations(
CLASSPATH_RESOURCE_LOCATIONS);
}
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CustomInterceptor())
.addPathPatterns("/**");
}
}
But i get NullPointer At: boolean isValidRequest = databaseService.checkIfTokenIsValid(authToken);
What is wrong here, why cannot spring Autowire the Databaseservice in Interceptor?
Note: Autowire works fine everywhere else, but not in the interceptor.
Solution (Thanks to M. Deinum)
Change the CustomWebConfiguration.java like below;
#Configuration
#EnableWebMvc
public class CustomWebConfiguration extends WebMvcConfigurerAdapter {
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
"classpath:/META-INF/resources/", "classpath:/resources/",
"classpath:/static/", "classpath:/public/"};
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!registry.hasMappingForPattern("/**")) {
registry.addResourceHandler("/**").addResourceLocations(
CLASSPATH_RESOURCE_LOCATIONS);
}
}
#Bean
public CustomInterceptor customInterceptor() {
return new CustomInterceptor();
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(customInterceptor())
.addPathPatterns("/**");
}
}
Spring will only autowire beans it knows about, you are creating the instance yourself outside the control of Spring.
Either inject a CustomInterceptor into your configuration class or add a #Bean method to make it a Spring managed instance. Then use that instance to add it to the list of interceptors.
It is OK to use #SpringBootApplication on your main class, however don't you miss #EnableAutoConfiguration and #ComponentScan on the same class to tell Spring Boot to look for other components and services automatically?