JAVA 实现登录校验
1、简单的登录:
根据输入的用户名和密码,去数据库中查询,可以查到,就登陆成功,查不到就登录失败
缺点(每次登陆后,调转到功能界面,可以通过直接输入功能界面的url 进行访问,无需登录,这样就造成程序失败!
解决: 可以加上一个登陆后的标记! 每次访问功能界面的时候,首先进行登陆的信息校验!)
2、登录校验
用户登录之后,每一次请求,都可以获取到登陆标记!
统一拦截:servlet中的过滤器 Filter Spring中的 拦截器 Interceptor
2.1、会话技术:
**会话:**用户打开浏览器,访问web 服务器的资源,会话建立。直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。
**会话跟踪:**一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便于在同一次会话中的多次请求间共享数据。
会话跟踪方案:
- 客户端会话跟踪技术: Cookie
/**
cookie : 同一浏览器,不同的请求,cookie一样的
请求头 Cookie 就是用来设置cookie的值
响应头 Set-Cookie 就是用来获取Cookie的值
优点:Http 协议中,支持的协议
缺点:移动端APP 无法使用cookie
不安全,用户可以禁用cookie
cookie不可以跨域 跨域(协议+ip/域名:端口号) 三个地址任意一个不一样就是跨域
*/
代码举例:
//设置 cookie
@GetMapping("/setCookie")
public R cookie(HttpServletResponse response){
response.addCookie(new Cookie("login_username","JiaHao"));
return R.success();
}
//获取Cookie
@PostMapping("/getCookie")
public R cookie(HttpServletRequest request){
Cookie[] cookies = request.getCookies();//获取所有的cookie
for(Cookie cookie : cookies){
if(cookie.getName().equals("login_username")){ //遍历cookie,得到name为login_username的cookie的值
System.out.println("login_username:" + cookie.getValue());
}
}
return R.success();
}
- 服务端会话跟踪技术: Session
/**
session: 存储在服务器端的
session 就是基于cookie 来实现的
优点:存储在服务器端,较为安全
缺点:在多服务器间,无法传播sessionId
cookie的缺点 因为sessionId存在cookie中,所以cookie的缺点,session也具有
*/
代码举例:
//往 HttpSession中存储值
@GetMapping("/setSession")
public R session (HttpSession session){
//打印 session的信息
log.info("HttpSession-s1:",session.hashCode());
session.setAttribute("loginUser","Xue"); //往session中存值
return R.success();
}
//从 HttpSession中获取值
@GetMapping("/getSession")
public R session(HttpServletRequest request){
HttpSession session = request.getSession();
log.info("HttpSession-s2:{}",session.hashCode());
Object loginUser = session.getAttribute("loginUser"); //从Session中 获取数据
log.info("loginUser:{}",loginUser);
return R.success();
}
- 令牌技术: JWT(token)
/**
JWT令牌技术:
优点:支持PC端、移动端
解决集群环境下的认证问题
减轻服务器端 存储压力
*/
2.2、JWT令牌:
用户身份的标识,本质就是字符串
JWT :全称( JSON Web Token)https://jwt.io/
定义了一种简洁的、自包含的格式,用于在通信双方以 JSON 数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。
组成:
第一部分: Header(头) 记录令牌类型,签名算法等。例如:{"alg":"HS256","type":"JWT"}
第二部分: Payload(有效载荷) , 携带者一些自定义信息,默认信息等,例如:{"id":"1","username":"Xue"}
第三部分: Signature(签名),防止Token 被篡改,确保安全性。将header,payload,并加入指定密钥,通过指定签名算法计算而来。
JWT令牌的生成
①、首先引入相关依赖:
中电信数公司的
<dependency>
<groupId>com.tele.toolkit</groupId>
<artifactId>tele-toolkit-jwt-starter</artifactId>
<version>${tele-toolkit.version}</version>
</dependency>
普通的
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
②、代码:
生成令牌JWT:
@Test
public void testGenerateJWT(){
Map<String , Object> claims = new HashMap<>();
claims.put("id" , 1);
claims.put("name" , "Xue")
String JWT = Jwts.builder()
.signWith(SignatureAlgorithm.HS256 , "Xue") //签名算法,自定义 第二个参数是自定义密钥/盐值
.setClaims(claims) //里面存的是自定义的内容 (载荷)
// System.currentTimeMillis() 获得当前时间的 毫秒值 3600*1000 等于一个小时
.setExpiration(new Date(System.currentTimeMillis() + 3600*1000)) //设置有效期 为一小时
.compact(); //获取JWT字符串。
}
解析令牌:
@Test
public void parseJWT(String JWT){
Claims claims = Jwts.parser()
.setSigningKey("Xue") //指定签名密钥 也就是盐值
.parseClaimsJws(JWT) //解析令牌 传入
.getBody(); //就是得到自定义的内容的方法
System.out.println(claims);
}
注意事项
- JWT 校验时使用的签名密钥,必须和生成 JWT 令牌时使用的密钥匙配套的。
- 如果 JWT 令牌解析校验时报错,则说明 JWT 令牌被篡改 或者 到期失效了 。
在登陆成功的时候,生成令牌,下发令牌
登录失败就返回一个错误信息
//登陆成功,生成令牌,下发令牌
if(e != null){
Map<String , Object> claims = new HashMap<>();
claims.put("id",e.getId());
claims.put("name",e.getName());
//JwtUtils 是一个生成和解析 JWT的工具类 类似于上面的代码。
String JWT = JwtUtils.generateJWT(claims);
return R.success(JWT);
}
//登陆失败,返回错误信息
return R.("用户名或密码错误")
2.3、过滤器Filter:
概念:
Filter 过滤器 ,是Java web三大组件 (Servlet , Filter , Listener)之一。
- 过滤器可以把对资源的请求拦截下来,从而实现一些特殊功能。
- 过滤器一般完成一些通用的操作,比如:登录校验,统一编码处理,敏感字符处理等。
快速入门:
- 定义过滤器 Filter:定义一个类,实现 Filter 接口,并重写其所有方法。
- 配置 Filter :Filter 类上加@WebFilter 注解,配置拦截资源的路径。引导类上加 @ServletComponentScan 开启Servlet 组件支持。
//编写一个类,实现 Filter 接口 ,并且重写 init , doFilter , destory 三个方法。
@WebFilter(urlPatterns = "/*") //表名当前类是拦截类,urlPatterns拦截的请求 , /*是 拦截所有请求。
//加上该注解时,还需要在启动类加上@ServletComponentScan 这个注解,因为这是servlet容器的,不是spring boot 容器的 所以要在 spring boot 启动类上加该注解 , 开启servlet组件支持。
public calss DemoFilter implements Filter{
//init 初始化方法,web服务器启动,创建Filter时调用,只调用一次。
public void init(FilterConfig filterConfig) throws ServletException{
Filter.super.init(filterConfig);
}
//doFilter 拦截方法 拦截到请求时调用该方法,可调用多次
public void doFilter(ServletRequest request , SrevletResponse response , FilterChain chain){
System.out.println("拦截方法执行,拦截到了请求。。。。。");
//放行 让它去访问对应的资源
chain.doFilter(request,response);
}
//销毁方法,服务器关闭时调用,只调用一次
public void destory(){
Filter.super.destory();
}
}
执行流程:
流程:过滤器拦截到请求,先执行放行之前的逻辑,执行后放行,放行后去访问对应的web资源,访问过后就会回到 Filter 当中,然后执行放行后的操作,
- 放行后访问对应资源,资源访问完毕之后,还会回到 Filter 之中。
- 回到 Filter 之中,会执行放行之后的逻辑。
拦截路径:
拦截路径 | urlPatterns值 | 含义 |
---|---|---|
拦截具体路径 | /login | 只有在访问 /login 路径时,才会拦截 |
目录拦截 | /emps/* | 访问 /emps 下的所有资源都会被拦截 |
拦截所有 | /* | 访问所有资源都会被拦截 |
过滤器链:
**介绍:**一个web 应用中,可以配置多个过滤器,这多个过滤器就形成了一个过滤器链。
**顺序:**注解配置的 Filter ,优先级 是按照过滤器类名(字符串)的自然排序。 A-B-C…Z
比如过滤器A 过滤器B , 程序执行时,首先执行过滤器 A 之前的逻辑,然后放行 ,放行是放行到过滤器B ,然后执行过滤器B的放行之前的逻辑,然后放行,进入到对应的 web 请求资源,返回到过滤器B的放行后的逻辑,然后执行过滤器A的放行后的逻辑。
登录校验功能:
流程:
- 获取请求的 url;
- 判断请求 url 中是否包含 login ,如果包含,说明是登陆操作,放行;
- 获取请求头中的的令牌( token);
- 判断令牌是否存在,如果不存在,返回错误结果(未登录);
- 解析token ,如果解析失败,返回错误接口板
- 放行;
public void doFilter(ServletRequest request , ServletResponse response , FilterChain chain) throws IOException{
//1、获取请求的url。 request 获取请求参数 response 响应结果
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
String url = req.getRequestURL().toString(); //获取请求url
//2、判断请求 url 中是否包含 login ,如果包含,说明是登陆操作,放行;
if(url.contains("login")){
chain.doFilter(request , response);
return ; //因为没有登录所以就不需要进行下面的操作,所以我们直接return之后,就不会进行后续操作。
}
//3、获取请求头中的的令牌( token);请求参数都在request 中
String jwt = req.getHeader("token");
//4、判断令牌是否存在,如果不存在,返回错误结果(未登录);
if(!StringUtils.hasLength(jwt)){ //判断jwt 有没有长度 没有就错,然后证明jwt 为空
log.info("未登录或登录失效,请重新登录!");
Result error = Result.error("NOT_LOGIN");
//手动转JSON 阿里巴巴 的依赖包 fastjson
String notLogin = JSONObject.toJSONString(error); //会将error 转换为JSON 字符串
resp.getWriter().write(notLogin); //将次消息响应给客户端
return ; //也要return 因为未登录就不许需要继续往下进行
}
//5、解析token ,如果解析失败,返回错误接口板
try{
JWTUtils.parseJWT(jwt); //解析成功就意味着登录,否则就未登录或token到期
} catch(Exception e){ //解析失败就是异常
log.info(e);
Result error = Result.error("NOT_LOGIN");
//手动转JSON 阿里巴巴 的依赖包 fastjson
String notLogin = JSONObject.toJSONString(error); //会将error 转换为JSON 字符串
resp.getWriter().write(notLogin); //将次消息响应给客户端
return ; //也要return 因为未登录就不许需要继续往下进行
}
//6、放行;
//令牌合法,解析成功
chain.doFilter(request , response);
}
2.4、拦截器 Interceptor:
2.4.1简介:
概念: 是一种动态拦截方法调用的机制,类似于过滤器。Spring 框架中提供的,用来动态拦截控制器方法的执行。
作用: 拦截请求,在指定的方法调用后,根据业务需要 执行预先设定的代码。
2.4.2快速入门:
步骤: 1、定义拦截器,实现 HandlerInterceptor接口,并重写其方法。(preHandel , postHandel , afterCompletion)
2、注册拦截器
//定义拦截器
@Component
public class LoginCheckInterceptor implements HandlerInterceptor{
@Override //目标资源方法执行前执行,返回true 放行 , 返回false 不放行;
public boolean preHandle(HttpServletRequest req , HttpServletResponse resp , Object handler)throws Exception{
return true; //为true 就代表可以运行controller中的方法
}
@Override //目标资源方法执行之后执行 在controller 方法执行之后
public void postHandle(HttpServletRequest req , HttpServletResponse resp , Object handler,ModelAndView modelAndView){
}
@Override // 视图渲染完毕之后执行, 最后最后执行
public void afterCompletion(HttpServletRequest req , HttpServletResponse resp , Object handler , Exception ex){
}
}
//注册配置拦截器
@Configuration //表明是配置类
public class WebConfig implements WebMvcConfigurer{
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**"); // /**表明拦截所有
}
}
2.4.3详解:
拦截路径:
addPathPatterns("/**"); // 需要拦截哪些资源
excludePathPatterns("/login"); //不需要拦截哪些资源
拦截路径 | 含义 | 举例 |
---|---|---|
/* | 一级路径 | 能匹配/depts , /emps , /login , 不能匹配/depts/1 |
/** | 任意级路径 | 能匹配 /depts , /depts/1 , /depts/1/2 |
/depts/* | /depts 下的一级路径 | 能匹配 /depts/1 , 不能匹配 /depts/1/2 , /depts |
/depts/** | /depts 下的任意级路径 | 能匹配 /depts , /depts/1 , /depts/1/2, 不能匹配 /emps/1 |
执行流程:
如果过滤器和拦截器同时存在的话,那么请求会先经过过滤器,然后进入到拦截器,具体为:发送请求,过滤器拦截到请求之后,会过滤请求,然后 请求进入到spring 容器 ,DispatcherServlet 发送请求 拦截器拦截到请求,拦截过后,请求到达 controller 然后返回给拦截器,返回到 DispatcherServlet ,返回到 过滤器,最终返回给浏览器。
过滤器 Filter 和 拦截器 Interceptor
**接口规范不同:**过滤器需要实现 Filter 接口 ,拦截器需要实现 HandlerInterceptor 接口
**拦截范围不同:**过滤器(范围更大)会拦截所有的资源, 而拦截器 Interceptor 只会拦截 Spring 环境中的资源。
2.4.4登录校验-Interceptor:
步骤(同过滤器一致)
- 获取请求的 url;
- 判断请求 url 中是否包含 login ,如果包含,说明是登陆操作,放行;
- 获取请求头中的的令牌( token);
- 判断令牌是否存在,如果不存在,返回错误结果(未登录);
- 解析token ,如果解析失败,返回错误接口板
- 放行;