Add WebSocket Support
Fixes gh-35
This commit is contained in:
34
samples/websocket/src/main/java/sample/Application.java
Normal file
34
samples/websocket/src/main/java/sample/Application.java
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package sample;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@Configuration
|
||||
@ComponentScan
|
||||
@EnableAutoConfiguration
|
||||
public class Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package sample.config;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
|
||||
|
||||
@Configuration
|
||||
public class DataSourceConfig {
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
|
||||
return builder.setType(EmbeddedDatabaseType.H2).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JedisConnectionFactory connectionFactory() throws Exception {
|
||||
return new JedisConnectionFactory();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package sample.config;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import redis.clients.jedis.Protocol;
|
||||
import redis.embedded.RedisServer;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@Configuration
|
||||
public class EmbeddedRedisConfig {
|
||||
|
||||
@Bean
|
||||
public RedisServerBean redisServer() {
|
||||
return new RedisServerBean();
|
||||
}
|
||||
|
||||
class RedisServerBean implements InitializingBean, DisposableBean {
|
||||
private RedisServer redisServer;
|
||||
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
redisServer = new RedisServer(Protocol.DEFAULT_PORT);
|
||||
redisServer.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
if(redisServer != null) {
|
||||
redisServer.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package sample.config;
|
||||
|
||||
import org.h2.server.web.WebServlet;
|
||||
import org.springframework.boot.context.embedded.ServletRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Initializes the H2 {@link WebServlet} so we can access our in memory database
|
||||
* from the URL "/h2".
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@Configuration
|
||||
public class H2Initializer {
|
||||
|
||||
@Bean
|
||||
public ServletRegistrationBean h2Servlet() {
|
||||
ServletRegistrationBean servletBean = new ServletRegistrationBean();
|
||||
servletBean.addUrlMappings("/h2/*");
|
||||
servletBean.setServlet(new WebServlet());
|
||||
return servletBean;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package sample.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.data.repository.query.SecurityEvaluationContextExtension;
|
||||
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
|
||||
|
||||
@Configuration
|
||||
@EnableWebMvcSecurity
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
@EnableRedisHttpSession
|
||||
public class WebSecurityConfig
|
||||
extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
|
||||
http
|
||||
.authorizeRequests()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.formLogin()
|
||||
.and()
|
||||
.logout()
|
||||
.permitAll();
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void configureGlobal(AuthenticationManagerBuilder auth, UserDetailsService userDetailsService) throws Exception {
|
||||
auth
|
||||
.userDetailsService(userDetailsService)
|
||||
.passwordEncoder(new BCryptPasswordEncoder());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
|
||||
return new SecurityEvaluationContextExtension();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package sample.config;
|
||||
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.messaging.simp.SimpMessageSendingOperations;
|
||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.session.web.socket.config.annotation.AbstractSessionWebSocketMessageBrokerConfigurer;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
|
||||
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
|
||||
|
||||
import sample.data.ActiveWebSocketUserRepository;
|
||||
import sample.websocket.WebSocketConnectHandler;
|
||||
import sample.websocket.WebSocketDisconnectHandler;
|
||||
|
||||
@Configuration
|
||||
@EnableScheduling
|
||||
@EnableWebSocketMessageBroker
|
||||
public class WebSocketConfig<S extends ExpiringSession> extends AbstractSessionWebSocketMessageBrokerConfigurer<S> {
|
||||
|
||||
@Bean
|
||||
public WebSocketConnectHandler<S> webSocketConnectHandler(SimpMessageSendingOperations messagingTemplate, ActiveWebSocketUserRepository repository) {
|
||||
return new WebSocketConnectHandler<S>(messagingTemplate, repository);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureStompEndpoints(StompEndpointRegistry registry) {
|
||||
registry.addEndpoint("/messages")
|
||||
.withSockJS();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureMessageBroker(MessageBrokerRegistry registry) {
|
||||
registry.enableSimpleBroker("/queue/", "/topic/");
|
||||
registry.setApplicationDestinationPrefixes("/app");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WebSocketDisconnectHandler<S> webSocketDisconnectHandler(SimpMessageSendingOperations messagingTemplate, ActiveWebSocketUserRepository repository) {
|
||||
return new WebSocketDisconnectHandler<S>(messagingTemplate, repository);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package sample.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.messaging.simp.SimpMessageType;
|
||||
import org.springframework.security.config.annotation.web.messaging.MessageSecurityMetadataSourceRegistry;
|
||||
import org.springframework.security.config.annotation.web.socket.AbstractSecurityWebSocketMessageBrokerConfigurer;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@Configuration
|
||||
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
|
||||
|
||||
@Override
|
||||
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
|
||||
messages
|
||||
.antMatchers(SimpMessageType.MESSAGE,"/queue/**","/topic/**").denyAll()
|
||||
.antMatchers(SimpMessageType.SUBSCRIBE, "/queue/**/*-user*","/topic/**/*-user*").denyAll()
|
||||
.anyMessage().hasRole("USER");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package sample.data;
|
||||
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
|
||||
@Entity
|
||||
public class ActiveWebSocketUser {
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
private String username;
|
||||
|
||||
private Calendar connectionTime;
|
||||
|
||||
public ActiveWebSocketUser() {}
|
||||
|
||||
public ActiveWebSocketUser(String id, String username, Calendar connectionTime) {
|
||||
super();
|
||||
this.id = id;
|
||||
this.username = username;
|
||||
this.connectionTime = connectionTime;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public Calendar getConnectionTime() {
|
||||
return connectionTime;
|
||||
}
|
||||
|
||||
public void setConnectionTime(Calendar connectionTime) {
|
||||
this.connectionTime = connectionTime;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package sample.data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
public interface ActiveWebSocketUserRepository extends CrudRepository<ActiveWebSocketUser,String> {
|
||||
|
||||
@Query("select DISTINCT(u.username) from ActiveWebSocketUser u where u.username != ?#{principal?.username}")
|
||||
List<String> findAllActiveUsers();
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package sample.data;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
public class InstantMessage {
|
||||
private String to;
|
||||
|
||||
private String from;
|
||||
|
||||
private String message;
|
||||
|
||||
private Calendar created = Calendar.getInstance();
|
||||
|
||||
public String getTo() {
|
||||
return to;
|
||||
}
|
||||
|
||||
public void setTo(String to) {
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
public String getFrom() {
|
||||
return from;
|
||||
}
|
||||
|
||||
public void setFrom(String from) {
|
||||
this.from = from;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public Calendar getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
public void setCreated(Calendar created) {
|
||||
this.created = created;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
112
samples/websocket/src/main/java/sample/data/User.java
Normal file
112
samples/websocket/src/main/java/sample/data/User.java
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package sample.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
|
||||
import org.hibernate.validator.constraints.Email;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
/**
|
||||
* Represents a user in our system.
|
||||
*
|
||||
* <p>
|
||||
* In a real system use {@link PasswordEncoder} to ensure the password is secured
|
||||
* properly. This demonstration does not address this due to time restrictions.
|
||||
* </p>
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@Entity
|
||||
public class User implements Serializable {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@NotEmpty(message = "First name is required.")
|
||||
private String firstName;
|
||||
|
||||
@NotEmpty(message = "Last name is required.")
|
||||
private String lastName;
|
||||
|
||||
@Email(message = "Please provide a valid email address.")
|
||||
@NotEmpty(message = "Email is required.")
|
||||
@Column(unique=true, nullable = false)
|
||||
private String email;
|
||||
|
||||
@NotEmpty(message = "Password is required.")
|
||||
private String password;
|
||||
|
||||
public User() {}
|
||||
|
||||
public User(User user) {
|
||||
this.id = user.id;
|
||||
this.firstName = user.firstName;
|
||||
this.lastName = user.lastName;
|
||||
this.email = user.email;
|
||||
this.password = user.password;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public void setFirstName(String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = 2738859149330833739L;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package sample.data;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
||||
* Allows managing {@link User} instances.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*
|
||||
*/
|
||||
public interface UserRepository extends CrudRepository<User, Long> {
|
||||
|
||||
User findByEmail(String email);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package sample.mvc;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping;
|
||||
import org.springframework.messaging.simp.SimpMessageSendingOperations;
|
||||
import org.springframework.messaging.simp.annotation.SubscribeMapping;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import sample.data.ActiveWebSocketUserRepository;
|
||||
import sample.data.InstantMessage;
|
||||
import sample.data.User;
|
||||
import sample.security.CurrentUser;
|
||||
|
||||
/**
|
||||
* Controller for managing {@link Message} instances.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/")
|
||||
public class MessageController {
|
||||
private SimpMessageSendingOperations messagingTemplate;
|
||||
private ActiveWebSocketUserRepository activeUserRepository;
|
||||
|
||||
@Autowired
|
||||
public MessageController(ActiveWebSocketUserRepository activeUserRepository,SimpMessageSendingOperations messagingTemplate) {
|
||||
this.activeUserRepository = activeUserRepository;
|
||||
this.messagingTemplate = messagingTemplate;
|
||||
}
|
||||
|
||||
@RequestMapping("/")
|
||||
public String im() {
|
||||
return "index";
|
||||
}
|
||||
|
||||
@MessageMapping("/im")
|
||||
public void im(InstantMessage im, @CurrentUser User currentUser) {
|
||||
im.setFrom(currentUser.getEmail());
|
||||
messagingTemplate.convertAndSendToUser(im.getTo(),"/queue/messages",im);
|
||||
messagingTemplate.convertAndSendToUser(im.getFrom(),"/queue/messages",im);
|
||||
}
|
||||
|
||||
@SubscribeMapping("/users")
|
||||
public List<String> subscribeMessages() throws Exception {
|
||||
return activeUserRepository.findAllActiveUsers();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package sample.security;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
/**
|
||||
* Annotate Spring MVC method arguments with this annotation to indicate you
|
||||
* wish to specify the argument with the value of the current
|
||||
* {@link Authentication#getPrincipal()} found on the
|
||||
* {@link SecurityContextHolder}.
|
||||
*
|
||||
* <p>
|
||||
* Creating your own annotation that uses {@link AuthenticationPrincipal} as a
|
||||
* meta annotation creates a layer of indirection between your code and Spring
|
||||
* Security's. For simplicity, you could instead use the
|
||||
* {@link AuthenticationPrincipal} directly.
|
||||
* </p>
|
||||
*
|
||||
* @author Rob Winch
|
||||
*
|
||||
*/
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@AuthenticationPrincipal
|
||||
public @interface CurrentUser {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package sample.security;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import sample.data.User;
|
||||
import sample.data.UserRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*
|
||||
*/
|
||||
@Service
|
||||
public class UserRepositoryUserDetailsService implements UserDetailsService {
|
||||
private final UserRepository userRepository;
|
||||
|
||||
@Autowired
|
||||
public UserRepositoryUserDetailsService(UserRepository userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username)
|
||||
throws UsernameNotFoundException {
|
||||
User user = userRepository.findByEmail(username);
|
||||
if(user == null) {
|
||||
throw new UsernameNotFoundException("Could not find user " + username);
|
||||
}
|
||||
return new CustomUserDetails(user);
|
||||
}
|
||||
|
||||
private final static class CustomUserDetails extends User implements UserDetails {
|
||||
|
||||
private CustomUserDetails(User user) {
|
||||
super(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return AuthorityUtils.createAuthorityList("ROLE_USER");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return getEmail();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = 5639683223516504866L;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package sample.websocket;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
|
||||
import org.springframework.messaging.simp.SimpMessageSendingOperations;
|
||||
import org.springframework.web.socket.messaging.SessionConnectEvent;
|
||||
|
||||
import sample.data.ActiveWebSocketUser;
|
||||
import sample.data.ActiveWebSocketUserRepository;
|
||||
|
||||
public class WebSocketConnectHandler<S> implements ApplicationListener<SessionConnectEvent> {
|
||||
private ActiveWebSocketUserRepository repository;
|
||||
private SimpMessageSendingOperations messagingTemplate;
|
||||
|
||||
public WebSocketConnectHandler(SimpMessageSendingOperations messagingTemplate, ActiveWebSocketUserRepository repository) {
|
||||
super();
|
||||
this.messagingTemplate = messagingTemplate;
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(SessionConnectEvent event) {
|
||||
MessageHeaders headers = event.getMessage().getHeaders();
|
||||
Principal user = SimpMessageHeaderAccessor.getUser(headers);
|
||||
if(user == null) {
|
||||
return;
|
||||
}
|
||||
String id = SimpMessageHeaderAccessor.getSessionId(headers);
|
||||
repository.save(new ActiveWebSocketUser(id, user.getName(), Calendar.getInstance()));
|
||||
messagingTemplate.convertAndSend("/topic/friends/signin", Arrays.asList(user.getName()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package sample.websocket;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.messaging.simp.SimpMessageSendingOperations;
|
||||
import org.springframework.web.socket.messaging.SessionDisconnectEvent;
|
||||
|
||||
import sample.data.ActiveWebSocketUser;
|
||||
import sample.data.ActiveWebSocketUserRepository;
|
||||
|
||||
public class WebSocketDisconnectHandler<S> implements ApplicationListener<SessionDisconnectEvent> {
|
||||
private ActiveWebSocketUserRepository repository;
|
||||
private SimpMessageSendingOperations messagingTemplate;
|
||||
|
||||
public WebSocketDisconnectHandler(SimpMessageSendingOperations messagingTemplate, ActiveWebSocketUserRepository repository) {
|
||||
super();
|
||||
this.messagingTemplate = messagingTemplate;
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(SessionDisconnectEvent event) {
|
||||
String id = event.getSessionId();
|
||||
if(id == null) {
|
||||
return;
|
||||
}
|
||||
ActiveWebSocketUser user = repository.findOne(id);
|
||||
if(user == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
repository.delete(id);
|
||||
messagingTemplate.convertAndSend("/topic/friends/signout", Arrays.asList(user.getUsername()));
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user