一.认证授权流程
1.认证授权流程
SpringSecurity是基于Filter实现认证和授权,底层通过FilterChainProxy代理去调用各种Filter(Filter链),Filter通过调用AuthenticationManager完成认证 ,通过调用AccessDecisionManager完成授权。流程如下图:
2.Security过滤器链
我们知道,SpringSecurity是通过很多的过滤器链共同协作完成认证,授权的流程,SpringSecurity中核心的过滤器链如下:
3.SpringSecurity认证流程原理
4.定义认证流程
在SpringSecurity的整个认证流程中,除了UserDetailsService需要我们自己定义外,其他的的组件都可以使用默认的,因为UserDetailsService是SpringSecurity获取数据库中的认证信息的媒介,而如何才能从数据库中获取认证信息只有我们才知道。在入门案例中我们使用的是InMemoryUserDetailsManager 基于内存的UserDetailsService方案,接下来我们需要把基于内存的方案修改为基于数据库的方案。
5.定义UserDetailsService
UserDetailsService
是SpringSecurity提供用来获取认证用户信息(用户名,密码,用户的权限列表)的 接 口,我们可以实现该接口,复写loadUserByUsername(username) 方法加载我们数 据 库中的用户信息
UserDetails
UserDetails是SpringSecurity用来封装用户认证信息,权限信息的对象,我们使用它 的实现类User封装用户信息 并返回,我们这里从数据库查询用户名
5.创建类UserDetailServiceImpl实现UserDetailsService接口
package cn.x.th.userService;
import cn.x.th.domain.Permission;
import cn.x.th.domain.VipUser;
import cn.x.th.mapper.VipUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private VipUserMapper vipUserMapper;
@Override//根据参数中的用户名,来查询用户的对象信息
//1.整个方法体中,并没有对前端传过来的密码进行校验。
//2.前端传过来的用户密码,后台代码根本就没有提供获取的方法。
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
VipUser vipUser = vipUserMapper.findUserByUserName(userName);
List<GrantedAuthority> authorityList = new ArrayList<>();//在传入时,会同时查询出当前用户的权限列表
//查询当前用户所有的权限
//在security进行授权管理时,就要在这里将当前登录用户的权限列表给查出来
List<Permission> permissionList = vipUserMapper.findPermissionListByUserName(userName);
//for (Permission permission:permissionList) {
//将查询来的权限逐一添加到authorityList中
//authorityList.add(new SimpleGrantedAuthority(permission.getExpression()));
// }
//密码最终校验的地方是由Security框架完成。 原始密码保存在SecurityContext上下文,传入一个加密之后的密码:vipUser.getPassword()
return new User(userName, vipUser.getPassword(),authorityList);
}
}
注意:这里定义了UserDetailSerice后,WebSecurityConfig中不在需要定义UserDetailService的Bean需要移除
6.导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- swagger-ui的依赖-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- springsecurity权限框架的环境依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 导入mybatis-plus相关的依赖-->
<!-- 导入数据库连接相关的依赖-->
<!-- 数据库驱动和druid-->
<!-- 注意坑: mybatis-plus3.x后,在自动生成的实体类中,对日期类型进行了升级,使用的是jdk1.8的LocalDate
要支持这种数据类型,必须对连接池druid和mysql驱动进行升级,升级版本如下:
-->
<!-- MySql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
<!-- druid连接池 这个starter的坐标实际上也只是导入了关联版本的druid连接池坐标,所以这里直接用导入druid坐标替代-->
<!--<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
</dependency>-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.1</version>
</dependency>
<!-- mybatis-plus3的依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
</dependencies>
注意:检查父工程有没有继承下面的依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
</parent>
7.yml配置
server:
port: 1200
#配置数据库的连接
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 连接池指定 springboot2.02版本默认使用HikariCP 此处要替换成Druid
driver-class-name: com.mysql.cj.jdbc.Driver # 这个驱动必须用新版,不能用老版
url: jdbc:mysql:///hrm-security?characterEncoding=utf-8&serverTimezone=GMT%2B8
username: root
password: 123456
druid:
initial-size: 5 # 初始化时建立物理连接的个数
min-idle: 5 # 最小连接池连接数量,最小空闲数量
max-active: 20 # 最大连接池连接数量,最大活跃连接数
max-wait: 60000 # 配置获取连接等待超时的时间
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1
testWhileIdle: true
testOnBorrow: true
testOnReturn: false
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
filters: stat,wall
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
stat-view-servlet:
allow: 0.0.0.0 # 允许哪些IP访问druid监控界面,多个IP以逗号分隔
login-username: admin # 设置登录帐号
login-password: 123456 # 设置登录密码
reset-enable: false # 是否允许重置数据
# url-pattern: /database/* # 默认访问根路径是:/druid/;也可以自定义设置
mybatis-plus: # mybatis的配置。下面的配置希望执行Sql语句可以打印到 控制台
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 开启mybatis-plus的sql打印
8.编写Mapper.xml
9.编写Mapper映射器接口
10.创建数据库 :sql见资料:auth-rbac.sql(可在资源里下载)
11.定义密码编码器
@Bean
public PasswordEncoder passwordEncoder(){
//return NoOpPasswordEncoder.getInstance();//不加密
return new BCryptPasswordEncoder();//加密
}
12.自定义登录页面
resource里准备登录页面static/login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登陆</title>
</head>
<body>
<h1>登陆</h1>
<form method="post" action="/login">
<div>
用户名:<input type="text" name="username">
</div>
<div>
密码:<input type="password" name="password">
</div>
<div>
<button type="submit">立即登陆</button>
</div>
</form>
</body>
</html>
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)//开启授权
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//密码编码器:不加密(权限框架提供了设置密码加密方式的配置对象)
@Bean
public PasswordEncoder passwordEncoder(){
// return NoOpPasswordEncoder.getInstance();//不加密
return new BCryptPasswordEncoder();//加密
}
//授权规则配置
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() //授权配置
.antMatchers("/login","/login.html").permitAll() //登录路径放行
.anyRequest().authenticated() //其他路径都要认证之后才能访问
.and().formLogin() //允许表单登录
.loginPage("/login.html") //这个就是自定义的登录页面
.loginProcessingUrl("/login") //告诉框架,现在的登录请求的URL地址是:/login
.successForwardUrl("/loginSuccess") // 设置登陆成功页
.successHandler(new MyAuthenticationSuccessHandler())//设置认证成功后,handler的处理器
.failureHandler(new MyAuthenticationFailureHandler())//设置认证失败后handler的处理器
.and().logout().permitAll() //登出路径放行 /logout。这是框架自带的登出请求
.and().csrf().disable(); //关闭跨域伪造检查
}
}
13.mapper层
public interface VipUserMapper extends BaseMapper<VipUser> {
@Select("select * from t_vip_user where username = #{userName}")
VipUser findUserByUserName(String username);
//List<Permission> findPermissionListByUserName(String userName);
}
14.测试