SpringBoot 2 + Spring Security 5 + JWT 的单页应用 Restful 解决方案
此前我已经写过一篇类似的教程,但那时候使用了投机的方法,没有尊重 Spring Security 的官方设计,自己并不感到满意。这段时间比较空,故重新研究了一遍。
特性
- 使用 JWT 进行鉴权,支持 token 过期
- 使用 Ehcache 进行缓存,减少每次鉴权对数据库的压力
- 尽可能贴合 Spring Security 的设计
- 实现注解权限控制
准备
开始本教程的时候希望对下面知识点进行粗略的了解。
- 知道 JWT 的基本概念
- 了解过 Spring Security
本项目中 JWT
密钥是使用用户自己的登入密码,这样每一个 token
的密钥都不同,相对比较安全。
大体思路:
登入:
- POST 用户名密码到
\login
- 请求到达
JwtAuthenticationFilter
中的attemptAuthentication()
方法,获取 request 中的 POST 参数,包装成一个UsernamePasswordAuthenticationToken
交付给AuthenticationManager
的authenticate()
方法进行鉴权。 AuthenticationManager
会从CachingUserDetailsService
中查找用户信息,并且判断账号密码是否正确。- 如果账号密码正确跳转到
JwtAuthenticationFilter
中的successfulAuthentication()
方法,我们进行签名,生成 token 返回给用户。 - 账号密码错误则跳转到
JwtAuthenticationFilter
中的unsuccessfulAuthentication()
方法,我们返回错误信息让用户重新登入。
请求鉴权:
请求鉴权的主要思路是我们会从请求中的 Authorization 字段拿取 token,如果不存在此字段的用户,Spring Security 会默认会用 AnonymousAuthenticationToken()
包装它,即代表匿名用户。
- 任意请求发起
- 到达
JwtAuthorizationFilter
中的doFilterInternal()
方法,进行鉴权。 - 如果鉴权成功我们把生成的
Authentication
用SecurityContextHolder.getContext().setAuthentication()
放入 Security,即代表鉴权完成。此处如何鉴权由我们自己代码编写,后序会详细说明。
准备 pom.xml
<?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.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.7.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>org.inlighting</groupId><artifactId>spring-boot-security-jwt</artifactId><version>0.0.1-SNAPSHOT</version><name>spring-boot-security-jwt</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- JWT 支持 --><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.8.2</version></dependency><!-- cache 支持 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency><!-- cache 支持 --><dependency><groupId>org.ehcache</groupId><artifactId>ehcache</artifactId></dependency><!-- cache 支持 --><dependency><groupId>javax.cache</groupId><artifactId>cache-api</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-test</artifactId><scope>test</scope></dependency><!-- ehcache 读取 xml 配置文件使用 --><dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>2.3.0</version></dependency><!-- ehcache 读取 xml 配置文件使用 --><dependency><groupId>com.sun.xml.bind</groupId><artifactId>jaxb-impl</artifactId><version>2.3.0</version></dependency><!-- ehcache 读取 xml 配置文件使用 --><dependency><groupId>com.sun.xml.bind</groupId><artifactId>jaxb-core</artifactId><version>2.3.0</version></dependency><!-- ehcache 读取 xml 配置文件使用 --><dependency><groupId>javax.activation</groupId><artifactId>activation</artifactId><version>1.1.1</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
pom.xml 配置文件这块没有什么好说的,主要说明下面的几个依赖:
<!-- ehcache 读取 xml 配置文件使用 -->
<dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>2.3.0</version>
</dependency><!-- ehcache 读取 xml 配置文件使用 -->
<dependency><groupId>com.sun.xml.bind</groupId><artifactId>jaxb-impl</artifactId><version>2.3.0</version>
</dependency><!-- ehcache 读取 xml 配置文件使用 -->
<dependency><groupId>com.sun.xml.bind</groupId><artifactId>jaxb-core</artifactId><version>2.3.0</version>
</dependency><!-- ehcache 读取 xml 配置文件使用 -->
<dependency><groupId>javax.activation</groupId><artifactId>activation</artifactId><version>1.1.1</version>
</dependency>
因为 ehcache 读取 xml 配置文件时使用了这几个依赖,而这几个依赖从 JDK 9 开始时是选配模块,所以高版本的用户需要添加这几个依赖才能正常使用。
基础工作准备
接下来准备下几个基础工作,就是新建个实体、模拟个数据库,写个 JWT 工具类这种基础操作。
UserEntity.java
关于 role 为什么使用 GrantedAuthority 说明下:其实是为了简化代码,直接用了 Security 现成的 role 类,实际项目中我们肯定要自己进行处理,将其转换为 Security 的 role 类。
public class UserEntity {public UserEntity(String username, String password, Collection<? extends GrantedAuthority> role) {this.username = username;this.password = password;this.role = role;}private String username;private String password;private Collection<? extends GrantedAuthority> role;public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public Collection<? extends GrantedAuthority> getRole() {return role;}public void setRole(Collection<? extends GrantedAuthority> role) {this.role = role;}
}
ResponseEntity.java
前后端分离为了方便前端我们要统一 json 的返回格式,所以自定义一个 ResponseEntity.java。
public class ResponseEntity {public ResponseEntity() {}public ResponseEntity(int status, String msg, Object data) {this.status = status;this.msg = msg;this.data = data;}private int status;private String msg;private Object data;public int getStatus() {return status;}public void setStatus(int status) {this.status = status;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}public Object getData() {return data;}public void setData(Object data) {this.data = data;}
}
Database.java
这里我们使用一个 HashMap 模拟了一个数据库,密码我已经预先用 Bcrypt
加密过了,这也是 Spring Security 官方推荐的加密算法(MD5 加密已经在 Spring Security 5 中被移除了,不安全)。
用户名 | 密码 | 权限 |
---|---|---|
jack | jack123 存 Bcrypt 加密后 | ROLE_USER |
danny | danny123 存 Bcrypt 加密后 | ROLE_EDITOR |
smith | smith123 存 Bcrypt 加密后 | ROLE_ADMIN |
@Component
public class Database {private Map<String, UserEntity> data = null;public Map<String, UserEntity> getDatabase() {if (data == null) {data = new HashMap<>();UserEntity jack = new UserEntity("jack","$2a$10$AQol1A.LkxoJ5dEzS5o5E.QG9jD.hncoeCGdVaMQZaiYZ98V/JyRq",getGrants("ROLE_USER"));UserEntity danny = new UserEntity("danny","$2a$10$8nMJR6r7lvh9H2INtM2vtuA156dHTcQUyU.2Q2OK/7LwMd/I.HM12",getGrants("ROLE_EDITOR"));UserEntity smith = new UserEntity("smith","$2a$10$E86mKigOx1NeIr7D6CJM3OQnWdaPXOjWe4OoRqDqFgNgowvJW9nAi",getGrants("ROLE_ADMIN"));data.put("jack", jack);data.put("danny", danny);data.put("smith", smith);}return data;}private Collection<GrantedAuthority> getGrants(String role) {return AuthorityUtils.commaSeparatedStringToAuthorityList(role);}
}
UserService.java
这里再模拟一个 service,主要就是模仿数据库的操作。
@Service
public class UserService {@Autowiredprivate Database database;public UserEntity getUserByUsername(String username) {return database.getDatabase().get(username);}
}
JwtUtil.java
自己编写的一个工具类,主要负责 JWT 的签名和鉴权。
public class JwtUtil {// 过期时间5分钟private final static long EXPIRE_TIME = 5 * 60 * 1000;/*** 生成签名,5min后过期* @param username 用户名* @param secret 用户的密码* @return 加密的token*/public static String sign(String username, String secret) {Date expireDate = new Date(System.currentTimeMillis() + EXPIRE_TIME);try {Algorithm algorithm = Algorithm.HMAC256(secret);return JWT.create().withClaim("username", username).withExpiresAt(expireDate).sign(algorithm);} catch (Exception e) {return null;}}/*** 校验token是否正确* @param token 密钥* @param secret 用户的密码* @return 是否正确*/public static boolean verify(String token, String username, String secret) {try {Algorithm algorithm = Algorithm.HMAC256(secret);JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username).build();DecodedJWT jwt = verifier.verify(token);return true;} catch (Exception e) {return false;}}/*** 获得token中的信息无需secret解密也能获得* @return token中包含的用户名*/public static String getUsername(String token) {try {DecodedJWT jwt = JWT.decode(token);return jwt.getClaim("username").asString();} catch (JWTDecodeException e) {return null;}}
}
Spring Security 改造
登入这块,我们使用自定义的 JwtAuthenticationFilter
来进行登入。
请求鉴权,我们使用自定义的 JwtAuthorizationFilter
来处理。
也许大家觉得两个单词长的有点像,
SpringBoot 2 + Spring Security 5 + JWT 的单页应用 Restful 解决方案相关推荐
- springBoot整合spring security+JWT实现单点登录与权限管理前后端分离
在前一篇文章当中,我们介绍了springBoot整合spring security单体应用版,在这篇文章当中,我将介绍springBoot整合spring secury+JWT实现单点登录与权限管理. ...
- springBoot整合spring security+JWT实现单点登录与权限管理前后端分离--筑基中期
写在前面 在前一篇文章当中,我们介绍了springBoot整合spring security单体应用版,在这篇文章当中,我将介绍springBoot整合spring secury+JWT实现单点登录与 ...
- Spring Security Oauth2 JWT 实现用户认证授权功能
Spring Security Oauth2 JWT 一 用户认证授权 1. 需求分析 1.1 用户认证与授权 什么是用户身份认证? 用户身份认证即用户去访问系统资源时系统要求验证用户的身份信息,身份 ...
- SpringBoot集成Spring Security —— 第二章自动登录
文章目录 一.修改login.html 二.两种实现方式 2.1 Cookie 存储 2.2 数据库存储 2.2.1 基本原理 2.2.2 代码实现 三.运行程序 在上一章:SpringBoot集成S ...
- Spring Security整合JWT,实现单点登录,So Easy~!
前面整理过一篇 SpringBoot Security前后端分离,登录退出等返回json数据,也就是用Spring Security,基于SpringBoot2.1.4 RELEASE前后端分离的情况 ...
- SpringBoot集成Spring Security(2)——自动登录
在上一章:SpringBoot集成Spring Security(1)--入门程序中,我们实现了入门程序,本篇为该程序加上自动登录的功能. 文章目录 一.修改login.html 二.两种实现方式 2 ...
- 基于Spring Security与JWT实现单点登录
基于RBAC的权限管理 RBAC(Role-Based Access Control):基于角色的访问控制 当前项目中,RBAC具体的表现为: 管理员表:ams_admin 角色表:ams_role ...
- 【Spring Cloud Alibaba 实战 | 总结篇】Spring Cloud Gateway + Spring Security OAuth2 + JWT 实现微服务统一认证授权和鉴权
一. 前言 hi,大家好~ 好久没更文了,期间主要致力于项目的功能升级和问题修复中,经过一年时间这里只贴出关键部分代码的打磨,[有来]终于迎来v2.0版本,相较于v1.x版本主要完善了OAuth2认证 ...
- SpringBoot 整合 Spring Security 实现安全认证【SpringBoot系列9】
SpringCloud 大型系列课程正在制作中,欢迎大家关注与提意见. 程序员每天的CV 与 板砖,也要知其所以然,本系列课程可以帮助初学者学习 SpringBooot 项目开发 与 SpringCl ...
最新文章
- 专访《花亦山心之月》:朝夕光年首款自研国风手游有啥不一样?
- Chrome View Source Code 那些事
- 线程、同步与锁——Mutex想说爱你不容易
- linux DTS介绍
- centos7开机启动自定义脚本_在Centos 7系统中开启启动自定义脚本的方法
- Huntor中国CRM评估报告连载(一)
- mysql8+新语法_MySQL8.0新特性
- MessageQueue nativePollOnce 一个不一样的 ANR
- 微机 —— 可编程并行接口芯片8255A 应用
- CentOS 7.2 安装Subversion(SVN)
- ​《2020科技趋势报告》重磅发布,AI 和中国,成为未来科技世界关键词
- 我的(此)电脑里面除了磁盘以外,多了一个CD驱动器,删除方法,亲测有效
- 【知识兔】自学Excel之8:数据输入与编辑(基础操作)
- WORD “锁定标记”的功能
- 谁能拒绝一个会动的皮卡丘挂件
- java字节流与字符流的区别编码缓冲
- MATLAB参数估计
- 雄牛PVC地板革新胶地板行业成环保绿色新选择
- vr直播怎么做?vr直播平台有哪些?
- 中国的IT培训班,到底有多赚钱?
热门文章
- R语言笔记5:控制结构
- 公共基因表达数据分析系统genevestigator,再也不愁表达数据的查询和比较了
- R语言ggplot2可视化:自定义设置连续变量图例(legend)宽度(width)、自定义设置连续变量图例位置(position)、自定义设置连续变量图例连续渐变
- python使用matplotlib对比多个模型在测试集上的效果并可视化、设置模型性能可视化结果柱状图(bar plot)标签的小数点位数(例如,强制柱状图标签0.7显示为两位小数0.70)
- R语言WVPlots包可视化克利夫兰点ClevelandDotPlot、并按照分类变量排序进行可视化克利夫兰点ClevelandDotPlot
- VRPM包安装失败解决方案:had non-zero exit status
- 机器学习数据预处理之缺失值:特征删除
- 运维管理工具+chef+puppet+ansible+SaltStack
- 自然语言处理NLP之BERT、BERT是什么、智能问答、阅读理解、分词、词性标注、数据增强、文本分类、BERT的知识表示本质
- 第二章 序列比对——Blast局部比对