目录
登录功能
创建LoginController中:
@Slf4j
@RestController
public class LoginController {@Autowired
private EmpService empService;@PostMapping("/login")
public Result login(@RequestBody Emp emp){
log.info("员工登录: {}", emp);
Emp e = empService.login(emp);return e !=null?Result.success():Result.error("用户名或者密码错误");
}
Emp login(Emp emp);
@Override
public Emp login(Emp emp) {
return empMapper.getByUsernameAndPassword(emp);
}
@Select("select * from emp where username = #{username} and password = #{password}")
Emp getByUsernameAndPassword(Emp emp);
登录校验(重点)
会话技术
会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。
会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据
会话跟踪方案:
1.客户端会话跟踪技术:Cookie
2.服务端会话跟踪技术:Session
令牌技术
JWT:定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。
Base64:是一种基于64个可打印字符(A-Z a-z 0-9 + /)来表示二进制数据的 编码方式。
JWT生成令牌
1.在pom.xml中加入依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
2.在单元测试类定义一个测试方法,生成JWT令牌
//@SpringBootTest
class TliasWebManagementApplicationTests {/**
* 生成JWT
*/
@Test
public void testGenJwt(){
Map<String, Object> claims = new HashMap<>();
claims.put("id",1);
claims.put("name","tom");String jwt = Jwts.builder()
.signWith(SignatureAlgorithm.HS256, "itheima")//签名算法
.setClaims(claims) //自定义内容(载荷)
.setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000))//设置有效期为1h
.compact();//字符串返回值
System.out.println(jwt);
}
JWT校验
/**
* 解析JWT
*/
@Test
public void testParseJwt(){
Claims claims = Jwts.parser()
.setSigningKey("itheima")
.parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidG9tIiwiaWQiOjEsImV4cCI6MTY3MDQ2NDU0N30.yPLRyiusrlrmWeC4-dhInjFuAghPkmiHSRHd_DTKi9E")
.getBody();//自定义内容
System.out.println(claims);
}
}
注意:JWT校验时使用的签名秘钥,必须和生成JWT令牌时使用的秘钥是配套的。
如果JWT令牌解析校验时报错,则说明 JWT令牌被篡改 或 失效了,令牌非法。
场景:登录认证。
令牌生成:登录成功后,生成令牌,然后返回给前端
令牌校验:在请求到达服务端后,对令牌进行统一拦截,校验。(后续每个请求,都要携带JWT令牌,系统在每次请求处理之前,先校验令牌,通过后,再处理)
登录-生成令牌
1.引入JWT令牌操作工具类。
2.登录完成后,调用工具类生成JWT令牌,并返回。
1.utils下导入JwtUtils工具类
LoginController下:(登录成功,生成令牌,下发令牌)
@Slf4j
@RestController
public class LoginController {@Autowired
private EmpService empService;@PostMapping("/login")
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); //jwt包含了当前登录的员工信息
return Result.success(jwt);
}
//登录失败, 返回错误信息
return Result.error("用户名或密码错误");
}}
实现统一校验JWT令牌(登录成功标记):
过滤器Filter
Filter 过滤器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一。
过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。
过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等。
快速入门:
1.定义Filter:定义一个类,实现 Filter 接口,并重写其所有方法。
2.配置Filter:Filter类上加 @WebFilter 注解,配置拦截资源的路径。启动类上加 @ServletComponentScan 开启Servlet组件支持。
filter包下的DemoFiler类:
@WebFilter(urlPatterns = "/*")
public class DemoFilter implements Filter {
@Override //初始化方法, web服务器启动,创建filter时调用,只调用一次
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init 初始化方法执行了");
}@Override //拦截到请求之后调用, 调用多次
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //拦截到请求时,调用该方法,可调用多次
System.out.println("Demo 拦截到了请求...放行前逻辑");
//放行
chain.doFilter(request,response);System.out.println("Demo 拦截到了请求...放行后逻辑");
}@Override //销毁方法, 服务器关闭时调用,只调用一次
public void destroy() {
System.out.println("destroy 销毁方法执行了");
}
}
加注解:
详解(执行流程,拦截路径,过滤器链)
请求 --> 放行前逻辑 --> 放行 --> 资源 --> 放行后逻辑
过滤器链
一个web应用中,可以配置多个过滤器,这多个过滤器就形成了一个过滤器链。
顺序:注解配置的Filter,执行优先级是按照过滤器类名(字符串)的自然排序确定。
一个一个执行
filter包下的DemoFiler类:
@WebFilter(urlPatterns = "/*")
public class XbcFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("Abc 拦截到了请求...放行前逻辑");
//放行
chain.doFilter(request,response);System.out.println("Abc 拦截到了请求...放行后逻辑");
}
}
场景:登录校验filter
访问后台管理系统,先完成登录操作,先访问登录接口login,登录成功之后,在服务端生成一个JWT令牌,并且把JWT令牌返回给前端,前端将其记录下来,在后面请求中,将JWT令牌携带到服务端,要想访问对应功能,先得校验令牌有效性,直接过滤器实现,在过滤器校验令牌有效性
若无效,直接响应错误信息,不会放行去访问,如果存在有效,则会放行,访问对应web资源,执行对应业务操作,
前端请求服务器,是在请求头当中将令牌携带到服务端的,请求头名字为token
在filter下
@Slf4j
@WebFilter(urlPatterns = "/*")
public class LoginCheckFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
//1.获取请求url。
String url = req.getRequestURL().toString();
log.info("请求的url: {}",url);
//2.判断请求url中是否包含login,如果包含,说明是登录操作,放行。
if(url.contains("login")){
log.info("登录操作, 放行...");
chain.doFilter(request,response);
return;
}
//3.获取请求头中的令牌(token)。
String jwt = req.getHeader("token");
//4.判断令牌是否存在,如果不存在,返回错误结果(未登录)。
if(!StringUtils.hasLength(jwt)){ //字符串为null或空串
log.info("请求头token为空,返回未登录的信息");
Result error = Result.error("NOT_LOGIN");
//手动转换 对象--json --------> 阿里巴巴fastJSON
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return;
}
//5.解析token,如果解析失败,返回错误结果(未登录)。
try {
JwtUtils.parseJWT(jwt);
} catch (Exception e) {//jwt解析失败
e.printStackTrace();
log.info("解析令牌失败, 返回未登录错误信息");
Result error = Result.error("NOT_LOGIN");
//手动转换 对象--json --------> 阿里巴巴fastJSON
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return;
}
//6.放行。
log.info("令牌合法, 放行");
chain.doFilter(request, response);
}
}
拦截器lnterceptor
是一种动态拦截方法调用的机制,类似于过滤器。Spring框架中提供的,用来动态拦截控制器方法的执行
作用:拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码。
入门
1.定义拦截器,实现HandlerInterceptor接口,并重写其所有方法。
2.注册拦截器
interceptor下的LoginCheckInterceptor.java:(ctrl+o:重写方法)
config下的WebConfig:(配置类)
详解(拦截路径,执行流程)
Filter与interceptor
区别:接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口。
拦截范围不同:过滤器Filter会拦截所有的资源,而Interceptor只会拦截Spring环境中的资源。
登录校验lnterceptor
package com.itheima.interceptor;
import com.alibaba.fastjson.JSONObject;
import com.itheima.pojo.Result;
import com.itheima.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 {
//1.获取请求url。
String url = req.getRequestURL().toString();
log.info("请求的url: {}",url);
//2.判断请求url中是否包含login,如果包含,说明是登录操作,放行。
if(url.contains("login")){
log.info("登录操作, 放行...");
return true;
}
//3.获取请求头中的令牌(token)。
String jwt = req.getHeader("token");
//4.判断令牌是否存在,如果不存在,返回错误结果(未登录)。
if(!StringUtils.hasLength(jwt)){
log.info("请求头token为空,返回未登录的信息");
Result error = Result.error("NOT_LOGIN");
//手动转换 对象--json --------> 阿里巴巴fastJSON
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return false;
}
//5.解析token,如果解析失败,返回错误结果(未登录)。
try {
JwtUtils.parseJWT(jwt);
} catch (Exception e) {//jwt解析失败
e.printStackTrace();
log.info("解析令牌失败, 返回未登录错误信息");
Result error = Result.error("NOT_LOGIN");
//手动转换 对象--json --------> 阿里巴巴fastJSON
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return false;
}
//6.放行。
log.info("令牌合法, 放行");
return true;
}
@Override //目标资源方法运行后运行
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle ...");
}
@Override //视图渲染完毕后运行, 最后运行
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion...");
}
}
异常处理
全局异常处理器
exception包下的GlobalExceptionHandler:
注解: