springboot+springsecurity+mybatis+websocket的使用小Demo
这个项目目并不实现特定的功能,仅仅是入门使用这些技术,知道如何配置信息。整体目录结构说明:目录存储内容如文件夹名称所示项目依赖管理使用Maven。
简介
这个项目目并不实现特定的功能,仅仅是入门使用这些技术,知道如何配置信息。
整体目录结构
说明:目录存储内容如文件夹名称所示
项目依赖管理使用Maven
springsecurity
首先明白springsecurity给应用程序提供了什么帮助,简单理解两个方面
认证:即,应用程序面向哪些人开放,得通过用户认证
授权:即,并不是每个认证用户都可以访问所有的资源或接口,需要进行权限的限定。
使用步骤
引入依赖jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
这一步之后,你正常启动就可以使用springsecurity提供的认证服务了,默认账号为user 密码在控制台有输出。
到这一步,所有的服务都还是默认的,登录界面默认、认证账户信息默认所以我们需要修改的就是这些内容。
认证账户修改:
新建配置类:
//新建配置类,并继承WebSecurityConfigurerAdapter(配置的主要类)
public class webSecurityConfig extends WebSecurityConfigurerAdapter
@Autowired
UerService uerService;
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
//重写这个方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(uerService);
}
在这个重写方法中,我们添加了一个我们自定义的认证信息源uerService,这个类是通过前端传过来的用户名来查询出来的数据库中的用户信息,认证逻辑就是比较一下前端传过来的密码和这个查询出来的密码是否相同,当然为了不能明码存储密码,所以,在配置文件中加入PasswordEncoder,使用BCryptPasswordEncoder编解码方式即可。
接下来看一下如何实现UerService
@Service
public class UerService implements UserDetailsService {
@Autowired
UserMapper userMapper;
public List<User> getUserList() {
//设置当前的页码和每页的大小
PageHelper.startPage(1, 2);
//执行下一句查询语句,对下一个查询语句做分页
List<User> list = userMapper.getUserList();
//封装成PageInfo类对象
PageInfo<User> pageInfo = new PageInfo<>(list);
return list;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//查询用户
User user = userMapper.getUserByName(username);
if (user == null) {
throw new UsernameNotFoundException("User " + username + " Not Found!");
}
org.springframework.security.core.userdetails.User user1 = new org.springframework.security.core.userdetails.
User(username, user.getPassWord(), getAuthoritiesList(user));
return user1;
}
private Collection<GrantedAuthority> getAuthoritiesList(User userInfo) {
Collection<GrantedAuthority> newAuthoritiesList = new ArrayList<GrantedAuthority>();
int roleID = userInfo.getRole().getRoleID();
if (roleID == 1) {
newAuthoritiesList.add(new SimpleGrantedAuthority("ROLE_ADMIN"));//实际ROLE
} else if (roleID == 2) {
newAuthoritiesList.add(new SimpleGrantedAuthority("ROLE_USER"));
} else {
newAuthoritiesList.add(new SimpleGrantedAuthority("ROLE_MAN"));
}
return newAuthoritiesList;
}
继承接口UserDetailsService,并实现方法loadUserByUsername,这个方法中的逻辑就是Mapper层去查询用户,并封装到user中,后面需要获取用户权限信息即getAuthoritiesList()方法,将查询出来的用户信息和权限信息封装到一个UserDetails对象并返回,安全框架拿到这个对象就可以做认证和权限了。
接下来,改变一些默认登录页。
我们往配置类中加入这些信息
@Override
protected void configure(HttpSecurity http) throws Exception {
//请求认证
http.authorizeRequests()
.antMatchers("/login","/static/**").permitAll()
// .anyRequest().authenticated()
.anyRequest().permitAll()
.and()
//登录表单,这里springsecurity会自动监听来自这个页面提交的登录信息
.formLogin().loginPage("/login")
.defaultSuccessUrl("/success")
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(customLoginOutSuccessHandler)
.permitAll()
.and()
//session管理
.sessionManagement()
.invalidSessionUrl("/login")
.and()
.csrf().disable()
.exceptionHandling().accessDeniedHandler(customAccessDeniedHandler);
}
这样在写一个login.html就可以了
mybatis
使用之前先了解一下mybatis
- 对JDBC代码做了封装,让开发者只需关注SQL语句本身,无需处理加载驱动,创建连接等繁琐过程
- 灵活定制和控制SQL,同时避免了SQL的注入
- 和spring框架集成,实现更高层次的数据访问抽象和业务逻辑分离
所以,这个框架让我们能够更加简便地操作数据库,并提供了灵活的订制SQL的功能
导入jar包
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
编写mapper层
@Mapper
public interface UserMapper {
List<User> getUserList();
User getUserByName(String username);
}
一个mapper对应于一个实现,实现的文件在xml文件里,所以这个需要将这个xml文件所在位置标识出来
#指定Mybatis的Mapper文件
mybatis.mapper-locations=classpath:mappers/*xml
#指定Mybatis的实体目录
mybatis.type-aliases-package=com.song.springdemo.entity
#配置是用来指定MyBatis使用哪种日志实现类来打印SQL语句和其他日志信息的
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
编写xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.song.springdemo.Dao.UserMapper">
<resultMap id="UserMapper" type="com.song.springdemo.Entity.User">
<id property="userID" javaType="Integer" column="user_id" jdbcType="INTEGER"/>
<result property="userName" javaType="String" column="user_name" jdbcType="VARCHAR"/>
<result property="passWord" javaType="String" column="password" jdbcType="VARCHAR"/>
<result property="Email" javaType="String" column="email" jdbcType="VARCHAR"/>
<result property="address" javaType="String" column="Address" jdbcType="VARCHAR"/>
<result property="createTime" javaType="Date" column="create_time" jdbcType="TIMESTAMP"/>
<result property="updateTime" javaType="Date" column="update_time" jdbcType="TIMESTAMP"/>
<association property="role" column="role_id" resultMap="com.song.springdemo.Dao.RoleMapper.role"/>
</resultMap>
<select id="getUserList" resultMap="UserMapper">
SELECT *
FROM db_user,db_role
WHERE db_user.role_id=db_role.role_id
</select>
<select id="getUserByName" resultMap="UserMapper" parameterType="String">
SELECT *
FROM db_user,db_role
WHERE db_user.role_id=db_role.role_id
AND user_name=#{username}
</select>
</mapper>
这样在service层就可以调用mapper层的接口,然后具体的实现在配置的xml文件中
websocket
首先了解一下websocket干了什么
不同于http这种协议的请求响应式的方式,websocket是基于http的全双工通信方式,也就是服务器可以主动给客户端发信息了!
导入jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
配置文件
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
// Enables a simple in-memory broker
registry.enableSimpleBroker("/topic");
}
}
这个配置文件说明了两个问题,客户端发送给前端的信息以“/app”为前缀,服务器给客户端的信息以“/topic”为前缀
看一下控制层的处理逻辑
@MessageMapping("/chat.sendMessage")
public void sendMessage(@Payload ChatMessage chatMessage) {
try {
redisTemplate.convertAndSend(msgToAll, JsonUtil.parseObjToJson(chatMessage));
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
}
这里使用了redis做消息队列,这里是将前端的信息序列化并发送到msgToAll频道,而没有直接去处理,那订阅了这个频道的客户端就需要处理逻辑了,看看服务端如何处理
@Component
public class WebSocketEventListener {
private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketEventListener.class);
@Value("${server.port}")
private String serverPort;
@Value("${redis.set.onlineUsers}")
private String onlineUsers;
@Value("${redis.channel.userStatus}")
private String userStatus;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@EventListener
public void handleWebSocketConnectListener(SessionConnectedEvent event) {
InetAddress localHost;
try {
localHost = Inet4Address.getLocalHost();
LOGGER.info("Received a new web socket connection from:" + localHost.getHostAddress() + ":" + serverPort);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
}
@EventListener
public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) {
StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage());
String username = (String) headerAccessor.getSessionAttributes().get("username");
if(username != null) {
LOGGER.info("User Disconnected : " + username);
ChatMessage chatMessage = new ChatMessage();
chatMessage.setType(ChatMessage.MessageType.LEAVE);
chatMessage.setSender(username);
try {
redisTemplate.opsForSet().remove(onlineUsers, username);
redisTemplate.convertAndSend(userStatus, JsonUtil.parseObjToJson(chatMessage));
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
}
}
}
订阅频道
@Component
public class RedisListenerBean {
private static final Logger LOGGER = LoggerFactory.getLogger(RedisListenerBean.class);
@Value("${server.port}")
private String serverPort;
@Value("${redis.channel.msgToAll}")
private String msgToAll;
@Value("${redis.channel.userStatus}")
private String userStatus;
/**
* redis消息监听器容器
* 可以添加多个监听不同话题的redis监听器,只需要把消息监听器和相应的消息订阅处理器绑定,该消息监听器
* 通过反射技术调用消息订阅处理器的相关方法进行一些业务处理
* @param connectionFactory
* @param listenerAdapter
* @return
*/
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
// 监听msgToAll
container.addMessageListener(listenerAdapter, new PatternTopic(msgToAll));
container.addMessageListener(listenerAdapter, new PatternTopic(userStatus));
LOGGER.info("Subscribed Redis channel: " + msgToAll);
LOGGER.info("Subscribed Redis channel:"+userStatus);
return container;
}
}
事件监听处理
@Component
public class RedisListenerHandle extends MessageListenerAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(RedisListenerHandle.class);
@Value("${redis.channel.msgToAll}")
private String msgToAll;
@Value("${redis.channel.userStatus}")
private String userStatus;
@Value("${server.port}")
private String serverPort;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private ChatService chatService;
/**
* 收到监听消息,如何去接收到Redis信息处理的问题
* @param message
* @param bytes
*/
@Override
public void onMessage(Message message, byte[] bytes) {
byte[] body = message.getBody();
byte[] channel = message.getChannel();
String rawMsg;
String topic;
try {
rawMsg = redisTemplate.getStringSerializer().deserialize(body);
topic = redisTemplate.getStringSerializer().deserialize(channel);
LOGGER.info("Received raw message from topic:" + topic + ", raw message content:" + rawMsg);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
return;
}
if (msgToAll.equals(topic)) {
LOGGER.info("Send message to all users:" + rawMsg);
ChatMessage chatMessage = JsonUtil.parseJsonToObj(rawMsg, ChatMessage.class);
//服务器拿到从频道的信息之后,发送到前端的回调函数
if (chatMessage != null) {
chatService.sendMsg(chatMessage);
}
} else if (userStatus.equals(topic)) {
ChatMessage chatMessage = JsonUtil.parseJsonToObj(rawMsg, ChatMessage.class);
if (chatMessage != null) {
chatService.alertUserStatus(chatMessage);
}
}else {
LOGGER.warn("No further operation with this topic!");
}
}
}

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐
所有评论(0)