从零开始写一个登录(持续集成)

前言

此项目算是一个demo,用于整合之前了解的技术。

并不注重前端 只在乎与前端的一些交互 所以使用了框架vue-elementsui-admin

先安装一个前端框架

选择element 网站快速成型工具

快速集成就决定是你了

由于时通过vue的脚手架vue-cli集成的工具包所以需要先安装cli

三部曲

1
2
3
npm install cooking-cli -g 安装webapck加强版构建工具
npm install 安装依赖
npm run dev 启动项目

引入通过vue-cli生成的Element UI入门套件 这里并不是必须

https://github.com/ElementUI/element-cooking-starter

1
clone

查看是否安装成功

1
vue list

也可以查看npm组件地址的方式去看

1
npm config get prefix

使用element项目模板 光的就带了一些工具包

https://github.com/ElementUI/element-cooking-starter

1
clone
1
2
3
npm install 安装依赖
npm run dev 启动项目
npm run build 构建项目

这里启动遇到成功 但是无法访问 报错

1
{ parser: "babylon" } is deprecated; we now treat it as { parser: "babel" }

安装一下prettier

https://prettier.io/docs/en/install.html

使用vue+element项目

1
https://github.com/PanJiaChen/vue-element-admin.git

日常clone没啥问题

vue有三宝 先install 后run dev 还有build

自动启动

image-20200412051242926

有一点百度看了下 这vue如何集成整合进javaweb里面

我寻思这前后端分离我为什么要把东西搞一块呢???

vue改了 build一下再run 不影响啊 写个bat

image-20200412052312870

oh year ♂

持续更新。。

4.19 这个github的vue项目有个分支 支持国际版也就是语言切换

image-20200419181920160

分支地址

改一下前端细节

查看中文文档

找到登录页面的代码 @/router/index.js 也就是路由

image-20200419193129732

把这里注释掉

image-20200419193813998

image-20200419194721581

因为没有自带验证码

这里引用vue的验证码组件vue2-verify

基于https://github.com/Hibear/verify的验证码项目

image-20200419220055477

image-20200419220109041

2020-06-07 更新

搭建后台

创建一个springboot项目

配置maven依赖(现已经加入的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.xia</groupId>
<artifactId>zhb</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>zhb</name>
<description>Demo project for Spring Boot</description>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>

<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>

<!-- This plugin is only to test run our little application. It is not
needed in most Shiro-enabled applications: -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.3.2</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>

<configuration>
<mainClass>com.example.Shiro.Tutorial</mainClass>
</configuration>
</plugin>
</plugins>

</build>



<dependencies>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<!--springboot web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>


<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
<!-- 去掉默认log配置 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>

<exclusion>
<artifactId>logback-classic</artifactId>
<groupId>ch.qos.logback</groupId>
</exclusion>

</exclusions>
</dependency>


<!-- 引入log4j2依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>


<dependency>

<groupId>
org.springframework.boot
</groupId>

<artifactId>
spring-boot-devtools
</artifactId>

<optional>
true
</optional>

<!-- 这个需要为 true 热部署才有效 -->

</dependency>




<!--mybatis-->


<dependency>


<groupId>
org.mybatis.spring.boot
</groupId>


<artifactId>
mybatis-spring-boot-starter
</artifactId>


<version>
1.3.2
</version>


</dependency>




<!-- mysql -->


<dependency>


<groupId>
mysql
</groupId>


<artifactId>
mysql-connector-java
</artifactId>


<version>
5.1.47
</version>


</dependency>




<!--阿里巴巴连接池-->


<dependency>


<groupId>
com.alibaba
</groupId>


<artifactId>
druid
</artifactId>


<version>
1.0.9
</version>


</dependency>



<!--shiro依赖-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>

<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.2</version>
</dependency>

<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.3.2</version>
</dependency>

<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>provided</scope>
</dependency>

<!--全局排除-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>

<!--验证码依赖-->
<dependency>
<groupId>com.ramostear</groupId>
<artifactId>Happy-Captcha</artifactId>
<version>1.0.1</version>
</dependency>

<!--thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

</dependencies>



</project>

加入log4j2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
<?xml version="1.0" encoding="UTF-8"?>
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration monitorInterval="5">
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->

<!--变量配置-->
<Properties>
<!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
<!-- %logger{36} 表示 Logger 名字最长36个字符 -->
<property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
<!-- 定义日志存储的路径 -->
<property name="FILE_PATH" value="E:/tool/ideaIU-2018.2.5/log4j2" />
<property name="FILE_NAME" value="zhb" />
</Properties>

<appenders>

<console name="Console" target="SYSTEM_OUT">
<!--输出日志的格式-->
<PatternLayout pattern="${LOG_PATTERN}"/>
<!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
</console>

<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用-->
<File name="Filelog" fileName="E:/tool/ideaIU-2018.2.5/log4j2/log.log" append="false">
<PatternLayout pattern="${LOG_PATTERN}"/>
</File>

<!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/info.log" filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<!--interval属性用来指定多久滚动一次,默认是1 hour-->
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="10MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
<DefaultRolloverStrategy max="15"/>
</RollingFile>

<!-- 这个会打印出所有的warn及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/warn.log" filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<!--interval属性用来指定多久滚动一次,默认是1 hour-->
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="10MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
<DefaultRolloverStrategy max="15"/>
</RollingFile>

<!-- 这个会打印出所有的error及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileError" fileName="${FILE_PATH}/error.log" filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<!--interval属性用来指定多久滚动一次,默认是1 hour-->
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="10MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
<DefaultRolloverStrategy max="15"/>
</RollingFile>

</appenders>

<!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。-->
<!--然后定义loggers,只有定义了logger并引入的appender,appender才会生效-->
<loggers>

<!--&lt;!&ndash;过滤掉spring和mybatis的一些无用的DEBUG信息&ndash;&gt;-->
<!--<logger name="org.mybatis" level="info" additivity="false">-->
<!--<AppenderRef ref="Console"/>-->
<!--</logger>-->
<!--&lt;!&ndash;监控系统信息&ndash;&gt;-->
<!--&lt;!&ndash;若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出。&ndash;&gt;-->
<!--<Logger name="org.springframework" level="info" additivity="false">-->
<!--<AppenderRef ref="Console"/>-->
<!--</Logger>-->

<!--<Logger name="com.xia.zhb.controller.LoginController" level="info" additivity="false">-->
<!--<AppenderRef ref="Console"/>-->
<!--</Logger>-->

<root level="info">
<appender-ref ref="Console"/>
<appender-ref ref="Filelog"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
</root>
</loggers>

</configuration>


详情可以看 https://www.cnblogs.com/keeya/p/10101547.html#%E4%B8%BA%E4%BB%80%E4%B9%88%E9%80%89%E7%94%A8log4j2

加入lombok

不细说了

配置全局yml

1
2
3
4
5
6
7
8
9
10
11
mybatis:
mapper-locations: classpath:mapper/*Dao.xml
typeAliasesPackage: com.xia.zhb.entity
server:
port: 8089
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/zhb?useUnicode=true&characterEncoding=UTF-8
username: root
password: root

创建数据库zhb 创建表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
CREATE TABLE `t_role` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`rolename` varchar(20) DEFAULT NULL COMMENT '角色名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8

CREATE TABLE `t_user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户主键',
`username` varchar(20) NOT NULL COMMENT '用户名',
`password` varchar(20) NOT NULL COMMENT '密码',
`role_id` int(11) DEFAULT NULL COMMENT '外键关联role表',
PRIMARY KEY (`id`),
KEY `role_id` (`role_id`),
CONSTRAINT `t_user_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `t_role` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8

CREATE TABLE `t_permission` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`permissionname` varchar(50) NOT NULL COMMENT '权限名',
`role_id` int(11) DEFAULT NULL COMMENT '外键关联role',
PRIMARY KEY (`id`),
KEY `role_id` (`role_id`),
CONSTRAINT `t_permission_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `t_role` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8

使用easycode快速生成代码 以及开启springboot热部署

详情看

https://www.zhbzdnb.com/2019/12/29/%E5%85%B3%E4%BA%8E%E4%BB%A3%E7%A0%81%E6%95%8F%E6%8D%B7%E5%92%8C%E8%A7%84%E8%8C%83/

使用生成验证码组件

项目地址

https://github.com/ramostear/Happy-Captcha

1
2
3
4
5
//验证码
@GetMapping("/captcha")
public void happyCaptcha(HttpServletRequest reqeust, HttpServletResponse response){
HappyCaptcha.require(reqeust,response).build().finish();
}

效果如图

image-20200607152328284

加入安全框架shiro

主要的俩类

自定义 Realm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.xia.zhb.shiro;

import com.xia.zhb.entity.TUser;
import com.xia.zhb.service.TUserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;

import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

import javax.annotation.Resource;
import java.util.HashSet;
import java.util.Set;

public class MyRealm extends AuthorizingRealm {

@Resource
private TUserService tuserService;

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 获取用户名
String username = (String) principalCollection.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
Set<String> setrole = new HashSet<String>();
setrole.add(tuserService.queryUsernameToRole(username));
// 给该用户设置角色,角色信息存在 t_role 表中取
authorizationInfo.setRoles(setrole);
// 给该用户设置权限,权限信息存在 t_permission 表中取
Set<String> setpermission = new HashSet<String>();
setpermission.add(tuserService.queryUsernameToPermission(username));
authorizationInfo.setStringPermissions(setpermission);
return authorizationInfo;
}

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 根据 Token 获取用户名,如果您不知道该 Token 怎么来的,先可以不管,下文会解释
String username = (String) authenticationToken.getPrincipal();
// 根据用户名从数据库中查询该用户
TUser user = tuserService.queryUsername(username);
if(user != null) {
// 把当前用户存到 Session 中
SecurityUtils.getSubject().getSession().setAttribute("user", user);
// 传入用户名和密码进行身份认证,并返回认证信息
AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), "myRealm");
return authcInfo;
} else {
return null;
}
}
}

Shiro 配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package com.xia.zhb.shiro;

import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;


@Configuration
public class ShiroConfig {

private static final Logger logger = LoggerFactory.getLogger(ShiroConfig.class);

/**
* 注入自定义的 Realm
* @return MyRealm
*/
@Bean
public MyRealm myAuthRealm() {
MyRealm myRealm = new MyRealm();
logger.info("====myRealm注册完成=====");
return myRealm;
}
/**
* 注入安全管理器
* @return SecurityManager
*/
@Bean
public SecurityManager securityManager() {
// 将自定义 Realm 加进来
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(myAuthRealm());
logger.info("====securityManager注册完成====");
return securityManager;
}
/**
* 注入 Shiro 过滤器
* @param securityManager 安全管理器
* @return ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
// 定义 shiroFactoryBean
ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();

// 设置自定义的 securityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);

// 设置默认登录的 URL,身份认证失败会访问该 URL
shiroFilterFactoryBean.setLoginUrl("/login");
// 设置成功之后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/success");
// 设置未授权界面,权限认证失败会访问该 URL
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");

// LinkedHashMap 是有序的,进行顺序拦截器配置
Map<String,String> filterChainMap = new LinkedHashMap<String,String>();

// // 配置可以匿名访问的地址,可以根据实际情况自己添加,放行一些静态资源等,anon 表示放行
// filterChainMap.put("/css/**", "anon");
// filterChainMap.put("/imgs/**", "anon");
// filterChainMap.put("/js/**", "anon");
// filterChainMap.put("/swagger-*/**", "anon");
// filterChainMap.put("/swagger-ui.html/**", "anon");
// 登录 URL 放行
filterChainMap.put("/login", "anon");

// // 以“/user/admin” 开头的用户需要身份认证,authc 表示要进行身份认证
// filterChainMap.put("/user/admin*", "authc");
// // “/user/student” 开头的用户需要角色认证,是“admin”才允许
// filterChainMap.put("/user/student*/**", "roles[admin]");
// // “/user/teacher” 开头的用户需要权限认证,是“user:create”才允许
// filterChainMap.put("/user/teacher*/**", "perms[\"user:create\"]");
//
// // 配置 logout 过滤器
// filterChainMap.put("/logout", "logout");

// 设置 shiroFilterFactoryBean 的 FilterChainDefinitionMap
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
logger.info("====shiroFilterFactoryBean注册完成====");
return shiroFilterFactoryBean;
}


}

vue+elementui集成到springboot

将之前的前端验证码注释加入以下代码

1
<img src="/captcha"/>

将vue项目bulid

此文件夹全部放置到后台,将index.html放一份到templates文件夹

image-20200607182341265

image-20200607182357210

创建进入类
1
2
3
4
5
6
7
8
@Controller
@RequestMapping("")
public class IndexController {
@RequestMapping("/index")
public String index() {
return "index";
}
}

访问结果

image-20200607182635423

6.14更新

更改vue项目 api 和 前缀配置文件

image-20200614213908178

image-20200614213933302

期间遇到问题 当我尝试将vue的前缀配置文件改成zhb

并将后台springboot加入全局前缀zhb时

vue有些文件引入时也用了zhb这个前缀 所以我现在先将vue项目和springboot的配置改为 /

给后台login方法设置log

image-20200614214008272

更改后台代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@PostMapping("/login")
public String login(@RequestBody TUser user) {
log.info("登录 ");

//获得当前用户到登录对象,现在状态为未认证
Subject subject= SecurityUtils.getSubject();
//用户名密码令牌
AuthenticationToken token=new UsernamePasswordToken(user.getUsername(),user.getPassword());
try {
subject.login(token);
} catch (UnknownAccountException uae) {
log.info("用户名和密码不匹配");
} catch (IncorrectCredentialsException ice) {
log.info("用户名和密码不匹配");
} catch (LockedAccountException lae) {
log.info("LockedAccountException");
} catch (ExcessiveAttemptsException eae) {
log.info("ExcessiveAttemptsException");
} catch (AuthenticationException ae) {
log.info("AuthenticationException");
}


return "登录页面";
}

因为vue-elementui-admin这个框架关于密码默认封装了密码需要6位

直接去数据库改一下密码

image-20200614225332787

成功登入

有些好奇所以去查看shiro的login方法实现

查看实现https://blog.csdn.net/u010003835/article/details/79036666

ctrl进入了DelegatingSubject

具体代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public void login(AuthenticationToken token) throws AuthenticationException {
this.clearRunAsIdentitiesInternal();
Subject subject = this.securityManager.login(this, token);
String host = null;
PrincipalCollection principals;
if (subject instanceof DelegatingSubject) {
DelegatingSubject delegating = (DelegatingSubject)subject;
principals = delegating.principals;
host = delegating.host;
} else {
principals = subject.getPrincipals();
}

if (principals != null && !principals.isEmpty()) {
this.principals = principals;
this.authenticated = true;
if (token instanceof HostAuthenticationToken) {
host = ((HostAuthenticationToken)token).getHost();
}

if (host != null) {
this.host = host;
}

Session session = subject.getSession(false);
if (session != null) {
this.session = this.decorate(session);
} else {
this.session = null;
}

} else {
String msg = "Principals returned from securityManager.login( token ) returned a null or empty value. This value must be non null and populated with one or more elements.";
throw new IllegalStateException(msg);
}
}

通过this.securityManager.login(this, token);

使用了继承SessionsSecurityManager的DefaultSecurityManager的login方法

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info;
try {
info = this.authenticate(token);
} catch (AuthenticationException var7) {
AuthenticationException ae = var7;

try {
this.onFailedLogin(token, ae, subject);
} catch (Exception var6) {
if (log.isInfoEnabled()) {
log.info("onFailedLogin method threw an exception. Logging and propagating original AuthenticationException.", var6);
}
}

throw var7;
}

Subject loggedIn = this.createSubject(token, info, subject);
this.onSuccessfulLogin(token, info, loggedIn);
return loggedIn;
}

实际调用DefaultSecurityManager.onFailedLogin(AuthenticationToken token, AuthenticationException ae, Subject subject)->DefaultSecurityManager.(AuthenticationToken token, AuthenticationException ex, Subject subject)->RememberMeManager接口的实现AbstractRememberMeManager类的onFailedLogin方法此方法调用CookieRememberMeManager.forgetIdentity

其具体代码

1
2
3
4
5
6
7
8
protected void forgetIdentity(Subject subject) {
if (WebUtils.isHttp(subject)) {
HttpServletRequest request = WebUtils.getHttpRequest(subject);
HttpServletResponse response = WebUtils.getHttpResponse(subject);
this.forgetIdentity(request, response);
}

}

只是粗略看一下 因为已经配置好了自定义Realm和ShiroConfig

详情看

https://www.zhbzdnb.com/2020/05/04/shiro%E7%AE%80%E5%8D%95%E5%85%A5%E9%97%A8%E5%AE%9E%E4%BE%8B/

6.18更新

因为是vue新手,直接使用vue-elementui-admin 使得我很多进度推进缓慢 所以我改用差不多的前端vue+elementui项目https://gitee.com/y_project/RuoYi-Vue/blob/master/ruoyi-ui

安装其依赖遇到问题此文章解决了我的问题http://www.xiwenblog.com/archives/1989

将项目拉下啦后;接上验证码、接上登录接口

进入成功

因为拥有其后台代码 改写login代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@PostMapping("/login")
public AjaxResult login(String username, String password) {
log.info("登录 ");

//获得当前用户到登录对象,现在状态为未认证
Subject subject= SecurityUtils.getSubject();
//用户名密码令牌
AuthenticationToken token=new UsernamePasswordToken(username,password);
AjaxResult ajaxResult=AjaxResult.success();
try {
subject.login(token);
} catch (UnknownAccountException uae) {
log.info("用户名和密码不匹配");
return ajaxResult=AjaxResult.error("用户名和密码不匹配");
} catch (IncorrectCredentialsException ice) {
log.info("用户名和密码不匹配");
return error("用户名和密码不匹配");
} catch (LockedAccountException lae) {
log.info("LockedAccountException");
return error("用户名和密码不匹配");
} catch (ExcessiveAttemptsException eae) {
log.info("ExcessiveAttemptsException");
return error("用户名和密码不匹配");
} catch (AuthenticationException ae) {
log.info("AuthenticationException");
return error("用户名和密码不匹配");
}
ajaxResult.put("token", token.getCredentials());
System.out.println(token.getCredentials());
return ajaxResult;
}

关于shiro的AuthenticationTokenhttps://www.jianshu.com/p/9ac577427b44

这里遇到问题 当我用若依的后台返回参数的方式返回其结果无效

他是使用自定义的一个令牌 我是使用shiro的AuthenticationToken.getCredentials()

找到前端的login方法 打上断点和log

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 登录
Login({ commit }, userInfo) {
const username = userInfo.username.trim()
const password = userInfo.password
const code = userInfo.code
const uuid = userInfo.uuid
return new Promise((resolve, reject) => {
login(username, password, code, uuid).then(res => {
console.log(res)
debugger
setToken(res.token)
commit('SET_TOKEN', res.token)
resolve()
}).catch(error => {
reject(error)
})
})

12.了明天再研究 最近工作很忙

6.21更新

使用nginx配置跨域和转发 nginx配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

#user nobody;
worker_processes 1;

#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;

#pid logs/nginx.pid;


events {
worker_connections 1024;
}


http {
include mime.types;
default_type application/octet-stream;

#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';

#access_log logs/access.log main;

sendfile on;
#tcp_nopush on;

#keepalive_timeout 0;
keepalive_timeout 65;

#gzip on;


server {
listen 80;
server_name localhost;
location / {
root E:/tool/ideaIU-2018.2.5/RuoYi-Vue/ruoyi-ui/dist/;
try_files $uri $uri/ /index.html;
index index.html index.htm;
}
location /prod-api/{
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://localhost:8089/;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}

# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;

# location / {
# root html;
# index index.html index.htm;
# }
#}


# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;

# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;

# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;

# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;

# location / {
# root html;
# index index.html index.htm;
# }
#}

}

更改验证码相关代码

1
2
3
4
5
6
7
8
9
10
11
getCode() {
/* getCodeImg().then(res => { */
var num = Math.ceil(Math.random() * 10);

this.codeUrl = process.env.VUE_APP_BASE_API+'/captcha?num=' + num;

//this.loginForm.uuid = res.uuid;
/* this.codeUrl = "data:image/gif;base64," + res.img;
this.loginForm.uuid = res.uuid; */
/* }); */
}

连接没有问题了,但是还是跳转有问题,原因是后台以及前端的token机制。

后台返回的token并不是前端规范的token。

继续研究

关于其机制 查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getToken } from '@/utils/auth'

NProgress.configure({ showSpinner: false })

const whiteList = ['/login', '/auth-redirect', '/bind', '/register']

router.beforeEach((to, from, next) => {
NProgress.start()
if (getToken()) {
/* has token*/
if (to.path === '/login') {
next({ path: '/' })
NProgress.done()
} else {
if (store.getters.roles.length === 0) {
// 判断当前用户是否已拉取完user_info信息
store.dispatch('GetInfo').then(res => {
// 拉取user_info
const roles = res.roles
store.dispatch('GenerateRoutes', { roles }).then(accessRoutes => {
// 测试 默认静态页面
// store.dispatch('permission/generateRoutes', { roles }).then(accessRoutes => {
// 根据roles权限生成可访问的路由表
router.addRoutes(accessRoutes) // 动态添加可访问路由表
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
})
})
.catch(err => {
store.dispatch('FedLogOut').then(() => {
Message.error(err)
next({ path: '/' })
})
})
} else {
next()
// 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓
// if (hasPermission(store.getters.roles, to.meta.roles)) {
// next()
// } else {
// next({ path: '/401', replace: true, query: { noGoBack: true }})
// }
// 可删 ↑
}
}
} else {
// 没有token
if (whiteList.indexOf(to.path) !== -1) {
// 在免登录白名单,直接进入
next()
} else {
next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页
NProgress.done()
}
}
})

router.afterEach(() => {
NProgress.done()
})

我并不想去照搬若依的后台代码,我需要自己去实现。

7.5

将若依前后端全部都给发布 打断点 了解了其token的原理,前端和后台任何交互都带着token 。
$$
前端请求带token->后台设置验证token的类,检验token有效->前端在发送login时验证其用户名和密码 并生成有效token
$$
前端代码已经实现且我并不注重前端的token,所以主要

  1. 怎么去实现验证token的拦截器
  2. 如何生成token

抽时间完成了关于shiro token的操作

shiro关于token的一些操作


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!