SpringBoot+JWT实现单点登录解决方案

news2024/11/24 9:53:52

一、什么是单点登录?

单点登录是一种统一认证和授权机制,指在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的系统,不需要重新登录验证。

单点登录一般用于互相授信的系统,实现单一位置登录,其他信任的应用直接免登录的方式,在多个应用系统中,只需要登录一次,就可以访问其他互相信任的应用系统。

随着时代的演进,大型web系统早已从单体应用架构发展为如今的多系统分布式应用群。但无论系统内部多么复杂,对用户而言,都是一个统一的整体,访问web系统的整个应用群要和访问单个系统一样,登录/注销只要一次就够了,不可能让一个用户在每个业务系统上都进行一次登录验证操作,这时就需要独立出一个单独的认证系统,它就是单点登录系统。

二、单点登录的优点

1.方便用户使用。用户不需要多次登录系统,不需要记住多个密码,方便用户操作。

2.提高开发效率。单点登录为开发人员提供类一个通用的验证框架。

3.简化管理。如果在应用程序中加入了单点登录的协议,管理用户账户的负担就会减轻。

三、JWT 机制

JWT(JSON Web Token)它将用户信息加密到token里,服务器不保存任何用户信息。服务器通过使用保存的密钥验证JWTToken的正确性,只要正确就通过验证。

数据结构:

JWT包含三个部分:Header头部,Payload负载和Signature签名。三个部门用“.”分割。校验也是JWT内部自己实现的 ,并且可以将你存储时候的信息从token中取出来无须查库。

JWT执行流程:

JWT的请求流程也特别简单,首先使用账号登录获取Token,然后后面的各种请求,都带上这个Token即可。具体流程如下:

1. 客户端发起登录请求,传入账号密码;

2. 服务端使用私钥创建一个Token;

3. 服务器返回Token给客户端;

4. 客户端向服务端发送请求,在请求头中携带Token;

5. 服务器验证该Token;

6. 返回结果。

四.创建Maven父项目

 2.指定打包类型为pom

 五.创建认证模块sso

 1.添加依赖,完整的pom文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.13</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>sso</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>sso</name>
    <description>sso</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--jwt-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.8.2</version>
        </dependency>
        <!--thymeleaf-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2添加jwt相关配置

 

 

 3.创建JWT配置类和JWT工具类

示例代码如下:

package com.example.sso.bean;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * @author qx
 * @date 2023/7/4
 * @des Jwt配置类
 */
@Component
@ConfigurationProperties(prefix = "jwt")
@Getter
@Setter
public class JwtProperties {

    /**
     * 过期时间-分钟
     */
    private Integer expireTime;

    /**
     * 密钥
     */
    private String secret;
}
package com.example.sso.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.example.sso.bean.JwtProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * @author qx
 * @date 2023/7/4
 * @des JWT工具类
 */
@Component
public class JwtUtil {

    @Autowired
    private JwtProperties jwtProperties;

    /**
     * 生成一个jwt字符串
     *
     * @param username 用户名
     * @return jwt字符串
     */
    public String sign(String username) {
        Algorithm algorithm = Algorithm.HMAC256(jwtProperties.getSecret());
        return JWT.create()
                // 设置过期时间1个小时
                .withExpiresAt(new Date(System.currentTimeMillis() + jwtProperties.getExpireTime() * 60 * 1000))
                // 设置负载
                .withClaim("username", username).sign(algorithm);
    }

    public static void main(String[] args) {
        Algorithm algorithm = Algorithm.HMAC256("KU5TjMO6zmh03bU3");
        String username = "admin";
        String token = JWT.create()
                // 设置过期时间1个小时
                .withExpiresAt(new Date(System.currentTimeMillis() + 60 * 60 * 1000))
                // 设置负载
                .withClaim("username", username).sign(algorithm);
        System.out.println(token);
    }

    /**
     * 校验token是否正确
     *
     * @param token token值
     */
    public boolean verify(String token) {
        if (token == null || token.length() == 0) {
            throw new RuntimeException("token为空");
        }
        try {
            Algorithm algorithm = Algorithm.HMAC256(jwtProperties.getSecret());
            JWTVerifier jwtVerifier = JWT.require(algorithm).build();
            jwtVerifier.verify(token);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }

}

4.创建服务层

package com.example.sso.service;

import com.example.sso.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author qx
 * @date 2023/7/4
 * @des 登录服务层
 */
@Service
public class LoginService {


    @Autowired
    private JwtUtil jwtUtil;

    /**
     * 登录
     *
     * @param username 用户名
     * @param password 密码
     * @return token值
     */
    public String login(String username, String password) {
        if ("".equals(username) || "".equals(password)) {
            throw new RuntimeException("用户名或密码不能为空");
        }
        // 为了测试方便 不去数据库比较密码
        if ("123".equals(password)) {
            // 返回生成的token
            return jwtUtil.sign(username);
        }
        return null;
    }

    /**
     * 校验jwt是否成功
     *
     * @param token    token值
     * @return 校验是否超过
     */
    public boolean checkJwt(String token) {
        return jwtUtil.verify(token);
    }
}

5.创建控制层

package com.example.sso.controller;

import com.example.sso.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @author qx
 * @date 2023/7/4
 * @des 验证控制层
 */
@Controller
@RequestMapping("/sso")
public class AuthController {

    @Autowired
    private LoginService loginService;

    /**
     * 登录页面
     */
    @GetMapping("/login")
    public String toLogin() {
        return "login";
    }

    /**
     * 登录
     *
     * @param username 用户名
     * @param password 密码
     * @return token值
     */
    @PostMapping("/login")
    @ResponseBody
    public String login(String username, String password) {
        return loginService.login(username, password);
    }

    /**
     * 验证jwt
     *
     * @param token token
     * @return 验证jwt是否合法
     */
    @RequestMapping("/checkJwt")
    @ResponseBody
    public boolean checkJwt(String token) {
        return loginService.checkJwt(token);
    }


}

6.创建一个登录页面login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
    <form method="post" action="/sso/login">
        用户名:<input type="text" name="username"/><br/>
        密码:<input type="password" name="password"/><br/>
        <button type="submit">登录</button>
    </form>
</body>
</html>

六、创建应用系统projectA 

 1.项目pom文件如下所示

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.example</groupId>
        <artifactId>my-sso</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>projectA</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.7.13</version>
        </dependency>
        <!--okhttp-->
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>4.10.0</version>
        </dependency>
    </dependencies>
</project>

 2.修改配置文件

 

 3.创建过滤器

package com.example.projectA.filter;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author qx
 * @date 2023/7/4
 * @des 登录过滤器
 */
@Component
@WebFilter(urlPatterns = "/**")
public class LoginFilter implements Filter {

    @Value("${sso_server}")
    private String serverHost;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        String token = httpServletRequest.getParameter("token");
        if (this.check(token)) {
            filterChain.doFilter(servletRequest, servletResponse);
        } else {
            HttpServletResponse response = (HttpServletResponse) servletResponse;
            String redirect = serverHost + "/login";
            response.sendRedirect(redirect);
        }
    }

    /**
     * 验证token
     *
     * @param token
     * @return
     * @throws IOException
     */
    private boolean check(String token) throws IOException {
        if (token == null || token.trim().length() == 0) {
            return false;
        }
        OkHttpClient client = new OkHttpClient();
        // 请求验证token的合法性
        String url = serverHost + "/checkJwt?token=" + token;
        Request request = new Request.Builder().url(url).build();
        Response response = client.newCall(request).execute();
        return Boolean.parseBoolean(response.body().string());
    }
}

4.创建测试控制层

package com.example.projectA.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author qx
 * @date 2023/7/4
 * @des 测试A
 */
@RestController
public class IndexController {

    @GetMapping("/testA")
    public String testA() {
        return "输出testA";
    }
}

5.启动类

package com.example.projectA;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

/**
 * @author qx
 * @date 2023/7/4
 * @des Projecta启动类
 */
@SpringBootApplication
@ServletComponentScan
public class ProjectaApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProjectaApplication.class,args);
    }
}

6.启动项目测试

我们访问http://localhost:8081/testA 系统跳转到了验证模块的登录页面

我们输入账号密码登录成功后返回token

 

 如何我们复制这段数据,把数据传递到token参数。

 我们看到正确获取到了数据。

七、创建应用系统projectB

我们再次模仿projectA创建projectB子模块。

 

 

 启动模块B

我们直接测试带上token参数

 

通过之前的token,无需登录即可成功进入了应用系统B。说明我们的单点登录系统搭建成功。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/719468.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【nav_msgs/Path.h发布路径】

#include <nav_msgs/Path.h> 是一个 ROS (Robot Operating System) 中的包含文件。它是用于包含 nav_msgs/Path 消息类型的头文件,这是一个标准的 ROS 消息类型。 nav_msgs/Path 消息类型常用于机器人导航系统中,以表示路径。这种路径通常由一系列的位置点组成,这些点…

Spirngboot读取html文件到字符串

一、引入依赖 <dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.3.5</version></dependency> 二、直接读取返回 public String getContent(String path){try {File file ResourceUt…

如何限定IP访问服务器端口(只允许指定IP访问数据库服务器的1433端口)

1、找到“控制面板”->“Windows防火墙”->“高级设置”->“入站规则” 2、选中左侧的"入站规则"&#xff0c;并点击右侧的"新建规则" 3、选择"端口"&#xff0c;点击"下一步" 4、输入要限定访问的端口&#xff0c;这里是要…

瑞吉外卖-Day02

title: 瑞吉外卖-Day02 abbrlink: ‘1’ date: 2023-04-1 19:30:00 瑞吉外卖-Day02 课程内容 完善登录功能新增员工员工信息分页查询启用/禁用员工账号编辑员工信息 分析前端页面效果是如何实现的 为什么点击左边 右边会根着变化 [外链图片转存失败,源站可能有防盗链机制…

【js】JS实现根据两点经纬度位置获取距离:

文章目录 一、JS实现根据两点经纬度位置获取距离&#xff1a;二、效果&#xff1a; 一、JS实现根据两点经纬度位置获取距离&#xff1a; // 根据经纬度计算距离&#xff0c;参数分别为第一点的纬度&#xff0c;经度&#xff1b;第二点的纬度&#xff0c;经度 function getDist…

环二肽试剂128857-77-2,Cyclo(-Gly-Arg-Gly-Asp-Ser-Pro-Ala),定制含D型与L型,S与R构型的氨基酸

规格单位&#xff1a;g |货期&#xff1a;按照具体的库存进行提供 | 纯度&#xff1a;95%试剂描述&#xff1a; 西安凯新生物科技有限公司供应的​Cyclo(-Gly-Arg-Gly-Asp-Ser-Pro-Ala)&#xff08;CAS号&#xff1a;128857-77-2&#xff09;环二肽试剂&#xff0c;是由两…

信不信,我一句话就能惹毛项目经理

早上好&#xff0c;我是老原。 上周有个做技术的粉丝问我&#xff0c;是不是技术做不下去的人才会去转管理&#xff1f; 我和他说&#xff0c;这句话千万别和你周围的项目经理说&#xff0c;不然分分钟和你生气。 不过这也不怪他&#xff0c;确实有很多做技术的人是被迫转管…

ICMP类型

ICMP&#xff08;Internet Control Message Protocol&#xff09;Internet控制报文协议。它是TCP/IP协议簇的一个子协议&#xff0c;用于在IP主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然并不传输用户…

(三)线程组和线程优先级

&#xff08;三&#xff09;线程组和线程优先级 3.1 线程组&#xff08;ThreadGroup&#xff09;3.2 线程的优先级01、Thread 的优先级02、ThreadGroup 和 Thread 优先级不一致问题 3.3 守护线程&#xff08;Daemon&#xff09;3.4 线程组的常用方法3.5 线程组的数据结构 3.1 线…

@RequestParam注解注意事项

在传参的时候&#xff0c;有的参数不是必传的&#xff0c;代码如下&#xff1a; 比如现在name为必传&#xff0c;position为非必传&#xff0c;我们来用postman测试下&#xff0c; 直接报如上图所示的错误&#xff0c;那么有什么办法阻止这个错误了&#xff0c;只要在不必传的参…

SpringBoot使用mybatis批量新增500万数据到mysql数据库Demo

SpringBoot使用mybatis批量新增500万数据到mysql数据库Demo 说明项目Demo代码地址项目目录mysql对应表建表语句pom.xmlapplication.yml配置类启动类代码OrderInfo 实体类TestController控制层接口层TestServiceTestServiceImpl实现层TestDao数据接口层dao层对应mapper.xml自定义…

熔断降级与限流在开源SpringBoot/SpringCloud微服务框架的最佳实践

目录导读 熔断降级与限流在开源SpringBoot/SpringCloud微服务框架的最佳实践1. 开源代码整体架构设计2. 微服务逻辑架构设计3. 微服务熔断降级与限流规划3.1 微服务熔断降级与限流场景分析3.2 微服务熔断降级与限流技术栈规划3.3 微服务熔断降级与限流技术选型3.3.1 熔断降级中…

自动化测试之selenium工具使用

1. 自动化测试的前提 1.1 什么是自动化&#xff1f; 减少人力成本完成大量重复性工作提高测试效率保证工作的一致性&#xff0c;提高信任度完成手工不能完成的工作 1.2 是否适合做自动化&#xff1f; 时间 &#xff08;项目周期长&#xff09;人员 &#xff08;熟悉自动化&…

神经网络术语解释

目录 Padding&#xff1a; 填充步幅&#xff08;stride&#xff09;Pooling Layer:池化层Batch NormalizationSeparable ConvolutionsREFERENCE Padding&#xff1a; 填充 在进行卷积层的处理之前&#xff0c;有时要向输入数据的周围填入固定的数据&#xff08;比 如0等&#…

redis高可用集群数据库的安装部署(6.2.12版本)

第三阶段基础 时 间&#xff1a;2023年7月3日 参加人&#xff1a;全班人员 内 容&#xff1a; 6.2.12版本redis集群部署 目录 一、环境配置&#xff1a;【两台服务器】 二、redis多实例配置&#xff1a; 三、构建redis cluster集群 四、创建主从 五、故障转移实验 …

IT安全部门应如何平衡企业内外部文件交换的业务效率与安全性?

在日常运营经营中&#xff0c;很多企业存在与外部客户的业务数据往来&#xff0c;如生产型企业与上下游供应链间的制造设计相关文件交换、金融企业与外部监管机构和合作方间的重要客户数据收发、文化娱乐产业内外部关于作品素材的传输交流等。当内外部数据文件交换较为频繁、且…

什么是Web3.0?

鲁迅先生曾言&#xff1a;“人一旦有钱&#xff0c;智商和情商都会是高86.4%&#xff0c;烦恼也会消失100%。”然而&#xff0c;问题来了&#xff0c;钱从哪里来&#xff1f;他只留下了一串数字Dle577。同时&#xff0c;莎士比亚也指出&#xff0c;这个世界上只有少数人能够把握…

《安全软件开发框架(SSDF) 1.1:降低软件漏洞风险的建议》解读(四)

安全软件开发框架SSDF是由美国国家标准与技术研究院发布的关于安全软件开发的一组实践&#xff0c;帮助开发组织减少发布的软件中的漏洞数量&#xff0c;减少利用未检测到或未解决的漏洞的潜在影响&#xff0c;从根本上解决漏洞防止再次发生。本文根据《Secure Software Develo…

防火墙基本原理详解

概要 防火墙是可信和不可信网络之间的一道屏障&#xff0c;通常用在LAN和WAN之间。它通常放置在转发路径中&#xff0c;目的是让所有数据包都必须由防火墙检查&#xff0c;然后根据策略来决定是丢弃或允许这些数据包通过。例如&#xff1a; 如上图&#xff0c;LAN有一台主机和一…

【业务功能篇38】上篇:Springboot+activiti7 工作流引擎 增加网关组件、Assignment分配权限

在前面的一篇文章中&#xff0c;简单举例了一个 工单电子流&#xff0c;【业务功能篇36】Springbootactiviti7 工作流引擎_studyday1的博客-CSDN博客仅有一个子任务&#xff0c;这种一般是针对比较简单的一个遗留问题记录场景&#xff0c;今天再介绍一个&#xff0c;相对比较复…