目录
SSO单点登录
SOO(Single Sign On) 单点登录
单点流程
cookie是用来在客户端存储数据的工具。
1、在其中一个子系统中登录,转到登录系统,在登录系统中完成登录,完成登录后向发起登录的子系统写入一个cookie
2、其他系统进行登录时,先判断是否存在token信息,存在及返回用户信息,没有统一到登陆地址页面进行登入,其他系统进行刷新即可
3、cookie中的子系统的域名,保持一致,所有的子系统才能访问到这个cookie
实现多系统单点登录
1、以springboot为项目背景
<?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>
<groupId>org.example</groupId>
<artifactId>sso</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>sso-vip</module>
<module>sso-main</module>
<module>sso-cart</module>
<module>sso-login</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath />
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring.boot.version>2.1.3.RELEASE</spring.boot.version>
<junit.version>4.11</junit.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!--测试包-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!--lombok简写-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<!-- SpringBoot的依赖配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.boot.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!--druid 连接池-->
<!-- <dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>-->
<!--spring测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<!--maven打包插件-->
</project>
2、修改host文件,刷新dns
#sso
127.0.0.1 www.codeshop.com
127.0.0.1 vip.codeshop.com
127.0.0.1 cart.codeshop.com
127.0.0.1 login.codeshop.com
3、创建父项目及多个子系统项目
<?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">
<parent>
<artifactId>sso</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sso-cart</artifactId>
<packaging>war</packaging>
<name>sso-cart Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
</properties>
<dependencies>
</dependencies>
<build>
</build>
</project>
<?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">
<parent>
<artifactId>sso</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sso-login</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>sso-login Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
</properties>
<dependencies>
<!--父依赖-->
<!--<dependency>
<groupId>org.example</groupId>
<artifactId>sso</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>-->
</dependencies>
<build>
</build>
</project>
<?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">
<parent>
<artifactId>sso</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sso-main</artifactId>
<packaging>war</packaging>
<name>sso-main Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
</properties>
<dependencies>
</dependencies>
<build>
</build>
</project>
<?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">
<parent>
<artifactId>sso</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sso-vip</artifactId>
<packaging>war</packaging>
<name>sso-vip Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
</dependencies>
<build>
</build>
</project>
3.1、启动类(单个)
package com.cn;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
/**
* @创建人 明
*/
@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
3.2、配置(单个)
server:
port: 9003
4、登录系统,负责token生成,写入。子系统登录页面拦截,负责db用户信息校验,登入用户信息退出
package com.cn.hm.controller;
import com.cn.hm.util.LogginCacheUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.thymeleaf.util.StringUtils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpSession;
/*
*@描述
*@参数 视图控制
*@返回值
*@创建人 HM
*@创建时间
*@修改人和其它信息
*/
@Controller
@RequestMapping("/view")
public class ViewController {
/**
*@描述
*@参数 跳转到登入页面
*@返回值
*@创建人 HM
*@创建时间
*@修改人和其它信息
*/
@GetMapping("/login")
public String toLogin(@RequestParam(required = false,defaultValue = "") String target
, HttpSession session
, @CookieValue(required = false,value = "TOKEN")Cookie cookie){
if(StringUtils.isEmpty(target)){//主页地址
target="http://www.codeshop.com:9002/view/index";
}
//进入登入系统,判断是否存在token
if(cookie!=null && LogginCacheUtil.loginUser.get(cookie.getValue())!=null){
return "redirect:"+target;
}
//地址校验
//存入本次交互会话
session.setAttribute("target",target);
return "login";
}
}
package com.cn.hm.controller;
import com.cn.hm.pojo.User;
import com.cn.hm.util.LogginCacheUtil;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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;
import org.thymeleaf.util.StringUtils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
/**
*@描述
*@参数 登录控制
*@返回值
*@创建人 HM
*@创建时间
*@修改人和其它信息
*/
@Controller
@RequestMapping("/login")
public class LoginController {
//private static final Logger LOGGER= (Logger) LoggerFactory.getLogger(LoginController.class);
private static Set<User> dbuser;
static{
dbuser=new HashSet<>();
dbuser.add(new User(0,"zhangsan","123456"));
dbuser.add(new User(1,"lisi","123456"));
dbuser.add(new User(2,"wangwu","123456"));
dbuser.add(new User(3,"hm","123456"));
}
@PostMapping
public String doLogin(User user, HttpSession session, HttpServletResponse response){
String target= (String) session.getAttribute("target");
//判断用户是否存在
Optional<User> first = dbuser.stream().filter(dbuser ->
dbuser.getPassword().equals(user.getPassword())
&&
dbuser.getUsername().equals(dbuser.getUsername())).findFirst();
if(first.isPresent()){
//保存用户信息
String token = UUID.randomUUID().toString();
Cookie cookie=new Cookie("TOKEN",token);
cookie.setDomain("codeshop.com");
cookie.setMaxAge(60);
response.addCookie(cookie);
LogginCacheUtil.loginUser.put(token,user);
}else{
//提示信息|返回当前页面
session.setAttribute("msg","用户名或密码错误");
return "login";
}
return "redirect:"+target;
}
/*
*@描述
*@参数 根据token返回用户信息
*@返回值
*@创建人 HM
*@创建时间
*@修改人和其它信息
*/
@GetMapping("/info")
@ResponseBody
public ResponseEntity<User> getUserInfo( String token){
if(!StringUtils.isEmpty(token)){
return ResponseEntity.ok(LogginCacheUtil.loginUser.get(token));
}else{
return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
}
}
}
6、子系统负责检查是否存在token,没有重定向到登入系统页面,地址校验
package com.cn.hm.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;
import org.thymeleaf.util.StringUtils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Map;
@Controller
@RequestMapping("/view")
public class IndexController {
/* private static final Logger LOGGER= (Logger) LoggerFactory.getLogger(IndexController.class);*/
@Autowired
private RestTemplate testTemplate;
private static String LOGIN_INFO_ADDRESS="http://login.codeshop.com:9001/login/info?token=";
@GetMapping("/index")
public String toIndex(@CookieValue(required = false,value = "TOKEN")Cookie cookie, HttpSession session){
if(cookie != null && !StringUtils.isEmpty(cookie.getValue())){
System.out.println("result"+LOGIN_INFO_ADDRESS);
Map result = testTemplate.getForObject(LOGIN_INFO_ADDRESS+cookie.getValue(), Map.class);
//LOGGER.info("认证信息"+result);
System.out.println("result"+result);
session.setAttribute("logginUser",result);
}
return "index";
}
@GetMapping("/quit")
public String toLoggin(@CookieValue(required = false,value = "TOKEN")Cookie cookie,
HttpServletResponse response,
HttpSession session){
if(cookie != null && !StringUtils.isEmpty(cookie.getValue())){
//单位为秒
cookie.setMaxAge(0);
response.addCookie(cookie);
session.removeAttribute("logginUser");
}
return "index";
}
}
JWT单点登录
什么是jwt?
JSON Web Token (JWT) 是一个开放标准 ( RFC 7519 ),它定义了一种紧凑且自包含的方式,用于在各方之间作为 JSON 对象安全地传输信息。
场景
授权:这是使用 JWT 最常见的场景。用户登录后,每个后续请求都将包含 JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小,并且能够轻松跨不同域使用。
信息交换:JSON Web Tokens 是一种在各方之间安全传输信息的好方法。因为 JWT 可以被签名——例如,使用公钥/私钥对——你可以确定发件人就是他们所说的那样。此外,由于使用标头和有效负载计算签名,因此您还可以验证内容是否未被篡改。
与传统的session认证有什么区别?
1、传统验证,数据量大是会消耗服务端内存
2、 基于cookie来识别用户,容易受到跨站伪造攻击, 不利于系统安全
3、在分布式系统中,不利于信息共享和集群应用,需要额外第三方工具做session信息共享
jwt认证流程
1、通过web表单将自己的用户名和密码发送到后端的接口,post请求,建议通过ssl加密的传输
2、后端对用户名和密码进行验证成功后,将用户的id等其他信息作为jwt payload(负载),将其头部分别进行base64编码拼接后签名,形成一个jwt(token)
。形成的jwt就是一个形同11.zz.xx的字符串。三部分:头.负载.签名组成,中间由点进行隔开
3、后端将jwt字符串作为登录成功后的结果返回给前端。前端将返回后的结果保存在localstorage或sessionStorage上,退出登录时将前端保存的jwt删除即可。
4、前端在每次请求时将jwt放入http head中的authorization位。(解决xss和xsrf问题)
5、后端检查是否存在,如存在验证jwt的有效性。例如签名是否正确;检查token是否过期;检查token的接收方是否是自己
6、验证通过后使用jwt中包含的用户信息进行其他逻辑操作,返回相应的结果。
优势
1、简洁(compact):可以通过url,post参数或者http headeer发送,数据量小,传输数独快
2、自包含:负载中包含了所有用户所需要的信息,避免多次数据库查询
3、token是以json加密的形式保存在客户端的,所以jwt是跨语言的
4、不需要在服务端保存会话信息,特别适用于分布式微服务
jwt的结构是什么?
令牌组成
1、标头(header)
2、有效负荷(payload)
3、签名(Signature)
header.payload.Signature
分析:
header:有两部分组成:令牌的类型和所使用的签名算法.例如HMAC SHA256或RSA.使用base64编码成jwt结构的第一部分。不允许在负载中加入敏感信息
BASE64UrlEncode(header):
{
“alg”:“HS256”,
“typ”:“jwt”
}
payload:第二部分有效负载:声明有关用户和其他数据的声明。同样用base64编码组成jwt结构的第二部分.不允许在负载中加入敏感信息
BASE64UrlEncode(payload):
{
“user_id”:“ajsdjfklasjdkljflkasjd213134kjfkaljsdlkfj”,
“user_name”:“hm”,
“domin”:true
}
signature:第三部分由前两部分base64编码后+自定义的随机密钥组成。HS256签名的作用是保证jwt没有被篡改过。
HMACSHA256(BASE64UrlEncode(header)+"."+BASE64UrlEncode(header),secret);
例如:BASE64UrlEncode(header)+"."+BASE64UrlEncode(payload)+“.”+HMACSHA256(BASE64UrlEncode(header)+"."+BASE64UrlEncode(payload),secret)
jwt常现异常信息
signatureverificationException: 签名不一致异常
TokenecpiredException: 令牌过期异常
algorithmMismatchException: 算法不匹配异常
Incalidclaimexception: 失效的payload异常
功能实例
需求小故事:
1、实现登入成功token生成,2、生成token后,登入前的token验证返回用户信息
配置信息-application.properties
# 应用名称
spring.application.name=jwt
spring.http.converters.preferred-json-mapper=gson
# 数据库信息
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/jwt?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
mybatis.type-aliases-package=com.example.jwt.entity
mybatis.mapper-locations=classpath:com/cn/jwt/mapper/*.xml
logging.level.com.example.jwt.dao=debug
# 应用服务 WEB 访问端口
server.port=8080
# THYMELEAF (ThymeleafAutoConfiguration)
# 开启模板缓存(默认值: true )
spring.thymeleaf.cache=true
# 检查模板是否存在,然后再呈现
spring.thymeleaf.check-template=true
# 检查模板位置是否正确(默认值 :true )
spring.thymeleaf.check-template-location=true
#Content-Type 的值(默认值: text/html )
spring.thymeleaf.content-type=text/html
# 开启 MVC Thymeleaf 视图解析(默认值: true )
spring.thymeleaf.enabled=true
# 模板编码
spring.thymeleaf.encoding=UTF-8
# 要被排除在解析之外的视图名称列表,⽤逗号分隔
spring.thymeleaf.excluded-view-names=
# 要运⽤于模板之上的模板模式。另⻅ StandardTemplate-ModeHandlers( 默认值: HTML5)
spring.thymeleaf.mode=HTML5
# 在构建 URL 时添加到视图名称前的前缀(默认值: classpath:/templates/ )
spring.thymeleaf.prefix=classpath:/templates/
# 在构建 URL 时添加到视图名称后的后缀(默认值: .html )
spring.thymeleaf.suffix=.html
pom.xml - jar包
<?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>
<groupId>com.example</groupId>
<artifactId>jwt</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>jwt</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.1.18.RELEASE</spring-boot.version>
<spring-cloud.version>Greenwich.SR6</spring-cloud.version>
</properties>
<dependencies>
<!-- slf4j-log4j12 -->
<!-- <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
<scope>test</scope>
</dependency>-->
<!--jwt包-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.7.0</version>
</dependency>
<!--mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<!--模板插件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--web服务-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
<!--测试包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!--打包插件-->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.18.RELEASE</version>
<configuration>
<mainClass>com.example.jwt.JwtApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.jwt.dao.UserDao">
<select id="login" parameterType="User" resultType="User">
select * from user
where userName = #{userName} and userPassword = #{userPassword}
</select>
</mapper>
dao
package com.example.jwt.dao;
import com.example.jwt.entity.User;
import org.apache.ibatis.annotations.Mapper;
/**
*@描述
*@参数
*@返回值
*@创建人 HM
*@创建时间
*@修改人和其它信息
*/
@Mapper
public interface UserDao {
User login(User user);
}
services
package com.example.jwt.service;
import com.example.jwt.entity.User;
/**
*@描述
*@参数 用户验证业务接口
*@返回值
*@创建人 HM
*@创建时间
*@修改人和其它信息
*/
public interface UserService {
User login(User user);
}
----
package com.example.jwt.service.impl;
import com.example.jwt.dao.UserDao;
import com.example.jwt.entity.User;
import com.example.jwt.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
/**
*@描述
*@参数 登入业务验证
*@返回值
*@创建人 HM
*@创建时间
*@修改人和其它信息
*/
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public User login(User user) {
User userVal = userDao.login(user);
if(!StringUtils.isEmpty(userVal)){
return userVal;
}
throw new RuntimeException("认证失败---");
}
}
controller
package com.example.jwt.controller;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.example.jwt.entity.User;
import com.example.jwt.service.UserService;
import com.example.jwt.util.JWTUtils;
import lombok.extern.slf4j.Slf4j;
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.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@RestController
@Slf4j
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/user/login")
public Map<String,Object> toIndex(User user){
log.info("用户信息"+user.getUserName()+":"+user.getUserPassword());
Map<String,Object> map = new HashMap<>();
try{
User login = userService.login(user);
Map<String,String> payload= new HashMap<>();
payload.put("userId",login.getUserId());
payload.put("userName",login.getUserName());
//生成token
String token = JWTUtils.getToken(payload);
map.put("state",true);
map.put("msg","认证成功");
map.put("token",token);//存到客户端缓存 LocalStorage | SessionStorage
}catch (Exception e){
map.put("state",false);
map.put("msg",e.getMessage());
}
return map;
}
/**
*@描述
*@参数 token验证在进行登入验证
*@返回值
*@创建人 HM
*@创建时间
*@修改人和其它信息
*/
@PostMapping(name = "/user/valaToken")
public Map<String,Object> validateToken(HttpServletRequest request){
String token = request.getHeader("token");
DecodedJWT tokenInfo = JWTUtils.getTokenInfo(token);
Map<String,Object> map= new HashMap<>();
map.put("用户信息",tokenInfo.getClaim("name").asString());
log.info("当前token",token);
map.put("state",true);
map.put("msg","请求成功!");
return map;
}
}
interceptors
package com.example.jwt.interceptors;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.example.jwt.util.JWTUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
*@描述
*@参数 jwt拦截器 (token验证)
*@返回值
*@创建人 HM
*@创建时间
*@修改人和其它信息
*/
public class JWTInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HashMap<String, Object> map = new HashMap<>();
String token = request.getHeader("token");
try {
JWTUtils.verify(token);
return true;
}catch (SignatureVerificationException e){
e.printStackTrace();
map.put("msg","无效签名");
}catch (TokenExpiredException e){
e.printStackTrace();
map.put("msg","token过期");
}catch (AlgorithmMismatchException e){
e.printStackTrace();
map.put("msg","token算法不一致");
}catch (Exception e){
e.printStackTrace();
map.put("msg","无效签名");
}
map.put("state",false);
String json = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(json);
return false;
}
}
config
package com.example.jwt.config;
import com.example.jwt.interceptors.JWTInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
*@描述
*@参数 注册拦截器
*@返回值
*@创建人 HM
*@创建时间
*@修改人和其它信息
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JWTInterceptor())
.addPathPatterns("/user/valaToken")//拦截
.excludePathPatterns("/user/login");//放行
}
}
entity
package com.example.jwt.entity;
import java.io.Serializable;
/**
*@author Administrator
* @描述
*@参数 验证实体
*@返回值
*@创建人 HM
*@创建时间
*@修改人和其它信息
*/
public class User implements Serializable {
private String userId;
private String userName;
private String userPassword;
User(){
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserPassword() {
return userPassword;
}
public void setUserPassword(String userPassword) {
this.userPassword = userPassword;
}
@Override
public String toString() {
return "User{" +
"userId='" + userId + '\'' +
", userName='" + userName + '\'' +
", userPassword='" + userPassword + '\'' +
'}';
}
}
utils
package com.example.jwt.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.apache.logging.log4j.Logger;
import org.junit.jupiter.api.Test;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
*@描述
*@参数 token信息逻辑
*@返回值
*@创建人 HM
*@创建时间
*@修改人和其它信息
*/
public class JWTUtils {
private static String SING = "token!Q@W3e4r";
public static final long EXPIRE_DATE=259200000;//1000*60*60*24*3
/**
* 生成token
* @param map //传入payload
* @return 返回token
*/
public static String getToken(Map<String,String> map){
if(StringUtils.isEmpty(map)){
throw new RuntimeException("用户载入信息失败");
}
JWTCreator.Builder builder = JWT.create();
map.forEach((k,v)->{
builder.withClaim(k,v);
});
//过期时间
builder.withExpiresAt(new Date(System.currentTimeMillis()+EXPIRE_DATE))
//签发人
.withIssuer("hm")
//签发时间
.withIssuedAt(new Date());
return builder.sign(Algorithm.HMAC256(SING));
}
/**
* 验证token // 如果验证通过,则不会报错,否则会抛出异常
* @param token
* @return
*/
public static DecodedJWT verify(String token){
if(StringUtils.isEmpty(token)){
throw new RuntimeException("token验证失败1:"+token);
}
try {
return JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
}catch (Exception e){
throw new RuntimeException("token验证失败2:"+e.getMessage());
//..处理视图
}
}
/**
* 获取token中payload
* @param token
* @return
*/
public static DecodedJWT getTokenInfo(String token){
if(StringUtils.isEmpty(token)){
throw new RuntimeException("获取token信息:"+token);
}
return JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
}
@Test
public void testUtil(){
Map<String,String> map=new HashMap<String,String>();
//存放的信息流
map.put("userName","hm");
map.put("userAccount","1343876419");
String token = JWTUtils.getToken(map);
//生成的token
System.out.println(token);
//eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
// eyJ1c2VyQWNjb3VudCI6IjEzNDM4NzY0MTkiLCJ1c2VyTmFtZSI6ImhtIiwiZXhwIjoxNjIzMjUwOTA4fQ.
// lyQKtiO7-UIEPSIM8ch6Tb6YO7MmolntG1zmFYhbZ0o
//取编码后的token
//"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyTmFtZSI6ImhtIiwiZXhwIjoxNjIzNDkzNjkyLCJ1c2VySWQiOiIxMjMxMmpzaGFzZGprZmFzZCJ9.Kx6K7NwIzfKlE0ZXlaE1yH2MrrxDuz5FBBhxjv-jx44");
DecodedJWT verify = JWTUtils.getTokenInfo(token);
//获取单个的jwt中的信息
System.out.println(">>>"+verify.getClaims().get("userName").asString());
//token验证
DecodedJWT verifyInfoFlag = JWTUtils.verify(token);
if(!StringUtils.isEmpty(verifyInfoFlag)){
System.out.println("验证通过");
}else{
System.out.println("验证失败");
}
/*DecodedJWT verifyInfoFlag = JWTUtils.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyTmFtZSI6ImhtIiwiZXhwIjoxNjIzNDkzNjkyLCJ1c2VySWQiOiIxMjMxMmpzaGFzZGprZmFzZCJ9.Kx6K7NwIzfKlE0ZXlaE1yH2MrrxDuz5FBBhxjv-jx44");
if(!StringUtils.isEmpty(verifyInfoFlag)){
System.out.println("验证通过");
}else{
System.out.println("验证失败");
}*/
}
}