6. 登录功能
6.1 登录认证
只进行用户名和密码是否存在的操作
@Slf4j
@RestController
public class LoginController {@Autowiredpublic 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@Testpublic 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@Testpublic 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 {@Autowiredpublic 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 {@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {// Tomcat传入的是request实际上是HTTPrequest,要进行强制类型转换HttpServletRequest req = (HttpServletRequest) servletRequest;HttpServletResponse resp = (HttpServletResponse) servletResponse;// 1. 获取请求urlString 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,fastJSONString 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,fastJSONString 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 {@Autowiredpublic LoginCheckInterceptor loginCheckInterceptor;// 重写方法,注册拦截器@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");}
}
拦截路径:
执行流程:
Filter和Interceptor的区别:
- 接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口
- 拦截范围不同:过滤器Filter会拦截所有资源,而Interceptor只会拦截Spring环境中的资源
6.4 异常处理
方案一:在Controller的方法中进行try…catch处理
方案二:全局异常处理器
@RestControllerAdvice = @ControllerAdvice + @ResponseBody