在開發過程中,Springboot整合了Shiro安全框架,並且採用shiro-redis來進行session的緩存操作
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>2.8.24</version>
</dependency>
開發完成後,在本地IDEA中運行完全沒問題,一切和預期相同,然後執行打包部署,問題就出現了,我的打包方式爲分離出依賴jar包和配置文件等,其他接口一切正常,登錄也沒問題,但是後面的登錄狀態校驗就怎麼也不成功,也就是不能通過token(sessionId)獲取到已登錄的賬號信息,折騰了差不多一天,終於找到了問題原因並解決。
首先貼出我出現錯誤的打包方式的代碼:
<build>
<plugins>
<!--打包jar-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<!--不打包資源文件-->
<excludes>
<exclude>static/**</exclude>
<exclude>*.properties</exclude>
<exclude>*.xml</exclude>
</excludes>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<!--MANIFEST.MF 中 Class-Path 加入前綴-->
<classpathPrefix>lib/</classpathPrefix>
<!--jar包不包含唯一版本標識-->
<useUniqueVersions>false</useUniqueVersions>
<!--指定入口類-->
<mainClass>com.lvyabar.tutor.TutorApplication</mainClass>
</manifest>
<manifestEntries>
<!--MANIFEST.MF 中 Class-Path 加入資源文件目錄-->
<Class-Path>./resources/</Class-Path>
</manifestEntries>
</archive>
<outputDirectory>${project.build.directory}</outputDirectory>
</configuration>
</plugin>
<!--拷貝依賴 copy-dependencies-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>
${project.build.directory}/lib/
</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<!--拷貝資源文件 copy-resources-->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-resources</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<outputDirectory>${project.build.directory}/resources</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<!--spring boot repackage,依賴 maven-jar-plugin 打包的jar包 重新打包成 spring boot 的jar包-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!--重寫包含依賴,包含不存在的依賴,jar裏沒有pom裏的依賴-->
<includes>
<include>
<groupId>null</groupId>
<artifactId>null</artifactId>
</include>
</includes>
<layout>ZIP</layout>
<!--使用外部配置文件,jar包裏沒有資源文件-->
<addResources>true</addResources>
<outputDirectory>${project.build.directory}</outputDirectory>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
通過上述打包方式,會生成文件夾lib(項目依賴jar包)、resources(原項目中resources文件夾下的文件)以及一個jar(項目啓動文件)文件,使用如下命令啓動項目,一切都很順利
java -jar xxx.jar
但是當登錄後,用token進行權限校驗以及獲取session信息後,出錯了,報錯的日誌是在下面這個方法中輸出的
org.crazycake.shiro.RedisSessionDAO#doReadSession
@Override
protected Session doReadSession(Serializable sessionId) {
if (sessionId == null) {
logger.warn("session id is null");
return null;
}
Session s = getSessionFromThreadLocal(sessionId);
if (s != null) {
return s;
}
logger.debug("read session from redis");
try {
s = (Session) valueSerializer.deserialize(redisManager.get(keySerializer.serialize(getRedisSessionKey(sessionId))));
setSessionToThreadLocal(sessionId, s);
} catch (SerializationException e) {
logger.error("read session error. settionId=" + sessionId);
}
return s;
}
正是logger.error("read session error. settionId=" + sessionId);這行代碼輸出了error的日誌信息,但是,因爲這是依賴的shiro-redis的代碼,我無法查看到詳細的報錯信息,於是我複製了這個類的所有代碼,在輸出錯誤日誌之前,打印了錯誤信息棧,找到了根本原因是redis中的session反序列化失敗,導致反序列化失敗的原因是因爲session裏存入的管理員信息類找不到,具體錯誤信息如下:
2019-12-25 11:38:34.905 ERROR 35048 --- [nio-8080-exec-7] c.lvyabar.tutor.mapper.RedisSessionDAO : org.crazycake.shiro.SerializationException: deserialize error
at org.crazycake.shiro.ObjectSerializer.deserialize(ObjectSerializer.java:52)
at com.lvyabar.tutor.mapper.RedisSessionDAO.doReadSession(RedisSessionDAO.java:142)
at org.apache.shiro.session.mgt.eis.AbstractSessionDAO.readSession(AbstractSessionDAO.java:168)
at org.apache.shiro.session.mgt.DefaultSessionManager.retrieveSessionFromDataSource(DefaultSessionManager.java:236)
at org.apache.shiro.session.mgt.DefaultSessionManager.retrieveSession(DefaultSessionManager.java:222)
at org.apache.shiro.session.mgt.AbstractValidatingSessionManager.doGetSession(AbstractValidatingSessionManager.java:118)
at org.apache.shiro.session.mgt.AbstractNativeSessionManager.lookupSession(AbstractNativeSessionManager.java:148)
at org.apache.shiro.session.mgt.AbstractNativeSessionManager.getSession(AbstractNativeSessionManager.java:140)
at org.apache.shiro.mgt.SessionsSecurityManager.getSession(SessionsSecurityManager.java:156)
at org.apache.shiro.mgt.DefaultSecurityManager.resolveContextSession(DefaultSecurityManager.java:461)
at org.apache.shiro.mgt.DefaultSecurityManager.resolveSession(DefaultSecurityManager.java:447)
at org.apache.shiro.mgt.DefaultSecurityManager.createSubject(DefaultSecurityManager.java:343)
at org.apache.shiro.subject.Subject$Builder.buildSubject(Subject.java:845)
at org.apache.shiro.web.subject.WebSubject$Builder.buildWebSubject(WebSubject.java:148)
at org.apache.shiro.web.servlet.AbstractShiroFilter.createSubject(AbstractShiroFilter.java:292)
at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:359)
at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:526)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1579)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.ClassNotFoundException: com.lvyabar.tutor.vo.admin.user.AdminVO
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Unknown Source)
at java.io.ObjectInputStream.resolveClass(Unknown Source)
at java.io.ObjectInputStream.readNonProxyDesc(Unknown Source)
at java.io.ObjectInputStream.readClassDesc(Unknown Source)
at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
at java.io.ObjectInputStream.readObject0(Unknown Source)
at java.io.ObjectInputStream.readObject(Unknown Source)
at java.util.HashMap.readObject(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at java.io.ObjectStreamClass.invokeReadObject(Unknown Source)
at java.io.ObjectInputStream.readSerialData(Unknown Source)
at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
at java.io.ObjectInputStream.readObject0(Unknown Source)
at java.io.ObjectInputStream.readObject(Unknown Source)
at org.apache.shiro.session.mgt.SimpleSession.readObject(SimpleSession.java:500)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at java.io.ObjectStreamClass.invokeReadObject(Unknown Source)
at java.io.ObjectInputStream.readSerialData(Unknown Source)
at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
at java.io.ObjectInputStream.readObject0(Unknown Source)
at java.io.ObjectInputStream.readObject(Unknown Source)
at org.crazycake.shiro.ObjectSerializer.deserialize(ObjectSerializer.java:48)
... 46 more
沒錯,究其根本,就是這個錯誤導致的:Caused by: java.lang.ClassNotFoundException: com.lvyabar.tutor.vo.admin.user.AdminVO,反序列化的時候找不到這個類,接下來就開始分析,爲什麼會找不到這個類呢,在本地不打包運行的時候完全沒問題,並且我也看了打包後的文件中是能找到這個類的,於是各種猜測、網上搜索原因,都沒解決我的問題,直到我看到一篇文章,關於項目打包後別的模塊無法import的原因,懷着試一試的態度,居然解決了我的問題,原因正是打包的問題,通過上面說到的打包方式,生成的jar包結構是這樣的
項目的類路徑是在BOOT-INF這個文件夾下,所以大概原因就知道了,因爲類路徑多了個BOOT-INF,所以導致反序列化的時候找不到類,於是,只要打包的時候去除BOOT-INF文件夾就好了,新的打包方式只需添加一行代碼<skip>true</skip>,完整的打包方式代碼如下:
<build>
<plugins>
<!--打包jar-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<!--不打包資源文件-->
<excludes>
<exclude>static/**</exclude>
<exclude>*.properties</exclude>
<exclude>*.xml</exclude>
</excludes>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<!--MANIFEST.MF 中 Class-Path 加入前綴-->
<classpathPrefix>lib/</classpathPrefix>
<!--jar包不包含唯一版本標識-->
<useUniqueVersions>false</useUniqueVersions>
<!--指定入口類-->
<mainClass>com.lvyabar.tutor.TutorApplication</mainClass>
</manifest>
<manifestEntries>
<!--MANIFEST.MF 中 Class-Path 加入資源文件目錄-->
<Class-Path>./resources/</Class-Path>
</manifestEntries>
</archive>
<outputDirectory>${project.build.directory}</outputDirectory>
</configuration>
</plugin>
<!--拷貝依賴 copy-dependencies-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>
${project.build.directory}/lib/
</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<!--拷貝資源文件 copy-resources-->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-resources</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<outputDirectory>${project.build.directory}/resources</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<!--spring boot repackage,依賴 maven-jar-plugin 打包的jar包 重新打包成 spring boot 的jar包-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!--重寫包含依賴,包含不存在的依賴,jar裏沒有pom裏的依賴-->
<includes>
<include>
<groupId>null</groupId>
<artifactId>null</artifactId>
</include>
</includes>
<layout>ZIP</layout>
<!--使用外部配置文件,jar包裏沒有資源文件-->
<addResources>true</addResources>
<outputDirectory>${project.build.directory}</outputDirectory>
<skip>true</skip>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
於是打包後的jar文件結構如下
再次運行項目,發現終於正常了,能正常解析出redis中的session數據了。