实现简单的登录功能

实体类

定义实体类为User3类。

使用@Data:提供类的get,set,equals,hashCode,canEqual,toString方法;

使用@AllArgsConstructor:提供类的全参构造

使用@NoArgsConstructor:提供类的无参构造

类代码如下

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User3
{
private Integer id;
private String userName;
private String password;
}

JWT核心类

JWT认证流程如图所示



可见服务器端需要做两件事,一是根据用户信息创建对应的JWT密钥,另一个是根据对应的密钥解码用户信息。

JWT token主要由三个部分组成,主要包括Header,Payload,Signature。通过“."间隔开来。

  • Header:包括两部分信息,令牌的类型(typ),签名算法(alg)。
  • Payload:也称为声明(Claims),包含JWT的主要信息,主要是用户身份信息,权限等,是一个JSON对象。
  • Signature:使用头部使用的算法和密钥对头部和载荷进行签名所生成的一部分,用来验证真实性和完整性。

签名主要是先将头部和载荷转换为Json格式,并进行base64编码,最后用.拼接在一起

Header: {"alg": "HS256", "typ": "JWT"}
Payload: {"sub": "1234567890", "name": "John Doe", "admin": true}

转换后为

Encoded Header: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Encoded Payload: eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

使用指定的算法对Encoded Header.Payload进行签名。最后再拼接在一起

验证时使用JWTVerifier实例,使用 HMAC256 算法和指定的密钥(SECRET)来验证签名。

主要代码如下

public class JwtUtil {
private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class);
/**
* 密钥
*/
private static final String SECRET = "my_secret";
/**
* 过期时间
**/
private static final long EXPIRATION = 1800L;//单位为秒
/**
* 生成用户token,设置token超时时间
*/
public static String createToken(User3 user) {
//过期时间30分钟
Date expireDate = new Date(System.currentTimeMillis() + EXPIRATION * 1000);
Map<String, Object> map = new HashMap<>();
map.put("alg", "HS256");
map.put("typ", "JWT");
String token = JWT.create()
.withHeader(map)// 添加头部
//可以将基本信息放到claims中
.withClaim("id", user.getId())//userId
.withClaim("userName", user.getUserName())//userName
.withClaim("password", user.getPassword())//password
.withExpiresAt(expireDate) //超时设置,设置过期的日期
.withIssuedAt(new Date()) //签发时间
.sign(Algorithm.HMAC256(SECRET)); //SECRET加密
System.out.println("生成的token:"+token);
return token;
}
/**
* 校验token并解析token
*/
public static Map<String, Claim> verifyToken(String token) {
DecodedJWT jwt = null;
System.out.println("识别的token:"+token);
try {
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
jwt = verifier.verify(token);
} catch (Exception e) {
logger.error(e.getMessage());
logger.error("token解码异常");
return null;
}
return jwt.getClaims();
}
}

JWT过滤器

使用过滤器可以提供一种轻量级,安全,高效的方式来处理身份验证和授权,可以简化开发流程。

可以避免服务器每次请求都进行状态管理,无需每次都查询数据库验证用户身份或访问权限。

使用过滤器前我们首先要进行注册

@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<JwtFilter> jwtFilter() {
FilterRegistrationBean<JwtFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new JwtFilter());
registrationBean.addUrlPatterns("/secure/*");
return registrationBean;
}
}

由于用户在每次请求时都会带上Authorization的token,所以要提取出对应的token,识别出用户对应的信息后再进行下一步处理。

整个过程处于过滤器链中,对于 OPTIONS 请求,直接放行并调用 chain.doFilter(request, response);

过滤器的代码如下。

public class JwtFilter implements Filter
{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res;
String temp = request.getHeader("authorization");
System.out.println("temp:" + temp);
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
final String token = request.getHeader("authorization");
System.out.println("token:" + token);
if ("OPTIONS".equals(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
chain.doFilter(request, response);
}
else { if (token == null) {
response.getWriter().write("没有token!");
return;
}
Map<String, Claim> userData = JwtUtil.verifyToken(token);
if (userData == null) {
response.getWriter().write("token不合法!");
return;
}
Integer id = userData.get("id").asInt();
String userName = userData.get("userName").asString();
String password= userData.get("password").asString();
//拦截器 拿到用户信息,放到request中
request.setAttribute("id", id);
request.setAttribute("userName", userName);
request.setAttribute("password", password);
chain.doFilter(req, res);
}
}
@Override
public void destroy() {
}
}

或许会有个疑问,那就是这里代码也调用了chain.doFilter(req, res);会导致递归等问题吗。

其实并不会,每个过滤器都会调用chain.doFilter(req, res)方法把请求传递给下一个过滤器,直到最后一个过滤器调用目标Servlet。整个过程处于线性并且有终点。

一般来说,过滤器链的工作流程如下:

  • 过滤器链初始化:当一个请求到达时,服务器根据配置初始化过滤器链。
  • 过滤器链顺序执行:请求进入第一个过滤器,第一个过滤器在逻辑中调用chain.doFilter(request, response); 将请求传递给下一个过滤器。
  • 传递到目标资源:过程一直持续,直到最后一个过滤器调用chain.doFilter(request, response)传递给最终的目标Servlet或JSP页面来处理。
  • 响应返回:目标资源生成响应后,响应会逆向通过所有过滤器回传给客户端,每个过滤器都有机会在返回路径上再次处理响应。

下面以一个例子来说明

public class Filter1 implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("Filter1: before chain");
chain.doFilter(request, response); // 将请求传递给下一个过滤器
System.out.println("Filter1: after chain");
}
} public class Filter2 implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("Filter2: before chain");
chain.doFilter(request, response); // 将请求传递给下一个过滤器
System.out.println("Filter2: after chain");
}
} public class TargetServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.getWriter().write("TargetServlet response");
}
}

当请求到达时,控制流执行如下:

Filter1 执行前部分代码(Filter1:before chain)。

Filter1 调用 chain.doFilter(request, response); 将请求传递给 Filter2。

Filter2 执行前部分代码(Filter2: before chain)。

Filter2 调用 chain.doFilter(request, response); 将请求传递给 TargetServlet。

TargetServlet 生成响应。

响应传回 Filter2,执行后部分代码(Filter2: after chain)。

响应传回 Filter1,执行后部分代码(Filter1: after chain)。

最终响应传回给客户端。

如果过滤器链中的最后一个过滤器调用了chain.doFilter(request, response);,请求将被传到目标资源。

解码返回响应

最后为了查看解码是否正确,还需要查看解码后的用户信息

编写类代码如下

  @RequestMapping("/secure/getUserInfo")
@UnInterception
public JSONObject login(HttpServletRequest request) {
// final String token = request.getHeader("authorization");
// System.out.println("token:" + token);
Integer id = (Integer) request.getAttribute("id");
System.out.println(id);
String userName = request.getAttribute("userName").toString();
String password = request.getAttribute("password").toString();
HttpServletResponse response = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getResponse();
assert response != null;
response.setCharacterEncoding("UTF-8");
JSONObject test = new JSONObject();
test.put("id",id);
test.put("userName",userName);
test.put("password",password);
System.out.println("当前用户信息id=" + id + ", userName=" + userName + ", password=" + password);
return test;
}

这里的request是已经完成了过滤后的请求,注意要设置字符编码为UTF-8,否则可能会返回乱码。

    static Map<Integer, User3> userMap = new HashMap<>();

    static {

        User3 user1 = new User3(1,"张三","123456");
userMap.put(1, user1);
User3 user2 = new User3(2,"李四","123123");
userMap.put(2, user2);
}
/**
* 模拟用户 登录
*/
@RequestMapping(value = "/login", method = RequestMethod.GET)
@UnInterception
public String login(@RequestParam String userName, @RequestParam String password)
{
System.out.println("userName: " + userName + ", password: " + password);
for (User3 dbUser : userMap.values()) {
if (dbUser.getUserName().equals(userName) && dbUser.getPassword().equals(password)) {
log.info("登录成功!生成token!");
String token = JwtUtil.createToken(dbUser);
return token;
}
}
return "";
}

测试结果

在Apifox上测试后结果如下。







可以看到正常发送了请求并且解码成功,而且编码是UTF-8

springboot实现登录demo的更多相关文章

  1. springboot之登录注册

    springboot之登录注册 目录结构 pom.xml <?xml version="1.0" encoding="UTF-8"?> <pr ...

  2. Xamarin.Android再体验之简单的登录Demo

    一.前言 在空闲之余,学学新东西 二.服务端的代码编写与部署 这里采取的方式是MVC+EF返回Json数据,(本来是想用Nancy来实现的,想想电脑太卡就不开多个虚拟机了,用用IIS部署也好) 主要是 ...

  3. 易Android登录Demo

    上一页介绍Android项目简单的页面跳转实例,算是对开发环境的熟悉,这一篇将在此基础上增加一些简单的逻辑,实现登录的效果. 登录之前: 登录成功: watermark/2/text/aHR0cDov ...

  4. SpringBoot注册登录(三):注册--验证账号密码是否符合格式及后台完成注册功能

    SpringBoot注册登录(一):User表的设计点击打开链接SpringBoot注册登录(二):注册---验证码kaptcha的实现点击打开链接      SpringBoot注册登录(三):注册 ...

  5. ElasticSearch 实现分词全文检索 - SpringBoot 完整实现 Demo 附源码【完结篇】

    可以先看下列文章 目录 ElasticSearch 实现分词全文检索 - 概述 ElasticSearch 实现分词全文检索 - ES.Kibana.IK安装 ElasticSearch 实现分词全文 ...

  6. Springboot实现登录功能

    SpringBoot简介 Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员不再 ...

  7. SpringBoot 并发登录人数控制

    通常系统都会限制同一个账号的登录人数,多人登录要么限制后者登录,要么踢出前者,Spring Security 提供了这样的功能,本文讲解一下在没有使用Security的时候如何手动实现这个功能 dem ...

  8. [原创]java WEB学习笔记56:Struts2学习之路---Struts 版本的 登录 demo

    本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...

  9. 也谈SSO,一个简单实用的单点登录Demo

    关于SSO(单点登录),百度百科解释如下 : “SSO英文全称Single Sign On,单点登录.SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统.它包括可以将这次主要 ...

  10. 开源框架bboss单点登录demo跑起来

    目前公司新项目要使用一个开源框架bboss的单点登录功能,要将此功能整合到新系统中去,所以我就学习了一下. 首先,进入这个bboss框架作者的博客中,找到相应的session共享,单点登录的博文,看了 ...

随机推荐

  1. Gitee千Star优质项目解析: ng-form-element低开引擎解析

    好家伙, 在写项目的时候,我发现自己的平台的组件写的实在是太难看了,于是想去gitee上偷点东西,于是我们本期的受害者出现了 gitee项目地址 https://gitee.com/jjxliu306 ...

  2. angular打包优化

    打包生产环境时需要的配置如下: 在angular.json里的"configurations"里配置: "configurations": { "pr ...

  3. 大数据之Hadoop的HDFS存储优化—异构存储(冷热数据分离)

    异构存储主要解决,不同的数据,储存在不同类型的硬盘中,达到最佳性能的问题 1)存储类型 RAM_DISK:内存镜像文件系统 SSD:SSD固态硬盘 DISK:普通磁盘,在HDFS中,如果没有主动声明数 ...

  4. 一文带你读懂Arthas实现原理

    一. 前言 Arthas 相信大家已经不陌生了,肯定用过太多次了,平时说到 Arthas 的时候都知道是基于Java Agent的,那么他具体是怎么实现呢,今天就一起来看看. 首先 Arthas 是在 ...

  5. es 排序突然很慢的原因

    今天突然之间发现一个访问es的查询很慢.由刚上线之前测试的100ms直接到了5s左右.瞬间懵逼. 这个用户索引大概200w的数据. 查询语句如下 GET /user/_search{"fro ...

  6. docker离线安装

    1. yum安装 #源添加 wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo ...

  7. iOS面试题详解-开篇

    一.前言 转眼之前,已经毕业5年,从事iOS开发5年.5年的时间说长不长,却把我从刚毕业的毛头小伙子变成了现在的"中年大叔",不仅仅是外表还有心态. 一方面不愿意接收自己形体的改变 ...

  8. python-一种字符串排序方式

    最近工作中,需要使用python实现一种排序方式,简要说明如下: 1.排序方式 假设有一个序列,数据为:['n1', 'n2', 'n10', 'n11', 'n21', 'n3', 'n13', ' ...

  9. VIVO IQOO 5G 开关

    VIVO IQOO 5G 开关 在拨号盘输入*#*#2288#*#*,然后点击网络模式选择. -

  10. Scrapy框架(六)--图片数据抓取

    基于文件下载的管道类 在scrapy中我们之前爬取的都是基于字符串类型的数据,那么要是基于图片数据的爬取,那又该如何呢? 其实在scrapy中已经为我们封装好了一个专门基于图片请求和持久化存储的管道类 ...