每日总结38
SpringBoot学习6(2.2整合项目:properties->yml,@Value->@Configuration,登录校验:会话(令牌Jwt)+统一拦截(Filter、Interceptor),异常处理)
1.代码优化
1.1properties配置文件优化为yml配置文件
这里的这几个参数都是写在java文件中,不灵活,修改的时候需要自己找到该java文件才能修改,可以将这些参数写在配置文件application.properties中,然后再使用注解@Value进行外部属性的注入

修改:


将properties文件换成yml文件,语法更加简洁

application.yml
spring:
#数据库连接信息
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/course
username: root
password: 123456
servlet:
multipart:
max-file-size: 10MB
max-request-size: 100MB
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 开启驼峰映射
map-underscore-to-camel-case: true
mybatis-plus:
global-config:
db-config:
id-type: auto
# 自定义阿里云OSS配置
aliyun:
oss:
endpoint : https://oss-cn-beijing.aliyuncs.com
accessKeyId : L
accessKeySecret : F
bucketName : web-tlias29
2.导入ConfigurationProperties依赖优化@Values注释获取配置文件的自定义数据
@Value注解优化@ConfigurationProperties
@ConfigurationProperties可以批量的将外部属性配置注入到bean对象的属性中
1. 引入依赖

2. 创建一个实体类
package com.example.utils;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "aliyun.oss")
public class AliOSSProperties {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
}
3.再调用该实体类

2.登录校验

2.1 会话技术
会话:用户打开浏览器访问web服务端的资源,会话建立,直到有一方断开连接,会话结束。
会话可以包含多次请求和响应。
会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自同一浏览器,以便在同一次会话的多次请求间共享数据。
会话跟踪方案:
客户端会话跟踪技术:Cookie
服务端会话跟踪技术:Session
令牌技术(当前主流)

1.Cookie和Session运用
package com.example.controller;
import com.example.entity.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@Slf4j
@RestController
public class SessionController {
/**
* 设置cookie
* @param response
* @return
*/
@GetMapping("/c1")
public Result cookie1(HttpServletResponse response){
//设置 Cookie响应Cookie
response.addCookie(new Cookie("login_username","admin"));
return Result.success();
}
/**
* 获取cookie
*/
@GetMapping("/c2")
public Result cookie2(HttpServletRequest request){
Cookie[] cookies = request.getCookies();
for (Cookie cookie:cookies) {
if(cookie.getName().equals("login_username")){
System.out.println("login_username: "+cookie.getValue());
}
}
return Result.success();
}
/**
* 设置Session
*/
@GetMapping("/s1")
public Result session1(HttpSession session){
log.info("HttpSession-s1:{}",session.hashCode());
session.setAttribute("loginUser","tom");
return Result.success();
}
/**
* 获取Session的值
*/
@GetMapping("/s2")
public Result session2(HttpServletRequest request){
HttpSession session = request.getSession();
log.info("HttpSession-s2:{}",session.hashCode());
Object loginUser = session.getAttribute("loginUser");
log.info("loginUser:{}",loginUser);
return Result.success();
}
}




2.令牌技术JWT(JSON Web Token)
定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息,由于数字签名的存在,这些信息是可靠的。
组成:
第一部分:Header(头),记录令牌的类型、签名算法
第二部分:Payload(有效载荷),携带自定义信息、默认信息
第三部分:Signature(签名),防止Token被篡改,确保安全。
①pom.xml引入依赖
<!-- JWT令牌 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
②测试使用代码
测试运行的时候,这个代码和SpringBoot无关,可以先把@SpringBootTest注释掉就不用加载整个SpringBoot项目,运行testGenJwt
package com.example;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@SpringBootTest
public class JJWTTest {
@Test
public void testUuid(){
for(int i=0;i<1000;i++){
String uuid = UUID.randomUUID().toString();
System.out.println(uuid);
}
}
/**
* 测试生成JWt令牌
*/
@Test
public void testGenJwt(){
Map<String,Object> claims = new HashMap<>();
claims.put("id",1);
claims.put("name","xiaoming");
String jwt = Jwts.builder()
//设置签名算法,密钥
.signWith(SignatureAlgorithm.HS256, "1010")
//设置自定义数据
.setClaims(claims)
//设置令牌的有效期,newDate是当前时间,
// System.currentTimeMillis()是获取当前的时间毫秒值然后
//+3600*1000表示当前时间+1h(有效期)
.setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000))
//获取返回值就是jwt令牌
.compact();
System.out.println(jwt);
}
}
运行testGenJwt的结果:

将结果复制到jwt官网查看:

③解析令牌的java代码
@Test
public void testParseJwt(){
Claims claims = Jwts.parser()
//给出密钥,才能解析该令牌
.setSigningKey("admin")
//将令牌传进去
.parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoieGlhb21pbmciLCJpZCI6MSwiZXhwIjoxNjk3NDYyMTUyfQ.5CURAl3in17KoZZnbvOiZJdjqWZ8vrAsGPHr6P4uVt8")
//拿到令牌第二部分的内容
.getBody();
System.out.println(claims);
}


④结合项目


JwtUtils.java
package com.example.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.Map;
public class JwtUtils {
private static String signKey = "admin";
private static Long expire = 43200000L;
/**
* 生成JWT令牌
* @param claims JWT第二部分负载 payload 中存储的内容
* @return
*/
public static String generateJwt(Map<String, Object> claims){
String jwt = Jwts.builder()
.addClaims(claims)
.signWith(SignatureAlgorithm.HS256, signKey)
.setExpiration(new Date(System.currentTimeMillis() + expire))
.compact();
return jwt;
}
/**
* 解析JWT令牌
* @param jwt JWT令牌
* @return JWT第二部分负载 payload 中存储的内容
*/
public static Claims parseJWT(String jwt){
Claims claims = Jwts.parser()
.setSigningKey(signKey)
.parseClaimsJws(jwt)
.getBody();
return claims;
}
}
LoginController.java
package com.example.controller;
import com.example.entity.Emp;
import com.example.entity.Result;
import com.example.service.EmpService;
import com.example.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/login")
public class LoginController {
@Autowired
private EmpService empService;
@PostMapping()
public Result login(@RequestBody Emp emp){
log.info("员工登录{}",emp);
Emp e = empService.login(emp);
//登录成功,生成令牌
if(e!=null){
Map<String, Object> claims= new HashMap<>();
claims.put("id",e.getId());
claims.put("name",e.getName());
claims.put("username",e.getUsername());
String jwt = JwtUtils.generateJwt(claims);
//将令牌返回到前端
return Result.success(jwt);
}
//登录失败,返回错误信息
return Result.error("用户名或密码错误");
}
}
在服务层、数据层添加查询代码:
//EmpService
Emp login(Emp emp);
//EmpServiceImpl
@Override
public Emp login(Emp emp) {
return empMapper.getByUsernameAndPassword(emp);
}
//EmpMapper
@Select("select * from emp where username = #{username} and password = #{password}")
Emp getByUsernameAndPassword(Emp emp);

2.2 过滤器Filter和拦截器Interceptor
1.过滤器Filter
Filter过滤器是JavaWeb三大组件(Servlet、Filter、Listener)之一。
可以将对资源的请求拦截下来,常用于登录校验、统一编码处理、敏感字符处理
步骤:
①定义一个类实现Filter接口,重写其所有方法
②配置Filter,在Filter类中加上@WebFilter(urlPatterns="/*)注解,配置拦截资源的路径,引导类中 (在项目启动的xxxApplication) 中加入注解@ServletComponentScan .


①结合项目
添加依赖
<!-- 阿里巴巴提供的fastjson工具包-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>

LoginCheckFilter.java
package com.example.filter;
import com.alibaba.fastjson.JSONObject;
import com.example.entity.Result;
import com.example.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
@WebFilter(urlPatterns = "/*")
public class LoginCheckFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//拿到请求对象和响应对象
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse resp = (HttpServletResponse) servletResponse;
//获取请求的url
String url = req.getRequestURL().toString();
log.info("请求的url:{}",url);
//判断url中有没有包含login,有则放行
if(url.contains("login")){
log.info("登录操作,放行");
filterChain.doFilter(servletRequest,servletResponse);
return;
}
//不是登录操作,获取令牌的请求头的token
String jwt = req.getHeader("token");
//判断令牌是否存在,如果不存在返回错误结果(未登录)
if(!StringUtils.hasLength(jwt)){
//没有登录的信息
log.info("请求头token为空,即未登录");
Result error=Result.error("NOT_LOGIN");
//手动将对象转为JSON返回 引入阿里巴巴提供的fastjson工具包
String notloing = JSONObject.toJSONString(error);
//响应数据
resp.getWriter().write(notloing);
return;
}
//令牌存在,解析jwt令牌是否解析成功(有无登录失效、令牌篡改)
try {
JwtUtils.parseJWT(jwt);
}catch (Exception e){
e.printStackTrace();
log.info("解析令牌失败");
Result error=Result.error("NOT_LOGIN");
//手动将对象转为JSON返回 引入阿里巴巴提供的fastjson工具包
String notloing = JSONObject.toJSONString(error);
//响应数据
resp.getWriter().write(notloing);
return;
}
//放行
log.info("令牌合法,放行");
filterChain.doFilter(servletRequest,servletResponse);
}
}
JwtUtils.java
package com.example.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.Map;
public class JwtUtils {
private static String signKey = "admin";
private static Long expire = 43200000L;
/**
* 生成JWT令牌
* @param claims JWT第二部分负载 payload 中存储的内容
* @return
*/
public static String generateJwt(Map<String, Object> claims){
String jwt = Jwts.builder()
.addClaims(claims)
.signWith(SignatureAlgorithm.HS256, signKey)
.setExpiration(new Date(System.currentTimeMillis() + expire))
.compact();
return jwt;
}
/**
* 解析JWT令牌
* @param jwt JWT令牌
* @return JWT第二部分负载 payload 中存储的内容
*/
public static Claims parseJWT(String jwt){
Claims claims = Jwts.parser()
.setSigningKey(signKey)
.parseClaimsJws(jwt)
.getBody();
return claims;
}
}
运行:

2.拦截器Interceptor
一种动态拦截方法的调用机制类似于过滤器,Spring框架中天空,用于动态拦截控制器方法的执行。
和拦截器的代码差不多,放行的代码不一样,放行直接写return true即可。
代码:
LoginCheckInterceptor.java
package com.example.interceptor;
import com.alibaba.fastjson.JSONObject;
import com.example.entity.Result;
import com.example.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
@Override //目标资源方法运行前运行, 返回true: 放行, 放回false, 不放行
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
// return HandlerInterceptor.super.preHandle(request, response, handler);
// System.out.println("preHandle ...");
//登录校验:和过滤器的代码一样
//获取请求的url
String url = req.getRequestURL().toString();
log.info("请求的url:{}",url);
//判断url中有没有包含login,有则放行
if(url.contains("login")){
log.info("登录操作,放行");
return true;//放行
}
//不是登录操作,获取令牌的请求头的token
String jwt = req.getHeader("token");
//判断令牌是否存在,如果不存在返回错误结果(未登录)
if(!StringUtils.hasLength(jwt)){
//没有登录的信息
log.info("请求头token为空,即未登录");
Result error=Result.error("NOT_LOGIN");
//手动将对象转为JSON返回 引入阿里巴巴提供的fastjson工具包
String notloing = JSONObject.toJSONString(error);
//响应数据
resp.getWriter().write(notloing);
return false;
}
//令牌存在,解析jwt令牌是否解析成功(有无登录失效、令牌篡改)
try {
JwtUtils.parseJWT(jwt);
}catch (Exception e){
e.printStackTrace();
log.info("解析令牌失败");
Result error=Result.error("NOT_LOGIN");
//手动将对象转为JSON返回 引入阿里巴巴提供的fastjson工具包
String notloing = JSONObject.toJSONString(error);
//响应数据
resp.getWriter().write(notloing);
return false;
}
//放行
log.info("令牌合法,放行");
return true;//放行
}
@Override //目标资源方法运行后运行
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
System.out.println("postHandle ...");
}
@Override //视图渲染完毕后运行, 最后运行
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
System.out.println("afterCompletion ..");
}
}
WebConfig.java
package com.example.config;
import com.example.interceptor.LoginCheckInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration //声明是配置类
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
@Override //重写方法注册拦截器
public void addInterceptors(InterceptorRegistry registry) {
// WebMvcConfigurer.super.addInterceptors(registry);
//添加拦截器以及要拦截的资源
registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");
}
}
运行:
登录获取令牌

查询部门操作:
如果没有写令牌,返回未登录的消息提示

3.异常处理
全局异常处理器
@RestControllerAdvice//异常处理器注解
@ExceptionHandler(Exception.class)//捕获哪一种异常(所有异常)
package com.example.exception;
import com.example.entity.Result;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice//异常处理器注解
public class GobalExceptionHandler {
@ExceptionHandler(Exception.class)//捕获所有异常
public Result ex(Exception ex){
ex.printStackTrace();
return Result.error("操作异常,请联系管理员");
}
}


浙公网安备 33010602011771号