一、简介
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两个类。
-
InvocationSecurityMetadataSourceService
将配置文件或数据库中存储的资源(url)提取出来加工成为url和权限列表的Map供Security使用
-
UserDetailsService
-
提取用户名和权限组成一个完整的(UserDetails)User对象,该对象可以提供用户的详细信息供AuthentationManager进行认证与授权使用
3、概念
认证
authenfication
: 身份验证
“身份验证” 是指建立主体(principal)的过程,主体就是他们声称的是谁 (“主体” 通常指用户、设备或在应用程序中可以执行动作的其他系统)。也就是 “证明你是谁”。
授权
authorization
: 授权
“授权” 是指确定主体(principal) 是否被允许执行系统中某个动作的过程。也就是 “你能做什么!”
为了达到“授权”决策(安全框架决定你是否有权限做此事),“身份验证”(authentication)过程已经建立了主体的身份(Principal)
二、SpringSecurity-HellWorld
1、测试环境搭建
1.1 创建普通的maven-war 工程:spring-security-helloworld
注意:
以下所有的测试都采用的 Idea 集成开发工具
pom 文件增加依赖
<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配置
<?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
<?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>
导入实验资源
- 需要资源的博客末尾有下载地址
- 导入页面
- 导入 controller
运行测试
2、引入SpringSecurity框架
2.1 添加 security-pom 依赖
<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 进行安全控制
<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 注解作用
@Configuration //声明当前类是一个配置类。相当与XML配置文件作用。
@EnableWebSecurity //声明式配置,启用SpringSecurity安全机制。
public class AppWebSecurityConfig extends WebSecurityConfigurerAdapter {
}
2.4 启动测试效果
- 所有资源访问受限(包括静态资源)
- 框架自带一个默认的登录界面
- 账号密码错误会有提示
- 查看登录页面的源码,发现有个hidden-input: name = “
_csrf
” 这个是springsecurity 帮我们防止“跨站请求伪造”
攻击;还可以防止表单重复提交。
三、SpringSecurity 实验
1、实验一: 授权首页和静态资源
- 配置类 (AppWebSecurityConfig extends WebSecurityConfigurerAdapter)
- 重写configure(HttpSecurity http)方法
@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 都可以访问
不存在的资源
- 有权限无资源 400
- 无权限
2、实验二: 默认及自定义登录页
@Override
protected void configure(HttpSecurity http) throws Exception {
//super.configure(http); // 取消默认配置
http.authorizeRequests()
// 设置匹配的资源放行
.antMatchers("/layui/**","/index.jsp").permitAll()
// 剩余任何资源必须认证
.anyRequest().authenticated();
// 实验二: 默认及自定义登录页
http.formLogin(); // 默认登录页
}
- http://localhost:8080/SpringSecurity/level3/3
@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
这个功能
http.csrf().disable();
3、实验三: 自定义表单登录逻辑分析
@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();
如果没有关闭 CSRF, 提交请求被拒绝, 需要暂时禁用csrf:http.csrf().disable();
- 表单提交请求失败,提取错误消息:${SPRING_SECURITY_LAST_EXCEPTION.message}
<div class="layadmin-user-login-box layadmin-user-login-header">
<h2>layuiAdmin</h2>
<p>layui 官方出品的单页面后台管理模板系统</p>
<p>${SPRING_SECURITY_LAST_EXCEPTION.message}</p>
</div>
4、实验四: 自定义认证用户信息
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 默认认证
//super.configure(auth);
//实验四:自定义认证用户信息 - 基于内存认证方式
auth.inMemoryAuthentication()
.withUser("zhangsan").password("123456").roles("学徒","大师")
.and()
.withUser("lisi").password("123456").authorities("罗汉拳","武当长拳");
}
输入用户名和密码之后一致,就可以登录页面后台,前提是与自己基于内存认证方式一致的用户名和密码
- CSRF 跨站请求伪造
- SpringSecurity 添加了 crsf 功能 【DefaultCsrfToken】,所有的表单提交为了防止跨站请求的伪造,我们需要加上_csrf项;或者,暂时禁用
http.csrf().disable();
// 可以在页面任意位置加,没有唯一性
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
5、实验五:用户注销完成
@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");
}
<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>
-
/logout: 退出系统
-
如果csrf 开启,必须post、方式的/logout 请求,表单中需要增加 csrf token
-
logoutUrl();退出系统需要发送的请求
-
logoutSuccessUrl();退出系统成功以后要跳转的页面地址
-
addLogoutHandler():自定义注销处理器
-
deleteCookies():指定需要删除的cookie
-
invalidateHttpSession():session失效(DEBUG)
6、实验六:基于角色的访问控制
@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()错误的设置在前面,后面的设置就不起作用了。
2. 设置所有, "/**" 都可以访问,其他再进行的设置就不会起作用了
3. 设置匿名访问/level3/** 可以不用登录,匿名访问:`.anyRequest().anonymous();`
拥有该角色的资源可以访问,否则不可以访问
@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、实验七:自定义访问拒绝处理页面
直接增加处理映射界面
http.exceptionHandling().accessDeniedPage("/unauth.html");
在控制器类中增加映射处理
@RequestMapping("/unauth.html")
public String unauth(){
return "unauth";
}
增加显示页面,将main.jsp复制,命名为unauth.jsp,增加一句提示信息
<h1>你无权访问该页面...</h1>
测试显示效果
8、实验八:记住我功能
8.1 记住我功能-免登录原理
http.rememberMe();
默认规则
- 页面checkbox提交
remember-me
参数
- 默认记住2周登录状态:
AbstractRememberMeServices
- 会在cookie中保存名为:remember-me的cookie
- 登录后页面,关闭浏览器,直接访问:http://localhost:8080/SpringSecurity/main.html 可以成功访问,不必登录。
- 这种方式,token值是放置在内存中的,服务器端重启tomcat,token会失效。需要将token记录在数据库持久化才不会失效。
8.2 记住我-数据版
引入pom.xml 包
<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>
配置数据源
<!-- 配置数据源 -->
<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>
创建表
create table persistent_logins (
username varchar(64) not null,
series varchar(64) primary key,
token varchar(64) not null,
last_used timestamp not null
);
设置记住我
@Autowired
DataSource dataSource;
@Override
protected void configure(HttpSecurity http) throws Exception {
//记住我
JdbcTokenRepositoryImpl ptr = new JdbcTokenRepositoryImpl();
ptr.setDataSource(dataSource);
http.rememberMe().tokenRepository(ptr);
}
四、认证
1、自定义UserDatailsService检索用户
创建表结构
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)
@Autowired
UserDetailsService userDetailsService;//用户详情查询服务组件的接口
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//根据用户名查询出用户的详细信息
auth.userDetailsService(userDetailsService);
}
编写UserDetailService实现
@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"));//暂时写死,过后数据库中查
}
}
运行测试结果,密码不一致,跳转到登录页,并提示错误消息
2、基于数据库(MD5密码)认证
2.1 配置 configure(AuthenticationManagerBuilder auth)
@Autowired
UserDetailsService userDetailsService;//用户详情查询服务组件的接口
@Autowired
PasswordEncoder passwordEncoder;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
2.2 引入MD5加密工具类:MD5Util.java
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
@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接口
3.2 使用BCryptPasswordEncoder进行密码加密
/**
* 推荐密码加密器用这个 BCryptPasswordEncoder; 将一个字符串加密成一个永不重复的密文
*/
// 1、加盐+加随机数
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
3.3 本地测试
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 字段长度扩大,重新启动服务器进行测试。