SSM+Shiro+原生的WebSocket集成的即时聊天系统

一、项目简介

这是一个即时通讯的Maven项目,采用了 Spring+SpringMVC+Mybatis+Shiro+原生的WebSocket 技术,这个项目主要是为了熟悉框架之间的整合,熟悉WebSocket的使用。SSM是目前使用较的框架,Shiro是一个安全管理框架,主要用来用户的认证登录和授权,WebSocket用来做消息推送。这个项目主要功能有登录、注册、管理员群发消息和单聊。

二、环境搭建

首先创建Maven项目,然后在pom.xml文件中添加项目所需的依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.webchat</groupId>
    <artifactId>MineChat</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <!--spring、springmvc相关 start-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.1.4.RELEASE</version>
        </dependency>
        <!--spring、springmvc相关 end-->
        <!--AOP相关 start-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.2</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.2</version>
        </dependency>
        <!--AOP相关 end-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.39</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.1.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-all</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.6</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.2</version>
        </dependency>
        <!--日志相关 start-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
        </dependency>
        <!--日志相关 end-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.8</version>
        </dependency>
        <!--jsp相关 start-->
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>taglibs</groupId>
            <artifactId>standard</artifactId>
            <version>1.1.2</version>
        </dependency>
        <!--jsp相关 end-->
        <dependency>
            <groupId>javax.websocket</groupId>
            <artifactId>javax.websocket-api</artifactId>
            <version>1.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-websocket</artifactId>
            <version>4.3.13.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.6.1</version>
        </dependency>
    </dependencies>
    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>7</source>
                    <target>7</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

applicationContext.xmlspring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--配置扫描包-->
    <context:component-scan base-package="com.webchat.service" use-default-filters="true">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    <context:property-placeholder location="classpath:db.properties"/>
    <bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
        <property name="driverClassName" value="${db.driver}"/>
        <property name="username" value="${db.username}"/>
        <property name="password" value="${db.password}"/>
        <property name="url" value="${db.url}"/>
    </bean>
    <!--配置事务管理器-->
    <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <tx:advice transaction-manager="transactionManager" id="txAdvice">
        <tx:attributes>
            <!-- 传播行为 -->
            <tx:method name="save*" propagation="REQUIRED"/>
            <tx:method name="insert*" propagation="REQUIRED"/>
            <tx:method name="add*" propagation="REQUIRED"/>
            <tx:method name="create*" propagation="REQUIRED"/>
            <tx:method name="delete*" propagation="REQUIRED"/>
            <tx:method name="update*" propagation="REQUIRED"/>
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
            <tx:method name="select*" propagation="SUPPORTS" read-only="true"/>
            <tx:method name="get*" propagation="SUPPORTS" read-only="true"/>
        </tx:attributes>
    </tx:advice>
    <aop:config>
        <aop:pointcut id="pc" expression="execution(* com.webchat.service.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>
    </aop:config>
    <!--配置Mybatis-->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations">
            <list>
                <value>classpath*:com/webchat/mapper/*.xml</value>
            </list>
        </property>
        <!--配置实体类别名-->
        <property name="typeAliasesPackage" value="com.webchat.entity"/>
        <!--配置类型转换器-->
        <property name="typeHandlersPackage" value="com.webchat.handler"/>
    </bean>
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.webchat.mapper"/>
        <property name="sqlSessionFactoryBeanName" value="sessionFactoryBean"/>
    </bean>
    <!--shiro配置-->
    <bean class="com.webchat.realm.UserRealm" id="userRealm">
        <property name="credentialsMatcher">
            <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                <property name="hashAlgorithmName" value="SHA-512"/>
                <property name="hashIterations" value="1024"/>
            </bean>
        </property>
    </bean>
    <bean class="org.apache.shiro.web.mgt.DefaultWebSecurityManager" id="securityManager">
        <property name="realm" ref="userRealm"/>
    </bean>
    <bean class="org.apache.shiro.spring.web.ShiroFilterFactoryBean" id="shiroFilter">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/login"/>
        <property name="filterChainDefinitions">
            <value>
                <!--
                    anon 表示无需认证(登录)就可访问
                    authc 表示需认证(登录)才可访问
                    /**=authc:表示除了在它之上的请求都需要拦截,放在最后
                -->
                /logout=logout
                /doLogin=anon
                /favicon.ico=anon
                /doRegister=anon
                /register=anon
                /resources/**=anon
                /**=authc
            </value>
        </property>
    </bean>
</beans>

spring-servlet.xmlspringmvc配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!--配置扫描包-->
    <context:component-scan base-package="com.webchat.controller" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    <mvc:annotation-driven/>
    
    <!--过滤静态文件-->
    <mvc:resources mapping="/**" location="/"/>
    <!--配置视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <!--解决POST请求乱码-->
    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceRequestEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>forceResponseEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!--配置shiro过滤器-->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

以上就是项目中一些主要的配置文件。

下面首先来看看WebSocket的代码。

三、WebSocket

Tomcat7开始支持WebSocketWebSocket程序分为两部分:浏览器端和服务端,在Tomcat7及以上的版本中,lib目录下出现了websocket-api.jar 这个jar包,提供了WebSocket的程序开发接口,在Tomcat的目录\webapps\examples下有WebSocket相关的例子,下面就来看看看WebSocket的创建以及方法。
1、浏览器端:

<script type="application/javascript">
    var ws = null; // 定义WebSocket的全局变量
    var target = 'ws://localhost:8080/test'; // 连接地址
    // 判断浏览器是否支持WebSocket,目前的浏览器基本都支持,除非是版本很低的浏览器
    if ('WebSocket' in window) {
        ws = new WebSocket(target);
    } else if ('MozWebSocket' in window) {
        ws = new MozWebSocket(target);
    } else {
        alert('WebSocket is not supported by this browser.');
        return;
    }
    ws.onopen = function (event) {
        // 连接成功时触发
    };
    ws.onmessage = function (event) {
        // 收到消息时触发
    };
    ws.onclose = function (event) {
        // 连接关闭时触发
    };
    ws.onerror = function (event) {
        // 连接出错时触发
    };
</script>

2、服务端

// 对应的就是浏览器中的 ws://localhost:8080/test,浏览器连接的就是这个/test这个地址
@ServerEndpoint("/test")
public class MyWebSocket {
    /**
     * 连接时触发
     *
     * @param session
     */
    @OnOpen
    public void open(Session session) {
        // session 可以表示当前连接的用户
        try {
            session.getBasicRemote().sendText("连接成功");
            System.out.println("连接成功");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 收到消息时触发
     *
     * @param session
     * @param msg     收到的消息
     */
    @OnMessage
    public void message(Session session, String msg) {
        System.out.println("收到浏览器的消息:" + msg);
        try {
            session.getBasicRemote().sendText("服务端返回的消息");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 连接关闭时触发
     *
     * @param session
     */
    public void close(Session session) {
        try {
            session.getBasicRemote().sendText("连接关闭");
            System.out.println("连接关闭");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 1、@ServerEndPoint注解:表示该类是一个WebSocket的连接点
  • 2、@OnOpen注解:表示客户端连接上服务端时触发
  • 3、@OnMessage注解:表示收到客户端消息时触发
  • 4、@OnClose注解:表示连接关闭时触发

以上是对WebSocket的基本介绍,下面展示在这个项目中WebSocket的用法。
1、服务端

// 为了注入service需要添加configurator = SpringConfigurator.class
@ServerEndpoint(value = "/ws/webchat/{userid}", configurator = SpringConfigurator.class)
public class WebChat {
    @Autowired
    private UserService userService;
    @Autowired
    private MessageService messageService;
    // 统计在线人数
    private static final ConcurrentMap<Integer, Session> sessionMap = new ConcurrentHashMap<Integer, Session>();
    private static final Gson gson = new Gson();
    // 当前用户
    private User user;
    // 用户列表
    private static List<User> users;
    @OnOpen
    public void open(Session session, @PathParam(value = "userid") int userid) {
        sessionMap.put(userid, session);
        userService.updateUserById(userid, 1); // 把该用户状态设置为上线。
        users = userService.allUser(); // 获取所有用户
        this.user = userService.getUserById(userid);
        if (this.user != null) {
            this.user.setPassword(null);
        }
        WsMessage wsMessage = new WsMessage();
        wsMessage.setUsers(this.users);
        wsMessage.setUserid(userid);
        broadcast(sessionMap, gson.toJson(wsMessage));
    }
    @OnMessage
    public void message(Session session, String jsonMessage) {
        WsMessage message = gson.fromJson(jsonMessage, WsMessage.class);
        if (message.getType() == 1) { // 消息类型为1时表示单聊
            message.setTime(new Timestamp(new Date().getTime()));
            messageService.saveMsg(message);
            Session s = sessionMap.get(message.getTo_id()); // 需要接收消息的用户
            message.setUsers(this.users);
            send(s, gson.toJson(message));
        } else {
            // 否则表示管理员发送系统消息
            message.setTime(new Timestamp(new Date().getTime()));
            messageService.saveMsg(message);
            message.setUsers(this.users);
            broadcast(sessionMap, gson.toJson(message));// 给用户发送系统消息
        }
    }
    @OnClose
    public void close(Session session) {
        this.sessionMap.remove(this.user.getId(), session);
        userService.updateUserById(this.user.getId(), 0);// 把该用户状态设置为离线。
        WsMessage wsMessage = new WsMessage();
        wsMessage.setTip(this.user.getNickname() + "退出聊天室");
        this.users = userService.allUser();
        wsMessage.setUsers(this.users);
        broadcast(sessionMap, gson.toJson(wsMessage)); // 通知所有人该用户已离线
    }
    public void broadcast(ConcurrentMap<Integer, Session> sessions, String msg) {
        Set<Map.Entry<Integer, Session>> entries = sessions.entrySet();
        for (Map.Entry<Integer, Session> me : entries) {
            try {
                me.getValue().getBasicRemote().sendText(msg);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public void send(Session session, String msg) {
        try {
            session.getBasicRemote().sendText(msg);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2、客户端
客户端使用的是jsp+layui框架,当用户登录系统时,首先展示的是用户列表,可以查看用户是否在线或离线,点击用户列表中的某一项,查看两者的聊天记录。点击系统消息选项卡,然后可以查看系统发的消息,只有管理员登录才能发送系统消息。下面展示部分代码

<script>
    var currentUser = JSON.parse('${currentUser}')
    var userid = currentUser.id;
    var currentUserNickname = currentUser.nickname;
    var ws;
    $(function () {
       
        if ('WebSocket' in window) {
            ws = new WebSocket('ws://localhost:8080/ws/webchat/' + userid);
        } else if ('MozWebSocket' in window) {
            ws = new MozWebSocket(target);
        } else {
            alert('WebSocket is not supported by this browser.');
            return;
        }
        ws.onopen = function (ev) {
        }
        ws.onmessage = function (ev) {
            var data = JSON.parse(ev.data)
            friendList(data);
            if (data.type == 1) {
                $('#' + data.from_id + "output").empty();
                $('#' + data.to_id + "output").empty();
                var msgList = getMsgList(data.from_id);
                msgList.forEach(function (e) {
                    showReceiveMessage(e.from_id, e.to_id, e.msg, e.time, e.type)
                })
            }
        }
        ws.onclose = function (ev) {
            var data = JSON.parse(ev.data)
            layer.msg(data.tip)
        }
    })
    //  打开单人聊天窗口
    function openChatWindow(toId, nickname) {
        var chatWith = '<div style="width: 100%;height: 100%;overflow: hidden">'
            + '<div  class="triangle" > <ul id="' + toId + 'output"></ul>'
            + '</div>'
            +
            '<div style="width: 100%;height: 35px"><input id="' + toId +
            'messageText" type="text" style="width: 87%;height: 100%"> <button class="layui-btn" οnclick="sendMsg(' + toId +
            ')">发送</button></div></div>';
        layer.open({
            type: 1,
            title: [nickname, 'font-family:Microsoft YaHei;font-size: 24px;height: 80px;'],
            content: chatWith,
            area: ['600px', '600px'],
            maxmin: true,
            shade: 0,
            id: toId,
            resize: true,
            zIndex: layer.zIndex,
            success: function (layero) {
            }
        });
        var msgList = getMsgList(toId);
        msgList.forEach(function (e) {
            showReceiveMessage(e.from_id, e.to_id, e.msg, e.time, e.type)
        })
    }
    // 将消息显示在聊天列表上
    function showReceiveMessage(from_id, to_id, msg, time, type) {
        time = getDateFull(time)
        if (from_id == userid) {
            var rightHtml = '<li class="textRight"><span>' + msg + '</span><img class="head-icon" src="resources/images/icon.jpg" alt=""></li>';
            $('#' + to_id + "output").append(rightHtml);
        } else {
            var leftHtml = '<li class="textLeft"><img class="head-icon" src="resources/images/icon.jpg" alt=""><span>' + msg +
                '</span></li>'
            $('#' + from_id + "output").append(leftHtml);
        }
        var scrollDiv = $('.triangle')
        scrollDiv.scrollTop(scrollDiv[0].scrollHeight);
    }
   
    // 发送消息
    function sendMsg(toId) {
        var msg = $("#" + toId + "messageText").val();
        var message = {
            from_id: userid,
            to_id: toId,
            msg: msg,
            type: 1
        }
        ws.send(JSON.stringify(message));
        uploadMineMsgList(toId, msg);
        $("#" + toId + "messageText").val("");
    }
    // 发消息时,本人的消息实时更新在消息列表上
    function uploadMineMsgList(toId, msg) {
        var rightHtml = '<li class="textRight"><span>' + msg + '</span><img class="head-icon" src="resources/images/icon.jpg" alt=""></li>';
        $('#' + toId + "output").append(rightHtml);
        var scrollDiv = $('.triangle')
        scrollDiv.scrollTop(scrollDiv[0].scrollHeight);
    }
    // 好友列表
    function friendList(data) {
        $('#friend').empty();
        $('#header-panel').empty();
        var online;
        var color;
        data.users.forEach(function (e) {
            if (e.online == 0) {
                online = '离线';
                color = '#666666';
            } else if (e.online == 1) {
                online = '在线';
                color = '#0000FF';
            }
            if (e.id != userid) {
                $('#friend').append('<li οnclick="openChatWindow(' + e.id + ',\'' + e.nickname +
                    '\')" style="border-bottom: 1px solid #D6D6D6; cursor: pointer;"><div class="friend-div">' +
                    '<img class="head-icon" src="resources/images/icon.jpg">' +
                    '<span style="margin-left: 10px">' + e.nickname + '</span><span style="margin-left: 10px;color: ' + color + '">' +
                    online +
                    '</span>' +
                    '</div></li>');
            } else {
                $('#header-panel').append('<div class="friend-div"style="padding-left: 5px;">\n' +
                    '            <img class="head-icon" src="resources/images/icon.jpg" alt="">\n' +
                    '            <span style="margin-left: 10px">' + e.nickname + '</span>\n' +
                    '        </div>');
            }
        })
    }
    // 获取消息记录
    function getMsgList(toId) {
        var allMessages = null;
        $.ajax({
            async: false, //设置同步
            url: 'getMsgList',
            data: {'from_id': userid, "to_id": toId},
            type: 'POST',
            success: function (res) {
                allMessages = res.obj;
            }
        })
        return allMessages;
    }
    function openSendSysMsgWindow() {
        var chatWith = '<div style="width: 100%;height: 100%;overflow: hidden">' +
            '<div style="width: 100%;height: 35px">' +
            '<input id="sysMessageText" type="text" style="width: 87%;height: 100%"> <button class="layui-btn" οnclick="sendSysMsg()">发送</button></div></div>';
        layer.open({
            type: 1,
            title: ['系统消息'],
            content: chatWith,
            area: ['600px', '100px'],
            shade: 0,
            id: '系统消息',
            resize: true,
            zIndex: layer.zIndex,
            success: function (layero) {
            }
        });
    }
    //发送系统消息
    function sendSysMsg() {
        var msg = $("#sysMessageText").val();
        var message = {
            msg: msg,
            type: 2
        }
        ws.send(JSON.stringify(message));
        $("#sysMessageText").val("");
    }
    // 获取系统消息
    function openSysMsg() {
        var chatWith = '<div style="width: 100%;height: 100%;overflow: hidden">'
            + '<div  class="triangle" > <ul id="sysmsglist"></ul>'
            + '</div></div>';
        layer.open({
            type: 1,
            title: ['系统消息', 'font-family:Microsoft YaHei;font-size: 24px;height: 80px;'],
            content: chatWith,
            area: ['600px', '600px'],
            maxmin: true,
            shade: 0,
            id: "系统消息",
            resize: true,
            zIndex: layer.zIndex,
            success: function (layero) {
            }
        });
        $.ajax({
            async: false, //设置同步
            url: 'getSysMsgList',
            type: 'GET',
            success: function (res) {
                if (res.obj) {
                    var msgList = res.obj;
                    msgList.forEach(function (e) {
                        var rightHtml = '<li class="textLeft"><img class="head-icon" src="resources/images/icon.jpg" alt=""><span>' + e.msg +
                            '</span></li>';
                        $('#sysmsglist').append(rightHtml);
                    })
                }
            }
        })
    }
</script>

以上就是WebSocket在项目中的关键代码。

四、Shiro

Shiro简介:

  • 1.ApacheShiro是Java 的一个安全(权限)框架。
  • 2.Shiro可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。
  • 3.Shiro作用:认证、授权、加密、会话管理、与Web 集成、缓存等。
  • 4.下载地址:http://shiro.apache.org/

在项目中使用Spring SpringMVC Mybatis整合了Shiro,对于Shiro的配置主要有两个地方,一个是Spring容器(applicationContext.xml)web.xml
1、Spring容器(applicationContext.xml)中的配置

<!--shiro配置-->
    <!--com.webchat.realm.UserRealm 这个是自定义的Realm-->
    <bean class="com.webchat.realm.UserRealm" id="userRealm">
        <property name="credentialsMatcher">
            <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                <!--密码加密和加盐-->
                <property name="hashAlgorithmName" value="SHA-512"/>
                <property name="hashIterations" value="1024"/>
            </bean>
        </property>
    </bean>
    <bean class="org.apache.shiro.web.mgt.DefaultWebSecurityManager" id="securityManager">
        <property name="realm" ref="userRealm"/>
    </bean>
    <bean class="org.apache.shiro.spring.web.ShiroFilterFactoryBean" id="shiroFilter">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/login"/>
        <property name="filterChainDefinitions">
            <value>
                <!--
                    anon 表示无需认证(登录)就可访问
                    authc 表示需认证(登录)才可访问
                    /**=authc:表示除了在它之上的请求都需要拦截,放在最后
                -->
                /logout=logout
                /doLogin=anon
                /favicon.ico=anon
                /doRegister=anon
                /register=anon
                /resources/**=anon
                /**=authc
            </value>
        </property>
    </bean>

2、web.xml

<!--
        这是一个通用的代理过滤器,在spring整合多个框架时都会用到,
        这个代理过滤器的作用就是拦截所有的请求,将请求交给框架的某个bean去处理
        默认情况下,所谓的某个bean实际上就是名字为filter-name的bean
        
        这里<filter-name>shiroFilter</filter-name>中的shiroFilter要和applicationContext.xml
        中 <bean class="org.apache.shiro.spring.web.ShiroFilterFactoryBean" id="shiroFilter">的
        id保持一致
    -->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

3、Realm

public class LoginRealm extends AuthorizingRealm {
    @Autowired
    UserService userService;
    @Autowired
    RoleService roleService;
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        Session session = SecurityUtils.getSubject().getSession();
        User user = (User) session.getAttribute("user");
        Role role = user.getRole();
        Set<String> roles = new HashSet<String>();
        roles.add(role.getRolename());
        return new SimpleAuthorizationInfo(roles);
    }
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String account = (String) token.getPrincipal();
        User user = userService.getUserByAccount(account);
        if (user == null) {
            throw new UnknownAccountException("账户不存在!");
        }
        Session session = SecurityUtils.getSubject().getSession();
        session.setAttribute("user", user);
        ByteSource salts = ByteSource.Util.bytes(user.getAccount());
        return new SimpleAuthenticationInfo(user.getAccount(), user.getPassword(),salts, getName());
    }
}

AuthenticatingRealmgetAuthenticationInfo方法中,会自动进行密码校验,调用的方法是assertCredentialsMatch

4、UserController

@PostMapping("/doLogin")
    @ResponseBody
    public RespBean doLogin(User user, Model model) {
        String msg = null;
        if (user != null) {
            // 执行登录
            UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
            Subject subject = SecurityUtils.getSubject();
            try {
                subject.login(token);
                msg = "登录成功";
            } catch (AuthenticationException e) {
                e.printStackTrace();
                msg = "用户名或密码错误";
            }
        }
        return RespBean.ok(msg);
    }

在注册时,Service层给用户密码进行加盐加密

    public int saveUser(User user) {
        // 注册时给密码加密和加盐
        ByteSource bytes = ByteSource.Util.bytes(user.getUsername());
        SimpleHash simpleHash = new SimpleHash("SHA-512", user.getPassword(), bytes, 1024);
        user.setNickname(user.getUsername());
        user.setPassword(simpleHash.toString());
        user.setRegTime(new Timestamp(new Date().getTime()));
        user.setEnabled(true);
        user.setOnline(0);
        int i = userMapper.saveUser(user);
        // 用户注册成功就赋予角色
        userMapper.saveRoleUser(user.getId(), 2);
        return i;
    }

项目界面
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

以上就是本系统的基本介绍,源码地址:https://github.com/JavaZzm/MineChat.git

公众号二维码
在这里插入图片描述

好的,下面是一个简单的SSM+Shiro的登录认证示例: 首先,需要在pom.xml中添加相关依赖: ```xml <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.3.RELEASE</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.5.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.5.3</version> </dependency> ``` 其中,spring-webmvc和spring-jdbc是Spring的核心依赖,mybatis-spring-boot-starter是MyBatis的依赖,shiro-core和shiro-springShiro的依赖。 然后,在Spring的配置文件中配置数据源和MyBatis: ```xml <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/test"/> <property name="username" value="root"/> <property name="password" value="root"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="typeAliasesPackage" value="com.example.demo.entity"/> <property name="mapperLocations" value="classpath:/mapper/*.xml"/> </bean> <bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.example.demo.mapper"/> </bean> ``` 接下来,配置Shiro: ```xml <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="myRealm"/> </bean> <bean id="myRealm" class="com.example.demo.shiro.MyRealm"/> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login"/> <!-- 登录页面 --> <property name="successUrl" value="/index"/> <!-- 登录成功页面 --> <property name="filterChainDefinitions"> <value> /login = anon /logout = logout /** = authc </value> </property> </bean> ``` 其中,DefaultWebSecurityManager是Shiro的安全管理器,MyRealm是自定义的Realm类,ShiroFilterFactoryBean是Shiro的过滤器。 最后,实现登录认证: ```java @Controller public class LoginController { @Autowired private UserService userService; @RequestMapping("/login") public String login() { return "login"; } @RequestMapping("/index") public String index() { return "index"; } @RequestMapping("/loginCheck") @ResponseBody public String loginCheck(String username, String password) { Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(username, password); try { subject.login(token); return "success"; } catch (AuthenticationException e) { return "fail"; } } } ``` 其中,UserService是自定义的用户服务类,login方法返回登录页面,index方法返回登录成功页面,loginCheck方法处理登录请求,判断用户名和密码是否正确。 以上就是一个简单的SSM+Shiro的登录认证示例,希望可以帮到你。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值