一、项目简介
这是一个即时通讯的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.xml
,spring
配置文件
<?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.xml
,springmvc
配置文件
<?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
开始支持WebSocket
,WebSocket
程序分为两部分:浏览器端和服务端,在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
整合了Shir
o,对于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());
}
}
在AuthenticatingRealm
的getAuthenticationInfo
方法中,会自动进行密码校验,调用的方法是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
。
公众号二维码