6. 登录功能
6.1 登录认证
只进行用户名和密码是否存在的操作
@Slf4j
@RestController
public class LoginController {
@Autowired
public 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) {
Emp e = empMapper.getByUsernameAndPassword(emp);
return e;
}
@Select("select * from emp where username = #{username} and password = #{password}")
Emp getByUsernameAndPassword(Emp emp);
问题:在未登录的情况下,也可以直接访问部门管理、员工管理等功能
6.2 登录校验
请求来的时候线进行登录校验,校验通过的话,执行业务;不通过就跳转到登录界面
1)会话技术
会话: 浏览器和服务器ide一次连接,一次会话中可以包含多次请求和响应
会话跟踪: 服务器需要识别多次请求是否来自同一浏览器,以便在同一次会话的多次请求间共享数据
会话跟踪方案:
- 客户端会话跟踪技术:Cookie
- 服务端会话跟踪技术:Session
- 令牌技术
Cookie:
①特点
- 浏览器第一次发送请求给服务器的时候,服务器自动将Cookie返回给浏览器
- 浏览器自动将收到的Cookie存储在本地
- 浏览器向服务器发送请求的时候,自动携带Cookie
②优点
HTTP协议中支持的技术,浏览器自动进行
③缺点
- 移动端APP无法使用Cookie
- 不安全,用户可以自己禁用Cookie(在浏览器的设置里面)
- Cookie不能跨域(协议、IP/域名、端口)
Session:
①特点
- 基于Cookie
②优点
-
存储在服务器端,安全
③缺点
- 服务器集群环境下无法直接使用Session
- Cookie的缺点
令牌技术:
①特点
字符串的形式,可以存储在任何容器中,不只是可以存储在Cookie中
②优点
- 支持PC端、移动端
- 解决集群环境下的认证问题
- 减轻服务器端存储压力
③缺点
- 需要自己实现
2)JWT令牌
全称:JSON Web Token(https://jwt.io/)
定义了一种**简洁(就是字符串)**的、**自包含(可以在其中包括用户名等需要的字符串)**的格式,用于在通信双方以json数据格式安全地传输信息。由于数字签名的存在,这些信息是可靠的
组成:
第一部分:Header(头),记录令牌类型、签名算法等;采用Base64编码
第二部分:Payload(有效荷载),携带一些自定义信息、默认信息等;采用Base64编码
第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload融入,并加入指定密钥,通过指定前面算法计算而来
例:
案例:
添加依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
// 生成JWTT
@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
@Test
public void testParseJwt() {
Claims claims = Jwts.parser()
.setSigningKey("itheima") // 密钥
.parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidG9tIiwiaWQiOjEsImV4cCI6MTcwNDY5Njg1NH0.MzTlN_jrVSa7UJtTFoXw4xQ9-t4R7JHUmHeom5KQ2ss")
.getBody();
System.out.println(claims);
}
错误
java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter
解决:
引入依赖
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
登录-生成令牌
@Slf4j
@RestController
public class LoginController {
@Autowired
public 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("用户名或密码错误");
}
}
3)过滤器Filter
概念: 过滤器是JavaWeb三大组件(Servlet、Filter、Listener)之一
功能: 把对资源的请求拦截下来,从而实现一些特殊功能,例如:登录校验、统一编码处理、敏感字符处理等
实现:
- 实现Filter接口
- @WebFilter(urlPatterns=“/*”)
- @ServletComponentScan——让启动类知道
@WebFilter(urlPatterns = "/*") // 拦截所有请求
public class DemoFilter implements Filter {
@Override // 初始化方法,只调用一次
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init初始化方法执行了");
}
@Override // 拦截到请求之后调用
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("拦截到了请求");
// 放行
filterChain.doFilter(servletRequest, servletResponse);
}
@Override // 销毁方法,只调用一次
public void destroy() {
System.out.println("destroy销毁方法执行了");
}
}
执行流程:
拦截路径:
过滤器链:
一个web应用有中,可以配置多个过滤器,多个过滤器就形成了一个过滤器链
顺序——注解配置的Filter,优先级是按照过滤器类名(字符串)的自然排序
@Slf4j
@WebFilter(urlPatterns = "/*")
public class LoginCheckFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// Tomcat传入的是request实际上是HTTPrequest,要进行强制类型转换
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse resp = (HttpServletResponse) servletResponse;
// 1. 获取请求url
String url = req.getRequestURL().toString();
log.info("请求的url:{}", url);
// 2. 判断请求url中是否包含login,如果包含,说明是登录操作,放行
if (url.contains("login")) {
log.info("放行");
filterChain.doFilter(servletRequest, servletResponse);
return;
}
// 3. 获取请求头中的令牌(token)
String jwt = req.getHeader("token");
// 4. 判断令牌是否存在,如果不存在,返回错误结果(未登录)
if (!StringUtils.hasLength(jwt)) {
log.info("请求头token为空,返回未登录的信息");
Result erro = Result.error("NOT_LOGIN");
// 手动将Result对象转化为json格式——alibaba,fastJSON
String notLogin = JSONObject.toJSONString(erro);
resp.getWriter().write(notLogin);
return;
}
// 5. 解析token,如果解析失败,返回错误结果(未登录)
try {
JwtUtils.parseJWT(jwt);
} catch (Exception e) {
e.printStackTrace();
log.info("令牌解析失败,返回未登录错误信息");
Result erro = Result.error("NOT_LOGIN");
// 手动将Result对象转化为json格式——alibaba,fastJSON
String notLogin = JSONObject.toJSONString(erro);
resp.getWriter().write(notLogin);
return;
}
// 6. 放行
log.info("合法,放行");
filterChain.doFilter(servletRequest, servletResponse);
}
}
4)拦截器Interceptor
概念: 是一种动态拦截方法调用的机制,类似过滤器。Spring框架中提供的,用来动态拦截控制器方法的执行
作用: 拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
@Override // 目标资源方法运行前运行,返回true:放行,返回false:不放行
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle ...");
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 ...");
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
public LoginCheckInterceptor loginCheckInterceptor;
// 重写方法,注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");
}
}
拦截路径:
执行流程:
Filter和Interceptor的区别:
- 接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口
- 拦截范围不同:过滤器Filter会拦截所有资源,而Interceptor只会拦截Spring环境中的资源
6.4 异常处理
方案一:在Controller的方法中进行try…catch处理
方案二:全局异常处理器
@RestControllerAdvice = @ControllerAdvice + @ResponseBody