Java阶段三Day06
文章目录
- Java阶段三Day06
- 同步请求和异步请求
- 案例演示
- 创建SpringBoot工程
- application.properties
- UserController
- UserMapper
- 静态页面
- JSON
- Spring Security
- 引入`SpringSecurity`框架
- 对项目的影响
- 关于SpringSecurity的配置
- 默认登录表单
- 设置白名单
- 模拟登录
- 使用自己的页面进行登录
- CSRF
- 关闭跨域攻击防御机制
- 通过真实数据进行登录
- 统一异常处理
- Security框架认证流程总结
- 教师总结
- 同步请求和异步请求
- 创建SpringBoot工程
- JSON
- SpringSecurity框架流程
- Security框架认证流程:
同步请求和异步请求
- 同步:指单线程依次做几件事
- 异步:指多线程同时做几件事
- 同步请求:指客户端浏览器只有一个主线程,主线程既要负责页面渲染 \ 监听用户的页面操作还需要负责发请求,当主线程发出请求时,会将页面中的内容清空,直到服务器响应了数据之后再将响应的数据展示出来,这个过程称为页面的整体刷新,同步请求无法实现页面的局部刷新
- 异步请求:指客户端浏览器由主线程否则页面渲染和监听用户的页面的操作,由子线程负责发出请求,当子线程请求到数据之后,可以把得到的数据显示到原页面中,这就是页面的局部刷新
案例演示
创建SpringBoot工程
- 工程导入依赖3个√(
MyBatis Framework
、MySQL Driver
、Spring Web
)
-
在pom.xml添加以下依赖
<!--引入Lombok依赖--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!--添加Knife4j依赖--> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-openapi2-spring-boot-starter</artifactId> <version>4.1.0</version> </dependency> <!--添加spring-validation依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/blog?serverTimezone=Asia/Shanghai&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root
# 设置MyBatis框架的映射(Mapper)配置文件的位置
mybatis.mapper-locations=classpath:mappers/*.xml
logging.level.com.liner=debug
UserController
@RestController
@RequestMapping("/v1/users/")
public class UserController {
@Autowired(required = false)
UserMapper mapper;
@RequestMapping("reg")
public ResultVO reg(@RequestBody UserRegDTO userRegDTO){
System.out.println("userRegDTO = " + userRegDTO);
//判断用户名是否存在
UserVO userVO = mapper.selectByUsername(userRegDTO.getUsername());
if (userVO!=null){
return new ResultVO(StatusCode.USERNAME_ERROR);
}
User user = new User();
BeanUtils.copyProperties(userRegDTO,user);
user.setCreated(new Date());
mapper.insert(user);
//当给客户端响应的是Java对象时SpringMVC框架会自动将Java对象转成
//Json格式的字符串,然后将字符串响应给客户端.
return new ResultVO(StatusCode.SUCCESS);
}
}
UserMapper
public interface UserMapper {
UserVO selectByUsername(String username);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.liner.mapper.UserMapper">
<select id="selectByUsername" resultType="com.liner.pojo.vo.UserVO">
SELECT id, password, nickname
FROM user
WHERE username = #{username}
</select>
</mapper>
静态页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册</title>
</head>
<body>
<div>
<input type="text" placeholder="用户名" v-model="user.username">
<input type="text" placeholder="密码" v-model="user.password">
<input type="text" placeholder="昵称" v-model="user.nickname">
<input type="button" value="注册" @click="reg()">
</div>
<!--引入vue框架文件-->
<script src="https://cdn.staticfile.org/vue/2.6.14/vue.min.js"></script>
<!--引入axios框架文件-->
<script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
<script>
let v = new Vue({
el:"body>div",
data:{
user:{username:"",password:"",nickname:""}
},
methods:{
reg(){
//发出异步请求response代表服务器响应对象
//response.data得到响应的ResultVO对象 不是每一个请求都有data
axios.post("/v1/users/reg",v.user).then(function (response) {
//当接收到JSON格式的字符串的时候Axios框架会自动将JSON格式的字符串转成了JS的对象
if (response.data.code === 1){
alert("注册成功");
location.href="/"; //localhost:8080
}else{
alert("用户已存在!")
}
})
}
}
})
</script>
</body>
</html>
JSON
JSON:JavaScript Object Notation
是一种轻量级数据交换格式(也称为数据封装格式)
当服务器给客户端响应复杂数据时需要将数据封装到ava对象里面,但是Java对象不是跨平台的对象,客户端是无法直接识别Java格式的对象的,这时候就需要将Java的对象先转成JSON格式的字符串,因为JSON字符串是跨平台的类型,客户端接收到JSON格式的字符串时,Axios框架会自动将其转成JS语言的对象
Spring Security
Spring Security是一个基于Spring框架的安全性认证和访问控制框架,主要用于保护Web应用程序。它提供了一组与安全相关的服务和类,使得开发者可以方便地为Web应用程序添加认证和访问控制功能。
主要作用:帮助我们进行登录的认证,和项目中涉及到权限时进行权限誓理操作
引入SpringSecurity
框架
<!-- Spring Boot支持Spring Security的依赖项,用于处理认证与授权-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
对项目的影响
-
所有的请求(包括根本不存在的)都必须登录,否则自动跳转到登录页面
-
默认的用户名是user,密码是启用项目时在控制台提示的一串UUID值 ,如下图
-
登录时,如果在打开登录页面后重启过服务器端,应该刷新登录页面,否则, 第1次输入并提交是无效的
-
当登录成功后,会自动跳转到此前尝试访问的URL
-
当登录成功后,可通过 /logout 退出登录
-
默认不接受普通的POST请求,如果提交POST请求,会响应403(Forbidden)
关于SpringSecurity的配置
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
}
}
默认登录表单
@Override
protected void configure(HttpSecurity http) throws Exception {
//如果调用以下方法,当需要访问通过认证的资源,但是未通过认证时,将自动跳转到登录页面
//如果未调用以下方法,将响应403
http.formLogin();
// super.configure(http);//不要保留调用父类同名方法的代码,不要保留!不要保留!不要保留!
}
设置白名单
//设置白名单(不需要登录即可访问的资源)
String[] urls = {"/reg.html", "/login.html", "/v1/users/reg", "/v1/users/login"};
http.authorizeRequests()//对请求进行授权
.mvcMatchers(urls)//匹配某些路径
.permitAll() //直接许可,即不需要认证即可访问
.anyRequest() //任意请求
.authenticated();//要求通过认证的
模拟登录
- 尝试登录用户名root,密码123456
- 创建
UserDetailsService
接口的实现类,并实现loadUserByUsername
方法 - 此时进行登录时会自动调用此方法
- 设置默认的密码编码为无编码
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
//配置密码加密的方式
public PasswordEncoder passwordEncoder(){
//NoOpPasswordEncoder.getInstance()获取一个无加密的实例
return NoOpPasswordEncoder.getInstance();
}
}
- 登录成功需要响应一个
UserDetail
对象,如果响应的是一个null则会认为是用户名不存在
package com.liner.security;
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;
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("username = " + username);
//点击登录按钮
UserDetails userDetail = User.builder()
.username("root").password("123456")
.disabled(false) //账号是否禁用
.accountLocked(false) //账号是否锁定
.accountExpired(false) //账号是否过期
.credentialsExpired(false) //凭证是否过期
.authorities("临时使用的权限") //权限
.build();
if (!username.equals("root")) {
return null; //代表用户名不存在
}
return userDetail;
}
}
使用自己的页面进行登录
- 需要在配置类中添加
http.formLogin().loginPage("“/login.html");
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置自己的登录页面
http.formLogin().loginPage("/login.html");
//设置白名单
String[] urls = {"/reg.html", "/login.html", "/v1/users/reg", "/v1/users/login"};
http.authorizeHttpRequests()//对请求进行授权
.mvcMatchers(urls)//匹配某些路径
.permitAll() //直接许可,即不需要认证即可访问
.anyRequest() //任意请求
.authenticated();//要求通过认证的
}
}
- 使用自己的登录页面时,Security框架的登录认证流程不会自动启动,需要我们在UserController处理login请求时手动开启
- 开启时需要在SpringSecurity框架的配置类中添加认证管理器
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean //添加此注解的目的是为了在Controller中自动装配
@Override
//添加认证管理器
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
}
- 添加后在自己的Controller里面添加以下代码才会触发
UserDetailServicelmpl
实现类
@Autowired
private AuthenticationManager authenticationManager;
@RequestMapping("login")
public ResultVO login(@RequestBody UserLoginDTO userLoginDTO) {
//通过认证管理器启动Security的认证流程返回认证结果对象
Authentication authenticate = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(userLoginDTO.getUsername(), userLoginDTO.getPassword()));
//将认证结果保存到Security上下文中让Security框架记住登录状态
SecurityContextHolder.getContext().setAuthentication(authenticate);
//代码执行到这里时代表登录成功!如果登录失败Security框架会抛出异常
return new ResultVO(StatusCode.SUCCESS);
}
CSRF
-
CSRF指的是
Cross-Site Request Forgery
,中文翻译为“跨站请求伪造”,也称跨域攻击。它是一种网络攻击方式,攻击者利用用户在其他网站上登录过的身份信息,在用户不知情的情况下发送恶意请求来执行非法操作。 -
攻击流程如下:
- 用户在网站A上登录并获取Cookie
- 用户在不注销网站A的情况下访问了网站B
- 网站B通过HTML代码里插入恶意链接的形式,向网站A发出请求
- 因为用户在网站A已经有了Cookie,所以请求被成功执行
- 一旦攻击者成功发起CSRF攻击,就能够执行一些非法或损害用户隐私的操作,比如修改用户密码、盗取用户资料、转账等等。
-
为了防止CSRF攻击,通常需要采取一些措施,如增加验证码、使用CSRF Token、验证Referer等。
关闭跨域攻击防御机制
- SpringSecurity框架默认开启了跨域攻击的防护,通过携带CSRF token对请求进行判断
- 因为现在都是前后端分离架构,客户端发出的请求都是异步请求,不存在form表单,所以不存在跨域攻击的问题,通过以下代码关闭跨域攻击,否则请求受限
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//禁用“防止伪造的跨域攻击”的防御机制
http.csrf().disable();
}
}
通过真实数据进行登录
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired(required = false)
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserVO user = userMapper.selectByUsername(username);
if (user == null) {
return null; //默认抛出 AuthenticationException 用户名找不到异常
}
UserDetails userDetail = User.builder()
.username(username).password(user.getPassword())
.disabled(false) //账号是否禁用
.accountLocked(false) //账号是否锁定
.accountExpired(false) //账号是否过期
.credentialsExpired(false) //凭证是否过期
.authorities("临时使用的权限") //权限
.build();
//如果用户输入的密码和数据库中查询到的密码不一致则会抛出异常
return userDetail;
}
}
统一异常处理
- 对用户名、密码错误导致抛出的异常进行统一异常处理
package com.liner.exception;
import com.liner.response.ResultVO;
import com.liner.response.StatusCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j //lombok提供的日志注解,在代码层面会为我们提供一个org.slf4j.Logger对象
@RestControllerAdvice //=@ControllerAdvice+@ResponseBody
public class GlobalExceptionHandler {
@ExceptionHandler({InternalAuthenticationServiceException.class, BadCredentialsException.class})
public ResultVO handleAuthenticationServiceException(AuthenticationException e){
log.error("异常信息 is {}",e.getMessage());//日志级别trace<debug<info<warn<error
if (e instanceof InternalAuthenticationServiceException){
log.warn("用户名不存在!!!");
return new ResultVO(StatusCode.USERNAME_ERROR,e.getMessage());
}
log.warn("密码错误!!!");
return new ResultVO(StatusCode.PASSWORD_ERROR,e.getMessage());
}
}
Security框架认证流程总结
-
在
SecurityConfig
配置类中配置好白名单,设置登录页面,关闭跨域攻击防御策略 -
当客户端请求路径不在白名单中,Security框架会自动将请求重定向到登录页面
-
在
login.html
登录页面中向/login
地址发送登录请求时,服务器中UserController
里面的login方法
处理该请求 -
在
login方法
中通过认证管理器manager
启动认证,将认证结果保存在Security上下文对象中 -
当
manager
启动认证流程后会自动调用UserDetailServiceImpl
里面的loadUserByUsername方法
,在方法内部,调用UserMapper
里面的查询方法通过用户名查询到UserVO
。 -
如果查询不到
return null
,此时Security框架会抛出异常代表用户名不存在,需要全局异常处理进行处理,如果查询到的密码和用户输入的密码一致,则不抛出异常 -
UserController
中的login方法
会执行完,给客户端响应登录成功的信息,如果登录的密码错误,Security框架会抛出代表密码错误的异常,此时也需要全局异常处理类进行处理
教师总结
同步请求和异步请求
- 同步: 指单线程依次做几件事
- 异步: 指多线程同时做几件事
- 同步请求: 指客户端浏览器只有一个主线程,主线程既要负责页面渲染\监听用户的页面操作还需要负责发请求, 当主线程发出请求时,会将页面中的内容清空,知道服务器响应了数据之后再将响应的数据展示出来, 这个过程称为页面的整体刷新, 同步请求无法实现页面的局部刷新
- 异步请求: 指客户端浏览器由主线程负责页面渲染和监听用户的页面的操作, 由子线程负责发出请求, 当子线程请求到数据之后,可以把得到的数据显示到原页面中, 这就是页面的局部刷新.
创建SpringBoot工程
- 工程名boot01 打3个勾
- 在pom.xml添加以下依赖 并刷新maven
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--添加Knife4j依赖-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
<version>4.1.0</version>
</dependency>
<!--Spring Validation依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
- 在application.properties里面添加以下配置信息
spring.datasource.url=jdbc:mysql://localhost:3306/blog?serverTimezone=Asia/Shanghai&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root
# 设置MyBatis框架的映射(Mapper)配置文件的位置
mybatis.mapper-locations=classpath:mappers/*.xml
logging.level.cn.tedu=debug
JSON
-
JSON 是一种轻量级的数据交换格式(也称为数据封装格式)
-
当服务器给客户端响应复杂数据时需要将数据封装到Java对象里面, 但是Java对象不是跨平台的对象, 客户端是无法直接识别Java格式的对象的, 这时候就需要将Java的对象先转成JSON格式的字符串, 因为字符串是跨平台的类型, 客户端接收到JSON格式的字符串时,Axios框架会自动将其转成JS语言的对象 .
SpringSecurity框架流程
- 在工程中的pom.xml里面添加依赖
- 创建一个
SecurityConfig.java
配置类, 在里面重写configure方法
- 在方法中配置白名单
- 创建
UserDetailsServiceImp
l实现类, 在里面配置正确的用户名和密码
- 将默认的密码编码器设置为无加密
-
配置自己的登录页面
-
使用自己的登录页面时,Security框架的登录认证流程不会自动启动,需要我们在
UserController
处理login
请求时手动开启,手动开启时需要用到认证管理器,在Security
配置类中进行配置
在UserController中自动装配认证管理器对象,并在login方法
中启动认证流程
- 关闭跨域攻击防御
-
在
UserDetailServiceImpl
里面添加从数据库中查询数据的代码 -
当登录失败时Security框架抛出了两种异常,需要在全局异常处理类中添加处理方法
Security框架认证流程:
- 在SecurityConfig配置类中配置好白名单, 设置登录页面, 关闭跨域攻击防御策略
- 当客户端请求的路径不在白名单里面, Security框架会自动将请求重定向到登录页面
- 在login.html登录页面中向/login地址发出登录请求时,服务器中UserController里面的login方法处理该请求
- 在login方法中通过认证管理器manager启动认证 将认证结果保存在Security上下文对象中
- 当manager启动认证流程后会自动调用UserDetailServiceImpl里面的loadUserByUsername方法, 在方法内部 调用UserMapper里面的查询方法通过用户名查询到UserVO, 如果查询不到return null, 此时Security框架会抛出异常代表用户名不存在,需要在全局异常处理中进行处理, 如果查询到的密码和用户输入的密码一致,则不会抛出任何异常 UserController中的login方法会执行完,给客户端响应登录成功的信息, 如果登录的密码错误Security框架会抛出代表密码错误的异常,此时也需要在全局异常处理类中进行处理.