一、简介

https://docs.spring.io/spring-security/site/docs/4.2.10.RELEASE/guides/html5/helloworld-xml.html

SpringSecurity 融合 Spring 技术栈,提供 JavaEE 应 用的整体安全解决方案;

Spring Security 为基于 Java EE 的企业软件应用提供全面的安全服务。

Spring Security 只需要少量配置,就能构建一个强大的安全的应用系统。

目前市面上受欢迎的两个安全框架:Apache Shiro、SpringSecurity;

SpringSecurity 可以无缝整合 Spring 应用,具有强大的自动化 web 安全管控功能。而 Shiro 是一个轻量级强大的安全框架,可以脱离 web 应用来提供安全管控,但是对于 web 的一些定制安全需要手动编写;SpringBoot 底层默认整合 SpringSecurity 作为安全框架,所以我们推荐 web 应用使用 SpringSecurity 来控制安全;

1、文档

Hello Spring Security <https://github.com/spring-projects/spring-security/tree/4.2.10.RELEASE/samples/javaconfig/helloworld > 基于 Java 配置整合示例

Hello Spring Security Boot https://github.com/spring-projects/spring-security/tree/4.2.10.RELEASE/samples/boot/helloworld 与 SpringBoot 整合案例

Hello Spring Security XML https://github.com/spring-projects/spring-security/tree/4.2.10.RELEASE/samples/xml/helloworld 基于 XML 方式整合示例

Hello Spring MVC Security https://github.com/spring-projects/spring-security/tree/4.2.10.RELEASE/samples/javaconfig/hellomvc SpringMVC 集成示例

Custom Login Form https://github.com/spring-projects/spring-security/tree/4.2.10.RELEASE/samples/javaconfig/form 自定义登录表单示例

2、使用方式

  • 一种是全部利用配置文件,将用户、权限、资源(url)硬编码在 xml 文件中

  • 二种是用户和权限用数据库存储,而资源(url)和权限的对应采用硬编码配置

  • 三种是细分角色和权限,并将用户、角色、权限和资源均采用数据库存储,并且自定义过滤器,代替原有的FilterSecurityInterceptor过滤器, 并分别实现AccessDecisionManager、InvocationSecurityMetadataSourceService和UserDetailsService,并在配置文件中进行相应配置。

  • 四是修改 springsecurity 的源代码,主要是修改 InvocationSecurityMetadataSourceService 和 UserDetailsService 两个类。

    1. InvocationSecurityMetadataSourceService

      将配置文件或数据库中存储的资源(url)提取出来加工成为 url 和权限列表的 Map 供 Security 使用

    2. UserDetailsService

​ 提取用户名和权限组成一个完整的(UserDetails)User 对象,该对象可以提供用户的详细信息供 AuthentationManager 进行认证与授权使用

3、概念

认证

authenfication: 身份验证

​ “身份验证” 是指建立主体(principal)的过程,主体就是他们声称的是谁 (“主体” 通常指用户、设备或在应用程序中可以执行动作的其他系统)。也就是 “证明你是谁”。

授权

authorization: 授权

​ “授权” 是指确定主体(principal) 是否被允许执行系统中某个动作的过程。也就是 “你能做什么!”

为了达到“授权”决策(安全框架决定你是否有权限做此事),“身份验证”(authentication)过程已经建立了主体的身份(Principal)

二、SpringSecurity-HellWorld

1、测试环境搭建

1.1 创建普通的 maven-war 工程:spring-security-helloworld

注意:以下所有的测试都采用的 Idea 集成开发工具

pom 文件增加依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 <dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.20.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>

web.xml 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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">
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

spring 配置:spring.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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.oy.security"></context:component-scan>

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>

<mvc:annotation-driven/>
<mvc:default-servlet-handler/>
</beans>

导入实验资源

  • 需要资源的博客末尾有下载地址
  • 导入页面

image-20210203111400600

  • 导入 controller

image-20210203165335907

运行测试

image-20210203111542765

2、引入 SpringSecurity 框架

2.1 添加 security-pom 依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>4.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.2.10.RELEASE</version>
</dependency>
<!-- 标签库 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>4.2.10.RELEASE</version>
</dependency>

2.2 web.xml 中添加 SpringSecurity 的 Filter 进行安全控制

1
2
3
4
5
6
7
8
<filter>
<filter-name>springSecurityFilterChain</filter-name><!--名称固定,不能变 -->
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

2.3 加入 SpringSecurity 配置类

  • @Configuration、@Bean 注解作用
1
2
3
4
5
@Configuration //声明当前类是一个配置类。相当与XML配置文件作用。
@EnableWebSecurity //声明式配置,启用SpringSecurity安全机制。
public class AppWebSecurityConfig extends WebSecurityConfigurerAdapter {

}

2.4 启动测试效果

image-20210203165802954

  • 所有资源访问受限(包括静态资源)
  • 框架自带一个默认的登录界面
  • 账号密码错误会有提示

image-20210203170522858

  • 查看登录页面的源码,发现有个 hidden-input: name = “_csrf“ 这个是 springsecurity 帮我们防止“跨站请求伪造” 攻击;还可以防止表单重复提交。

image-20210203170641242

三、SpringSecurity 实验

1、实验一: 授权首页和静态资源

  • 配置类 (AppWebSecurityConfig extends WebSecurityConfigurerAdapter
  • 重写 configure(HttpSecurity http)方法
1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration // 声明当前类是一个配置类。相当于XML配置文件作用。
@EnableWebSecurity //声明式配置,启用SpringSecurity安全机制
public class AppWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//super.configure(http); // 取消默认配置
http.authorizeRequests()
// 设置匹配的资源放行
.antMatchers("/layui/**","/index.jsp").permitAll()
// 剩余任何资源必须认证
.anyRequest().authenticated();
}
}
  • 测试结果

静态资源和 index.jsp 都可以访问

image-20210203171903224

不存在的资源

  1. 有权限无资源 400

image-20210203172202192

  1. 无权限

image-20210203172242911

2、实验二: 默认及自定义登录页

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
protected void configure(HttpSecurity http) throws Exception {
//super.configure(http); // 取消默认配置
http.authorizeRequests()
// 设置匹配的资源放行
.antMatchers("/layui/**","/index.jsp").permitAll()
// 剩余任何资源必须认证
.anyRequest().authenticated();

// 实验二: 默认及自定义登录页
http.formLogin(); // 默认登录页

}

image-20210203174330647

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
protected void configure(HttpSecurity http) throws Exception {
//super.configure(http); // 取消默认配置
http.authorizeRequests()
// 设置匹配的资源放行
.antMatchers("/layui/**","/index.jsp").permitAll()
// 剩余任何资源必须认证
.anyRequest().authenticated();

// 实验二: 默认及自定义登录页

// 自定义登录页
http.formLogin().loginPage("/index.jsp");

}

​ 在访问没有权限资源或页面时,将会自动跳转到index.jsp登录页面

  • 在测试的时候需要先暂时_csrf这个功能
1
http.csrf().disable();

3、实验三: 自定义表单登录逻辑分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
protected void configure(HttpSecurity http) throws Exception {
//super.configure(http); // 取消默认配置
http.authorizeRequests()
// 设置匹配的资源放行
.antMatchers("/layui/**","/index.jsp").permitAll()
// 剩余任何资源必须认证
.anyRequest().authenticated();

// 自定义登录页
http.formLogin().loginPage("/index.jsp")
.loginProcessingUrl("/index.jsp")
.usernameParameter("loginacct")
.passwordParameter("userpswd")
.defaultSuccessUrl("/main.html");
http.csrf().disable(); // 禁用csrf

}
  • 表单提交地址:${PATH }/index.jsp

  • 表单提交请求方式:post

  • 提交表单:

    • 引入 jquery: <script src=”${PATH }/layui/jquery.min.js”></script>
    • $(“form”).submit();

image-20210203181846728

image-20210203181905444

如果没有关闭 CSRF, 提交请求被拒绝, 需要暂时禁用 csrf:http.csrf().disable();

image-20210203181950686

  • 表单提交请求失败,提取错误消息:**${SPRING_SECURITY_LAST_EXCEPTION.message}**
1
2
3
4
5
<div class="layadmin-user-login-box layadmin-user-login-header">
<h2>layuiAdmin</h2>
<p>layui 官方出品的单页面后台管理模板系统</p>
<p>${SPRING_SECURITY_LAST_EXCEPTION.message}</p>
</div>

image-20210203182702663

4、实验四: 自定义认证用户信息

1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 默认认证
//super.configure(auth);


//实验四:自定义认证用户信息 - 基于内存认证方式
auth.inMemoryAuthentication()
.withUser("zhangsan").password("123456").roles("学徒","大师")
.and()
.withUser("lisi").password("123456").authorities("罗汉拳","武当长拳");
}

​ 输入用户名和密码之后一致,就可以登录页面后台,前提是与自己基于内存认证方式一致的用户名和密码

image-20210203183705870

  • CSRF 跨站请求伪造
  • SpringSecurity 添加了 crsf 功能 【DefaultCsrfToken】,所有的表单提交为了防止跨站请求的伪造,我们需要加上_csrf 项;或者,暂时禁用 http.csrf().disable();
1
2
// 可以在页面任意位置加,没有唯一性
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />

image-20210203221217981

5、实验五:用户注销完成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
protected void configure(HttpSecurity http) throws Exception {
//super.configure(http); // 取消默认配置
http.authorizeRequests()
// 设置匹配的资源放行
.antMatchers("/layui/**","/index.jsp").permitAll()
// 剩余任何资源必须认证
.anyRequest().authenticated();

//http.logout(); //默认注销请求 请求路径:"/logout"
http.logout().logoutUrl("/logout").logoutSuccessUrl("/index.jsp");


}
1
2
3
4
5
6
<li class="layui-nav-item">
<form id="logout" action="${PATH}/logout" method="post">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
<a onclick="$('#logout').submit()">退出</a>
</form>
</li>

image-20210203222857350

  • /logout: 退出系统

  • 如果csrf 开启,必须post、方式的/logout 请求,表单中需要增加 csrf token

  • logoutUrl();退出系统需要发送的请求

  • logoutSuccessUrl();退出系统成功以后要跳转的页面地址

  • addLogoutHandler():自定义注销处理器

  • deleteCookies():指定需要删除的 cookie

  • invalidateHttpSession():session 失效(DEBUG)

6、实验六:基于角色的访问控制

1
2
3
4
5
6
7
8
9
10
11
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 允许所有人都访问静态资源
.antMatchers("/layui/**","/index.jsp").permitAll()
.antMatchers("/level1/**").hasRole("学徒")
.antMatchers("/level2/**").hasRole("大师")
.antMatchers("/level3/**").hasRole("宗师")
// 放置最后,以上没有规定的都需要权限认证
.anyRequest().authenticated();
}

注意:

1.  将.anyRequest().authenticated()错误的设置在前面,后面的设置就不起作用了。
  1. 设置所有, “/**“ 都可以访问,其他再进行的设置就不会起作用了
  2. 设置匿名访问/level3/** 可以不用登录,匿名访问:.anyRequest().anonymous();

拥有该角色的资源可以访问,否则不可以访问

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 默认认证
//super.configure(auth);


//实验四:自定义认证用户信息 - 基于内存认证方式
auth.inMemoryAuthentication()
.withUser("zhangsan").password("123456").roles("学徒","大师")
.and()
.withUser("自定义访问拒绝处理页面,lisi").password("111111").authorities("USER","MANGER");
// .withUser("lisi").password("123456").authorities("罗汉拳","武当长拳");
}

7、实验七:自定义访问拒绝处理页面

直接增加处理映射界面

1
http.exceptionHandling().accessDeniedPage("/unauth.html");

在控制器类中增加映射处理

1
2
3
4
@RequestMapping("/unauth.html")
public String unauth(){
return "unauth";
}

增加显示页面,将 main.jsp 复制,命名为 unauth.jsp,增加一句提示信息

1
<h1>你无权访问该页面...</h1>

image-20210203225722734

测试显示效果

image-20210203225758442

8、实验八:记住我功能

8.1 记住我功能-免登录原理

1
http.rememberMe();

默认规则

  • 页面 checkbox 提交 remember-me参数

image-20210203230539852

  • 默认记住 2 周登录状态:AbstractRememberMeServices

image-20210203230712762

  • 会在cookie中保存名为:remember-me 的 cookie

image-20210203230802123

  • 登录后页面,关闭浏览器,直接访问:http://localhost:8080/SpringSecurity/main.html 可以成功访问,不必登录。
  • 这种方式,token 值是放置在内存中的,服务器端重启 tomcat,token 会失效。需要将 token 记录在数据库持久化才不会失效。

8.2 记住我-数据版

引入 pom.xml 包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>4.3.20.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>

配置数据源

1
2
3
4
5
6
7
8
9
10
11
<!-- 配置数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="root"></property>
<property name="password" value="root"></property>
<property name="url" value="jdbc:mysql://localhost:3306/security?useSSL=false"></property>
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
</bean>
<!-- jdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>

创建表

1
2
3
4
5
6
create table persistent_logins (
username varchar(64) not null,
series varchar(64) primary key,
token varchar(64) not null,
last_used timestamp not null
);

image-20210203231619271

image-20210203231727138

设置记住我

1
2
3
4
5
6
7
8
9
10
@Autowired
DataSource dataSource;

@Override
protected void configure(HttpSecurity http) throws Exception {
//记住我
JdbcTokenRepositoryImpl ptr = new JdbcTokenRepositoryImpl();
ptr.setDataSource(dataSource);
http.rememberMe().tokenRepository(ptr);
}

image-20210203232100434

image-20210203232126591

四、认证

1、自定义 UserDatailsService 检索用户

创建表结构

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
CREATE TABLE `t_admin` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`loginacct` varchar(255) NOT NULL,
`userpswd` char(32) NOT NULL,
`username` varchar(255) NOT NULL,
`email` varchar(255) NOT NULL,
`createtime` char(19) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8 COMMENT='';

insert into `t_admin`(`id`,`loginacct`,`userpswd`,`username`,`email`,`createtime`) values (1,'superadmin','e10adc3949ba59abbe56e057f20f883e','超级管理员','admin@atguigu.com','2019-01-12 17:18:00'),(3,'lisi','e10adc3949ba59abbe56e057f20f883e','lisi','lisi@atguigu.com','2019-01-12 17:18:00'),(4,'wangwu','f1887d3f9e6ee7a32fe5e76f4ab80d63','wangwu','wangwu@163.com','2019-01-12 17:18:00'),(8,'aaa','123456','aaa','aaa@atguigu.com','2019-01-12 17:18:00'),(12,'xxxx','e10adc3949ba59abbe56e057f20f883e','xxxx','xxxx@163.com','2019-01-21 10:54:36'),(13,'yy','e10adc3949ba59abbe56e057f20f883e','yy','yy@atguigu.com','2019-01-21 10:56:49'),(14,'qqq','e10adc3949ba59abbe56e057f20f883e','qqqq','qqq@atguigu.com','2019-01-21 11:00:01'),(15,'qqq456','e10adc3949ba59abbe56e057f20f883e','测试中文123','qqq654@atguigu.com','2019-01-21 11:15:53');

CREATE TABLE `t_menu` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`pid` int(11) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`icon` varchar(255) DEFAULT NULL,
`url` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8 COMMENT='';

insert into `t_menu`(`id`,`pid`,`name`,`icon`,`url`) values (1,0,'控制面板','glyphicon glyphicon-dashboard','main.html'),(2,0,'权限管理','glyphicon glyphicon glyphicon-tasks',NULL),(3,2,'用户维护','glyphicon glyphicon-user','admin/index.html'),(4,2,'角色维护','glyphicon glyphicon-king','role/index.html'),(5,2,'权限维护','glyphicon glyphicon-lock','permission/index.html'),(6,2,'菜单维护','glyphicon glyphicon-th-list','menu/index.html'),(7,0,'业务审核','glyphicon glyphicon-ok',NULL),(8,7,'实名认证审核','glyphicon glyphicon-check','auth_cert/index.html'),(9,7,'广告审核','glyphicon glyphicon-check','auth_adv/index.html'),(10,7,'项目审核','glyphicon glyphicon-check','auth_project/index.html'),(11,0,'业务管理','glyphicon glyphicon-th-large',NULL),(12,11,'资质维护','glyphicon glyphicon-picture','cert/index.html'),(13,11,'分类管理','glyphicon glyphicon-equalizer','certtype/index.html'),(14,11,'流程管理','glyphicon glyphicon-random','process/index.html'),(15,11,'广告管理','glyphicon glyphicon-hdd','advert/index.html'),(16,11,'消息模板','glyphicon glyphicon-comment','message/index.html'),(17,11,'项目分类','glyphicon glyphicon-list','projectType/index.html'),(18,11,'项目标签','glyphicon glyphicon-tags','tag/index.html'),(19,0,'参数管理','glyphicon glyphicon-list-alt','param/index.html');

CREATE TABLE `t_permission` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '',
`name` varchar(255) DEFAULT NULL COMMENT '',
`title` varchar(255) DEFAULT NULL COMMENT '',
`icon` varchar(255) DEFAULT NULL,
`pid` int(11) DEFAULT NULL COMMENT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8 COMMENT='';

insert into `t_permission`(`id`,`name`,`title`,`icon`,`pid`) values (1,NULL,'用户模块','glyphicon glyphicon-user',0),(2,'user:add','新增','glyphicon glyphicon-plus',1),(3,'user:delete','删除','glyphicon glyphicon-remove',1),(4,'user:update','更新','glyphicon glyphicon-pencil',1),(5,'user:get','查询','glyphicon glyphicon-zoom-in',1),(6,'user:assign:role','授予角色','glyphicon glyphicon-user',1),(7,NULL,'角色模块','glyphicon glyphicon-heart',0),(8,'role:add','新增','glyphicon glyphicon-plus',7),(9,'role:delete','删除','glyphicon glyphicon-remove',7),(10,'role:get','查询','glyphicon glyphicon-zoom-in',7),(11,'role:update','修改','glyphicon glyphicon-pencil',7),(12,'role:assign:permission','授予权限','glyphicon glyphicon-user',7),(13,NULL,'菜单模块','glyphicon glyphicon-th-list',0),(14,'menu:add','新增','glyphicon glyphicon-plus',13),(15,'menu:delete','删除','glyphicon glyphicon-remove',13),(16,'menu:update','修改','glyphicon glyphicon-pencil',13),(17,'menu:get','查询','glyphicon glyphicon-zoom-in',13),(18,'menu:assign:permission','授予权限','glyphicon glyphicon-user',13);

CREATE TABLE `t_role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=44 DEFAULT CHARSET=utf8 COMMENT='';

insert into `t_role`(`id`,`name`) values (1,'PM - 项目经理'),(2,'SE - 软件工程师'),(3,'PG - 程序员'),(4,'TL - 组长'),(5,'GL - 组长'),(6,'QA - 品质保证'),(7,'QC - 品质控制'),(8,'SA - 软件架构师'),(9,'CMO / CMS - 配置管理员'),(10,'测试工程师'),(11,'审批人员');

CREATE TABLE `t_role_permission` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`roleid` int(11) DEFAULT NULL,
`permissionid` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `FK_Reference_3` (`roleid`),
KEY `FK_Reference_4` (`permissionid`),
CONSTRAINT `FK_Reference_4` FOREIGN KEY (`permissionid`) REFERENCES `t_permission` (`id`),
CONSTRAINT `FK_Reference_3` FOREIGN KEY (`roleid`) REFERENCES `t_role` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8 COMMENT='';

insert into `t_role_permission`(`id`,`roleid`,`permissionid`) values (7,1,1),(8,1,2),(9,1,6),(10,1,13),(11,1,16),(12,1,17),(13,1,18);

CREATE TABLE `t_admin_role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`adminid` int(11) DEFAULT NULL,
`roleid` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `FK_Reference_1` (`adminid`),
KEY `FK_Reference_2` (`roleid`),
CONSTRAINT `FK_Reference_2` FOREIGN KEY (`roleid`) REFERENCES `t_role` (`id`),
CONSTRAINT `FK_Reference_1` FOREIGN KEY (`adminid`) REFERENCES `t_admin` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8 COMMENT='';

insert into `t_admin_role`(`id`,`adminid`,`roleid`) values (2,1,2),(9,1,4),(11,1,8),(12,15,1),(13,15,2),(14,15,3);


CREATE TABLE `t_permission_menu` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`menuid` int(11) DEFAULT NULL,
`permissionid` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `FK_Reference_10` (`menuid`),
KEY `FK_Reference_9` (`permissionid`),
CONSTRAINT `FK_Reference_10` FOREIGN KEY (`menuid`) REFERENCES `t_menu` (`id`),
CONSTRAINT `FK_Reference_9` FOREIGN KEY (`permissionid`) REFERENCES `t_permission` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8 COMMENT='';

insert into `t_permission_menu`(`id`,`menuid`,`permissionid`) values (7,2,1),(8,2,2),(9,2,3),(10,2,4),(11,2,5),(12,2,6),(13,2,7),(14,2,8),(15,2,9),(16,2,10),(17,2,11),(18,2,12);

配置 configure(AuthenticationManagerBuilder auth)

1
2
3
4
5
6
7
8
@Autowired
UserDetailsService userDetailsService;//用户详情查询服务组件的接口

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//根据用户名查询出用户的详细信息
auth.userDetailsService(userDetailsService);
}

编写 UserDetailService 实现

image-20210204160515382

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Component
public class UserDetailsServiceImpl implements UserDetailsService {

@Autowired
JdbcTemplate jdbcTemplate;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String queryUser = "SELECT * FROM `t_admin` WHERE loginacct=?";

//1、查询指定用户的信息
Map<String, Object> map = jdbcTemplate.queryForMap(queryUser, username);

//2、将查询到的用户封装到框架使用的UserDetails里面
return new User(map.get("loginacct").toString(), map.get("userpswd").toString(),
AuthorityUtils.createAuthorityList("ADMIN", "USER"));//暂时写死,过后数据库中查
}

}

运行测试结果,密码不一致,跳转到登录页,并提示错误消息

image-20210204160556591

2、基于数据库(MD5 密码)认证

2.1 配置 configure(AuthenticationManagerBuilder auth)

1
2
3
4
5
6
7
8
9
10
@Autowired
UserDetailsService userDetailsService;//用户详情查询服务组件的接口

@Autowired
PasswordEncoder passwordEncoder;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}

2.2 引入 MD5 加密工具类:MD5Util.java

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
package com.oy.security.component;

import java.security.MessageDigest;

/**
* MD5算法 哈希算法 MD5算法具有以下特点: 1、压缩性:任意长度的数据,算出的MD5值长度都是固定的。 2、容易计算:从原数据计算出MD5值很容易。
* 3、抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
* 4、强抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。
*/
public class MD5Util {
public static String digest16(String inStr) {
return digest(inStr, 16);
}

public static String digest(String inStr) {
return digest(inStr, 32);
}

private static String digest(String inStr, int rang) {
MessageDigest md5 = null;
if (StringUtil.isEmpty(inStr)) {
return "";
}

try {
md5 = MessageDigest.getInstance("MD5"); // 取得算法
} catch (Exception e) {
e.printStackTrace();
return "";
}

char[] charArray = inStr.toCharArray();
byte[] byteArray = new byte[charArray.length];

for (int i = 0; i < charArray.length; i++) {
byteArray[i] = (byte) charArray[i];
}

byte[] md5Bytes = md5.digest(byteArray); // 加密

StringBuilder hexValue = new StringBuilder();

for (int i = 0; i < md5Bytes.length; i++) {
int val = ((int) md5Bytes[i]) & 0xff;
if (val < 16)
hexValue.append("0");
hexValue.append(Integer.toHexString(val));
}
if (rang == 32) {
return hexValue.toString();
} else {
return hexValue.toString().substring(8, 24);// 转换为32位字符串
}
}

public static void main(String args[]) {
String s = new String("123456");
System.out.println(digest(s));
}
}

2.3 PasswordEncoder 接口实现类:PasswordEncoderImpl

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
@Service
public class PasswordEncoderImpl implements PasswordEncoder {

/**
* 密码加密的算法
* @param charSequence
* @return
*/
@Override
public String encode(CharSequence charSequence) {
return MD5Util.digest(charSequence.toString());
}

/**
* 比较登录密码和数据库存储密码是否一致
* @param charSequence 页面的明文密码
* @param s 数据库的密文密码
* @return
*/

@Override
public boolean matches(CharSequence charSequence, String s) {
return s.equals(MD5Util.digest(charSequence.toString()));
}
}

3、基于数据库(BCryptPasswordEncoder)密码加密认证

3.1 PasswordEncoder 接口

image-20210204163302959

3.2 使用 BCryptPasswordEncoder 进行密码加密

1
2
3
4
5
 /**
* 推荐密码加密器用这个 BCryptPasswordEncoder; 将一个字符串加密成一个永不重复的密文
*/
// 1、加盐+加随机数
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());

3.3 本地测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class BCryptPasswordEncoderTest {

@Test
public void test(){
BCryptPasswordEncoder pe = new BCryptPasswordEncoder();

String encode = pe.encode("123456");
System.out.println(encode);

// 1. $2a$10$9MeQiXyoq.DVWmdNPC7/TuiwOPCSmeT0xLbMWkUrbAF5.Pnq2mdEK
// 2. $2a$10$gqjnDfOJ/c9Stvz6pQFXLOHxIQzSpOQXgcgJxpsMgglAtDrMY2Kda
// 3. $2a$10$tnjbeb0J1vUdynJeu.IYiu5TPQwmrBUiYa0pfgIZIIuQ3r4yIt/ea
}
}

3.4 服务器运行测试

将生成的密文存储到数据库中(注意:userpswd字段长度)把 userpaswd 字段长度扩大,重新启动服务器进行测试。

image-20210204164520586