上次作业问题
SpringSecurity
SpringSecurity简介
项目环境搭建
SpringSecurity认证
1.作业问题
1-1 axios文件上传并提交表单数据 如何实现?
方法1:
html页面
<template id="userAddTempl">
<div>
<h3>用户添加</h3>
<hr>
<p>编码: <input type="text" v-model="user.usercode"></p>
<p>密码: <input type="text" v-model="user.userpassword"></p>
<p>姓名: <input type="text" v-model="user.userName"></p>
<p>性别:
<select v-model="user.gender">
<option value="">请选择</option>
<option value="1">男</option>
<option value="2">女</option>
</select>
</p>
<p>生日: <input type="date" v-model="user.birthday"></p>
<p>电话: <input type="text" v-model="user.phone"></p>
<p>地址: <input type="text" v-model="user.address"></p>
<p>角色: <select v-model="user.roleid">
<option value="">请选择</option>
<option :value="role.id" v-for="role in roleList">{{role.rolename}}</option>
</select>
</p>
<p>头像: <input type="file" id="avatarImg" style="width:200px;"></p>
<p>
<input type="button" value="确定" @click="doAddUser()">
<input type="button" value="返回列表" @click="goBack()">
</p>
</div>
</template>
methods:{
doAddUser(){
let file = document.getElementById("avatarImg").files[0];
console.log(file);
let formData = new FormData();
//这里将表单数据封装成json保存至formData中
formData.append("formUser",new Blob([JSON.stringify(this.user)],{type: "application/json"}));
formData.append("uploadFile",file);
const config = {
headers: { "Content-Type": "multipart/form-data;boundary="+new Date().getTime() }
};
axios.post("/doAddUser",formData,config).then(res=>{
if(res.status==200){
alert("添加成功!");
this.$router.push("/userList");
}
});
}
},
Controller:
/*
* 1.@RequestPart这个注解用在multipart/form-data表单提交请求的方法上。
* 2.支持的请求方法的方式MultipartFile,属于Spring的MultipartResolver类。这个请求是通过http协议传输的。
* 3.@RequestParam也同样支持multipart/form-data请求。
* 4.他们最大的不同是,当请求方法的请求参数类型不再是String类型的时候。
* 5.@RequestParam适用于name-valueString类型的请求域,@RequestPart适用于复杂的请求域(像JSON,XML)。
*/
@RequestMapping("/doAddUser")
public ResponseEntity<String> doAddUser(@RequestPart("formUser") User user, MultipartFile uploadFile) {
//idpicpath
System.out.println(user);
try {
InputStream inputStream = uploadFile.getInputStream();
String fname = uploadFile.getOriginalFilename();
String suffix = fname.substring(fname.lastIndexOf("."));
String fileName = UUID.randomUUID().toString().replaceAll("-", "") + suffix;
String uploadPath = ResourceUtils.getURL("classpath:").getPath() + "static/upload/";
File destDir = new File(uploadPath);
if (!destDir.exists()) {
destDir.mkdirs();
}
//创建目标文件
File destFile = new File(uploadPath, fileName);
try {
//使用工具类方法将文件拷贝
FileCopyUtils.copy(uploadFile.getInputStream(), new FileOutputStream(destFile));
} catch (IOException exception) {
return new ResponseEntity<String>("上传文件失败", HttpStatus.BAD_REQUEST);
}
} catch (IOException e) {
e.printStackTrace();
}
return new ResponseEntity<String>("添加成功", HttpStatus.OK);
}
}
方法2:
methods:{
doAddUser(){
let file = document.getElementById("avatarImg").files[0];
console.log(file);
let formData = new FormData();
formData.append("usercode",this.user.userCode);
formData.append("userName",this.user.userName);
formData.append("gender",this.user.gender);
formData.append("phone",this.user.phone);
formData.append("birthday",this.user.birthday);
//....
formData.append("uploadPic",file);
const config = {
headers: { "Content-Type": "multipart/form-data;boundary="+new Date().getTime() }
};
axios.post("/doAddUser",formData,config).then(res=>{
if(res.status==200){
alert("添加成功!");
this.$router.push("/userList");
}
});
}
},
Controller:
@RequestMapping("/doAddUser")
public ResponseEntity<String> doAddUser(User user,@RequestParam("uploadPic") MultipartFile uploadFile) {
//idpicpath
System.out.println(user);
try {
InputStream inputStream = uploadFile.getInputStream();
String fname = uploadFile.getOriginalFilename();
String suffix = fname.substring(fname.lastIndexOf("."));
String fileName = UUID.randomUUID().toString().replaceAll("-", "") + suffix;
String uploadPath = ResourceUtils.getURL("classpath:").getPath() + "static/upload/";
File destDir = new File(uploadPath);
if (!destDir.exists()) {
destDir.mkdirs();
}
//创建目标文件
File destFile = new File(uploadPath, fileName);
try {
//或者: uploadFile.transferTo(destFile)
//使用工具类方法将文件拷贝 commons-fileupload.jar依赖
FileCopyUtils.copy(uploadFile.getInputStream(), new FileOutputStream(destFile));
} catch (IOException exception) {
return new ResponseEntity<String>("上传文件失败", HttpStatus.BAD_REQUEST);
}
} catch (IOException e) {
e.printStackTrace();
}
return new ResponseEntity<String>("添加成功", HttpStatus.OK);
}
1-2 使用mybatisplus执行多表连接查询
在实体类上添加了关联的字段
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("smbms_user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 用户编码
*/
@TableField("userCode")
private String userCode;
/**
* 用户名称
*/
private String userName;
/**
* 用户密码
*/
@TableField("userPassword")
private String userPassword;
/**
* 性别(1:女、 2:男)
*/
private Integer gender;
/**
* 出生日期
*/
private Date birthday;
/**
* 手机
*/
private String phone;
/**
* 地址
*/
private String address;
/**
* 用户角色(取自角色表-角色id)
*/
private Integer roleid;
/**
* 创建者(userId)
*/
@TableField("createdBy")
private Integer createdBy;
/**
* 创建时间
*/
@TableField("creationDate")
private Date creationDate;
/**
* 更新者(userId)
*/
@TableField("modifyBy")
private Integer modifyBy;
/**
* 更新时间
*/
@TableField("modifyDate")
private Date modifyDate;
@TableField("IDPICPATH")
private String idpicpath;
@TableField(exist = false) //表示mybatisplus自带的CRUD 不去使用此字段
private Role role;
}
mybatisplus内置的crud都不能执行,需要在上面添加注解
@TableField(exist = false)
2.SpringSecurity
2-1 简介
Spring Security 安全框架
|-两大核心功能:
认证 - authenticate 【动词】 、authentication [身份验证; 认证]
|- 身份进行认证的认证方式 -- 登录系统时,登录操作
鉴权 - authorization 【批准; 授权】
authority 权力
鉴权 是建立在认通过以后的操作
![image-20220704113250243](https://i-blog.csdnimg.cn/blog_migrate/3c56eca0efd23979bc6b57b35b481bf2.png)
SpringSecurity特点:
a. 身份验证和访问控制框架
b. Spring Security是一个专注于为Java Spring应用程序提供 身份验证和 授权的框架
c. Spring Security的真正强大之处在于它可以轻松扩展以满足自定义要求
SpringSecurity与shiro
Shiro比Spring security更容易使用,实现和最重要的理解
Spring Security更加知名的唯一原因是因为品牌名称
Spring-security 对Spring结合较好,如果项目用的SpringMVC,使用起来很方便。
2-2 项目环境搭建
使用Springboot脚手架-SpringSecruity Demo
![image-20220704114318187](https://i-blog.csdnimg.cn/blog_migrate/7ba671fc297b7ee82321b86d2e445c25.png)
login.html为登录页面,都可以访问
在登录成功之后,进入home.html主页
主页中有四个超链接,分别对应我们将要实现的四个功能用户管理、角色管理...
但是四个页面需要有不同的权限,主要看红色的字体
启动时,由于添加springSecurity,会出一行:
Using generated security password: 04765d11-339d-492f-b55f-eb75e1135a5a
生成的默认登录的密码
默认的用户名是user
2-3 SpringSecurity认证
(1) HttpBasic模式登录认证
SpringSecurity 自带一种基础认证模式
HttpBasic登录验证模式是Spring Security实现登录验证最简单的一种方式,也可以说是最简陋的一种方式。它的目的并不是保障登录验证的绝对安全,而是提供一种“防君子不防小人”的登录验证
我们现在使用的是spring boot2.0版本(依赖Security 5.X版本),HttpBasic不再是默认的验证模式,在spring security 5.x默认的验证模式已经是表单模式。所以我们要使用Basic模式,需要在配置类中去配置
Basic模式登录认证不是表单,而是一个简陋的对话框
实现方式:创建SpringSecurity配置类
WebSecurityConfigurerAdapter是适配器类, 在配置的时候需要我们自己写配置类去继承他,然后编写自己所特殊需要的配置
/**
* Spring Securtiy配置类
*/
@Configuration //配置类
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
}
@Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//开启httpBasci模式登录认证
http.httpBasic()
//每个模块配置使用and结尾
.and()
//配置路径拦截,表明路径访问所对应的权限,角色,认证信息
.authorizeRequests()
.anyRequest()
//所有请求都需要登录认证才能访问
.authenticated();
}
}
(2) 默认formLogin表单模式
在spring security 5.x默认的验证模式已经是表单模式,接下来要讲的formLogin模式,注释掉@Configuration,方便测试
//@Configuration //配置类
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
...
}
可以通过applicaton.yml配置用户名与密码
spring:
security:
user:
name: tom
password: tom
(3) 自定义formLogin表单模式
/**
* Spring Securtiy配置类
*/
@Configuration //配置类
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
}
@Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//开启自定义表单模式登录认证
//需要放行的url在这里配置,必须要放行/login和/login.html,不然会报错
http.authorizeRequests().antMatchers("/login", "/login.html")
.permitAll().anyRequest().authenticated()
.and().
// 设置登陆页、登录表单form中action的地址,也就是处理认证请求的路径
formLogin().loginPage("/login.html").loginProcessingUrl("/login")
//登录表单form中密码输入框input的name名,不修改的话默认是password
.usernameParameter("username").passwordParameter("password")
//登录认证成功后默认转跳的路径
.defaultSuccessUrl("/home");
//关闭CSRF跨域攻击防御
http.csrf().disable();
}
}
注:由于我们现在是使用静态login.html登录,此页面没有读取服务端生成的token值,在这里我们暂时关闭csrf跨域攻击的防御机制
(4) 认证的流程底层分析
自定义formLogin表单认证流程分析:
javax.servlet.Filter接口
当第一次发起请求时,首先调用DelegatingFilterProxy过滤器的doFilter()方法
DelegatingFilterProxy就是一个对于servlet filter的代理,用这个类的优势主要是通过Spring容器来管理servlet filter的生命周期,本质上来说DelegatingFilterProxy就是一个Filter
启动一个FilterProxyChain 过滤器代理的链 【根据请求加载一组过滤器】
DelegatingFilterProxy:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
103行设置断点: Filter delegateToUse = this.delegate;
//...
}
FilterChainProxy过滤器链接代理类
在FilterChainProxy的dofilter方法中,会去调用来获取该过滤链里面的过滤器的个数,然后采用设计模式中的责任链模式依次顺序调用对应的过滤器的dofilter方法,一共是11个(WebAsyncManagerIntegrationFilter、SecurityContextPersistenceFilter、HeaderWriterFilter 、LogoutFilter 、UsernamePasswordAuthenticationFilter、RequestCacheAwareFilter、SecurityContextHolderAwareRequestFilter、AnonymousAuthenticationFilter、SessionManagementFilter、ExceptionTranslationFilter、FilterSecurityInterceptor)
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (this.currentPosition == this.size) {
if (FilterChainProxy.logger.isDebugEnabled()) {
FilterChainProxy.logger.debug(UrlUtils.buildRequestUrl(this.firewalledRequest) + " reached end of additional filter chain; proceeding with original chain");
}
this.firewalledRequest.reset();
this.originalChain.doFilter(request, response);
} else {
++this.currentPosition;
Filter nextFilter = (Filter)this.additionalFilters.get(this.currentPosition - 1);
if (FilterChainProxy.logger.isDebugEnabled()) {
FilterChainProxy.logger.debug(UrlUtils.buildRequestUrl(this.firewalledRequest) + " at position " + this.currentPosition + " of " + this.size + " in additional filter chain; firing Filter: '" + nextFilter.getClass().getSimpleName() + "'");
}
// 170行 断点 nextFilter.doFilter(request, response, this);
}
}
![image-20220704154921614](https://i-blog.csdnimg.cn/blog_migrate/2cb35a00d94eb78d4b8e472e9cbc7078.png)
(5) PasswordEncoder密码加密
public interface PasswordEncoder {
String encode(CharSequence var1); //将原始密码加密 '123'
boolean matches(CharSequence var1, String var2); //密码匹配参数1为原始密码
default boolean upgradeEncoding(String encodedPassword) {
return false; //设置是否需要升级
}
}
步骤1: 首先在启动类上创建 Bean注解方法
@SpringBootApplication
public class SpringbootSecurityDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootSecurityDemoApplication.class, args);
}
//创建一个PasswordEncoder加密器存入容器中
@Bean
public PasswordEncoder getPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
步骤2: 在测试类中测试PasswordEncoder方法
@SpringBootTest
class SpringbootSecurityDemoApplicationTests {
@Autowired
private PasswordEncoder passwordEncoder;
@Test
void contextLoads() {
String pwd1 = passwordEncoder.encode("123");
String pwd2 = passwordEncoder.encode("123");
System.out.println(pwd1); //$2a$10$21Ae7HsPihdZNu.YWhqeHu4dJ5/45l5mpQxN8P3HWzPNlDeh84cm2
System.out.println(pwd2); //$2a$10$000Misx3wpASVQkzz.iyK.q9L9qx7Thl7mey/UzoLEAvVJgenvGc.
System.out.println(passwordEncoder.matches("123",pwd1));
System.out.println(passwordEncoder.matches("123",pwd2));
//$2a$10$s65QPon1AZTjZhQjlL.jF.8JBeIjdPLoL.UfZkDA5Uzv94yQhq.RG
//$2a$10$/Rs7Zr41nC6UWgP/Ij0tnOnrBwMLjwRDYrGJrNMzMMlWQB5XgmBJe
}
}
步骤3: 在配置类中使用加密的方式在内存中配置用户名和密码,修改类WebSecurityConfig
/**
* Spring Securtiy配置类
*/
@Configuration //配置类
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("tom")
.password(passwordEncoder.encode("123")).roles();
auth.inMemoryAuthentication().withUser("admin")
.password(passwordEncoder.encode("admin")).roles();
}
...
}