一、基本登录功能
(一)需求: 根据账号与密码判别用户是否可以登录
(二)实现步骤
- Controller接收传递的JSON格式数据,使用@RequestBody+实体类进行接收,调用Service具体处理。
- Service创建登录接口,实现类调用Mapper根据条件查询是否存在具体用户,存在则返回具体用户,反之返回null。
- Mapper创建条件查询接口,利用条件查询SQL查询用户。
(三)代码实现
UserController.java
@RestController //@Controller+@ResponseBody
public class LoginCtroller{
@PostMapping("/login")
public int login(@RequestBody User user){
User u = userService.login(user);
return u !=null?200:-1;
}
}
UserService.java
public interface UserService {
User login(User user);
}
UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
@Override
public User login(User user) {
return userMapper.getUserByUsernameAndPassword(user);
}
}
UserMapper.java
@Mapper
public interface UserMapper {
@Select("select * from user where username = #{username} and password = #{password}")
User getUserByUsernameAndPassword(User user);
}
(四)现有登录的缺陷
- 无法限制用户直接访问内容页。
二、登录校验
(一)概念: 每一次请求都要进行用户登录授权。
(二)登录校验相关技术
1、登录标记: 一般采用会话技术,在用户登录成功后,每一次请求中都会获取该标记。
2、统一拦截: 两种方式
- 过滤器 Filter
- 拦截器 Interceptor
(三)会话技术
1、概念
- 会话: 用户打开浏览器,访问we服务器的资源,会话建立,直到一方断开连接,会话结束。在一次会话中可以多次请求和响应。
- 会话跟踪: 一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据。
- 会话跟踪方案:
- 客户端会话跟踪技术—— Cookie
- 服务端会话跟踪技术—— Session
- 令牌技术
2、客户端会话跟踪 Cookie
(1)Cookie使用过程:
- 用户登录成功后,服务器创建Cookie并自动发送给浏览器。
- 浏览器接收Cookie并自动存储在浏览器本地。
- 用户使用浏览器向服务器发送请求,浏览器自动将Cookie请求头(Set-Cookie: name=value)发送到服务器的同一拦截层进行校验,校验通过后处理请求。
模拟Cookie在服务器端的操作
@Slf4j
@RestController
public class SessionController {
@GetMapping("/c1")
public int cookie1(HttpServletResponse response){
response.addCookie(new Cookie("login_username","zengoo"));
return 200;
}
@GetMapping("/c2")
public int cookie(HttpServletRequest request){
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) {
if (cookie.getName().equals("login_username")) System.out.println("用户登录的账号为 " + cookie.getValue());
}
return 200;
}
}
执行C1方法请求到服务器效果图
执行C2方法,获取指定的Cookie内容效果图
(2)Cookie的优缺点
- 优点
- HTTP协议支持的技术
- 缺点
- 移动端无法使用Cookie
- 不安全,用户可以禁用Cookie
- Cookie不支持跨域
3、服务器会话跟踪技术 Session
(1)Session的使用过程
- 用户登录成功后常见Session会话,通过Cookie记录SessionID并发送给浏览器
- 浏览器接收Cookie并存储到本地
- 当发生请求服务器时,SessionID发送到服务器,服务器查找对应Session进行连接。
(2)模拟Session
@Slf4j
@RestController
public class SessionController {
@GetMapping("/s1")
public int session1(HttpSession session){
log.info("HttpSession-s1:{}",session.hashCode());
session.setAttribute("login_account","tom1101");
return 200;
}
@GetMapping("/s2")
public int session2(HttpSession session){
log.info("HttpSession-s2:{}",session.hashCode());
Object user = session.getAttribute("login_account");
log.info("user:{}",user);
return 200;
}
}
设置session效果图
服务端获取Session会话效果图
(3)Session的优缺点
- 优点
- 存储在服务器端,安全性高
- 缺点
- 服务器集群环境下无法使用Session(原因是产生Session的服务器与服务分配的服务器可能不是同一台)
- Cookie的缺点(Session的底层是Cookie)
4、令牌技术
(1)令牌技术的使用过程
- 用户登录成功后获取从服务器端获取以个令牌(例如token,accessKey…),服务器返回给浏览器
- 浏览器接收令牌并存储在本地(使用Cookie或Session都可以)
- 当发生请求服务器时,浏览器发送令牌给服务器,服务器进行校验后处理请求
(2)令牌技术的优缺点
- 优点
- 支持PC、移动
- 解决集群环境下的认证问题
- 减轻服务器存储压力
- 缺点
- 需要手动实现(生成、存储等过程)
(3)JWT令牌技术
- 简介
- 全称: JSON Web Token(官网:https://jwt.io/)
- 作用: 定义了一种简洁的(JWT是字符串)、自包含(可以插入自定义的数据)的格式,用于在通信双方以JSON数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。
- 结构:
- 第一部分:Header(头),记录令牌类型,签名算法等。例如 {“alg”:“HS256”,“type”:“JWT”}
- 第二部分:Payload(有效载荷),自定义的内容、默认信息等。例如 {“id”:“1”,“username”:“Tom”}
- 第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload,并加入指定密钥,通过指定签名算法计算而来。
- JWT使用
- 引入JWT依赖。
io.jsonwebtoken.jjwt - 0.9.1
- 引入JWT依赖。
- 使用JWT的注意事项
- JWT校验时必须使用对应的签名密钥
- JWT令牌失效的原因:
- 令牌被篡改
- 令牌到期
生产JWT
public class DataTest {
@Test
public void getJWT(){
//载荷内容
Map<String,Object> claims = new HashMap<>();
claims.put("id",1);
claims.put("username","tom");
String jwt = Jwts.builder()
.signWith(SignatureAlgorithm.HS256,"zengoo")//签名算法Base64+签名密钥"zengoo"
.setClaims(claims) //载荷
.setExpiration(new Date(System.currentTimeMillis()+ (12*3600*1000))) //有效期
.compact();
System.out.println(jwt);
}
}
/*打印结果*/
eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjg4MDYzMzUwLCJ1c2VybmFtZSI6InRvbSJ9.occWRYeyio23As2Xd0RilmZtASE3wF0bYc_6F3OFE-M
解析令牌
@Test
public void parseJWT(){
Claims claim = Jwts.parser()
.setSigningKey("zengoo") //签名密钥
.parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjg4MDYzMzUwLCJ1c2VybmFtZSI6InRvbSJ9.occWRYeyio23As2Xd0RilmZtASE3wF0bYc_6F3OFE-M") //JWT内容
.getBody();
System.out.println(claim);
}
/*打印输入*/
{id=1, exp=1688063350, username=tom}
(四)Filter 过滤器
1、简介
- 概念: Filter 过滤器,是JavaWeb 三大组件(
Servlet、Filter、Listener
)之一。 - 作用: 过滤器可以把资源的请求拦截下来,完成通用操作,例如:登录校验、统一编码处理、敏感词字符处理等。
2、使用
(1)定义Filter: 定义一个类,实现Filter接口,并重写方法。
@WebFilter(urlPatterns = "/*") //拦截所有请求
public class LoginFilter implements Filter {
//初始化方法,web服务器启动,创建Filter时调用,仅调用一次
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
//对请求方法进行拦截处理,可多次调用
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//处理代码
System.out.println("拦截方法执行");
//放开请求
filterChain.doFilter(servletRequest,servletResponse);
}
//销毁方法,服务器关闭时调用,仅调用一次
@Override
public void destroy() {
Filter.super.destroy();
}
}
(2)配置Filter: Filter类加上 @WebFilter
注解,配置拦截资源的路径,启动类上加 @ServletComponentScan
注解,开启Servlet组件支持。
@ServletComponentScan //开启Servlet组件扫描
@SpringBootApplication
public class SpringbootMybatisApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootMybatisApplication.class, args);
}
}
3、过滤器的细节问题
(1)执行流程
- web服务器启动,过滤器启动初始化方法init
- 浏览器发送任意一个请求给web服务器,过滤器拦截该请求
- 过滤器启动doFilter方法判别请求或处理请求内容,确认无误后提交给FilterChain类的doFilter()方法放开请求访问资源。
- 若还有需要处理的逻辑,则可在放开请求后编写执行。
(2)拦截路径
- 常见拦截路径
路径方式 | 举例 | 说明 |
---|---|---|
具体路径 | /login | 访问 /login 时拦截 |
目录拦截 | /userAPI/* | 访问 /userAPI 下的资源时拦截 |
拦截所有 | /* | 访问所有资源都会被拦截 |
(3)过滤器链
- 概念: 一个web应用中,可配置多个过滤器,多个过滤器形成的过滤通道形成了一个过滤链。
- 作用: 按照类名顺序通过过滤器链,服务器端可以通过多个过滤器进行多次额外加工。
(五)Interceptor 拦截器
1、简介
- 概念: Spring框架中提供的一种动态拦截方法调用的机制,类似于过滤器,所以拦截器的作用范围是整个Spring框架,其它的资源不进行拦截。
- 作用: 拦截请求,在指定的方法调用前后,根据业务需求执行预先设定的代码。
2、使用拦截器
(1)定义拦截器: 实现HandlerInterceptor 接口,并重写所有方法
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override //Controller之前执行,true——放开拦截;false——不放开
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("前期处理");
return true;
}
@Override //Controller之后执行
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("请求被拦截");
}
@Override //视图渲染完毕后执行
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("后期处理");
}
}
(2)注册拦截器
@Configuration
public class LoginConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册拦截器并配置拦截路径与非拦截路径
registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/loign");
}
}
3、拦截器的细节问题
(1)执行流程
- web服务器启动,先启动Filter类init()方法。
- 浏览器发送请求,服务器接收请求后过滤器
Filter
先进行拦截,通过前端控制器DispatcherServlet
转发给拦截器Interceptor
,在请求进入控制器Controller
前对数据进行处理,判断可放开后,请求进入资源访问。 - 请求访问资源完毕后,返回拦截器进行加工,再转发给前端控制器,再转发给过滤器,最终返回到浏览器。
(2)拦截路径
- 常见拦截路径
路径方式 | 举例 | 说明 |
---|---|---|
一级路径 | /* | 拦截/login,/list,不能拦截 /depts/1 |
任意拦截 | /** | 全路径拦截 |
某一路径下的一级路径 | /depts/* | 能拦截 /depts/1,不能拦截 /depts/1/2 ,/depts |
某一路径下的所有路径 | /depts/** | 能拦截 /depts下的所有路径 |
(六)过滤器与拦截器的区别
- 接口规范: 过滤器 ——
Filter
;拦截器 ——HandlerInterceptor
- 拦截范围: 过滤器—— 拦截所有资源 ; 拦截器 —— 拦截Spring环境下的资源
三、异常处理
两种解决方案
- 方案一: 在Controller的所有方法中进行try…catch处理
- 方案二: 定义全局异常处理器
(一)异常处理器的使用
1、定义异常处理器
@RestControllerAdvice //声明控制器异常处理,相当于@ControllerAdvice + @ResponseBody
public class GlobalExceptionHandler {
//统一进行异常处理,并向浏览器返回异常结果
@ExceptionHandler(Exception.class) //定义捕获的异常类型 Exception是所有异常的父类
public int ex(Exception ex){
ex.printStackTrace();
return 500;
}
}