Spring Boot Security 整合 JWT 实现 无状态的分布式API接口
简介
JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案。JSON Web Token 入门教程 - 阮一峰,这篇文章可以帮你了解JWT的概念。本文重点讲解Spring Boot 结合 jwt ,来实现前后端分离中,接口的安全调用。
快速上手
之前的文章已经对 Spring Security 进行了讲解,这一节对涉及到 Spring Security 的配置不详细讲解。若不了解 Spring Security 先移步到 Spring Boot Security 详解。
建表
DROP TABLE IF EXISTS `user`;
DROP TABLE IF EXISTS `role`;
DROP TABLE IF EXISTS `user_role`;
DROP TABLE IF EXISTS `role_permission`;
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `user` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `role` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `user_role` (
`user_id` bigint(11) NOT NULL,
`role_id` bigint(11) NOT NULL
);
CREATE TABLE `role_permission` (
`role_id` bigint(11) NOT NULL,
`permission_id` bigint(11) NOT NULL
);
CREATE TABLE `permission` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`url` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`description` varchar(255) NULL,
`pid` bigint(11) NOT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO user (id, username, password) VALUES (1,'user','e10adc3949ba59abbe56e057f20f883e');
INSERT INTO user (id, username , password) VALUES (2,'admin','e10adc3949ba59abbe56e057f20f883e');
INSERT INTO role (id, name) VALUES (1,'USER');
INSERT INTO role (id, name) VALUES (2,'ADMIN');
INSERT INTO permission (id, url, name, pid) VALUES (1,'/user/hi','',0);
INSERT INTO permission (id, url, name, pid) VALUES (2,'/admin/hi','',0);
INSERT INTO user_role (user_id, role_id) VALUES (1, 1);
INSERT INTO user_role (user_id, role_id) VALUES (2, 1);
INSERT INTO user_role (user_id, role_id) VALUES (2, 2);
INSERT INTO role_permission (role_id, permission_id) VALUES (1, 1);
INSERT INTO role_permission (role_id, permission_id) VALUES (2, 1);
INSERT INTO role_permission (role_id, permission_id) VALUES (2, 2);
项目结构
resources
|___application.yml
java
|___com
| |____gf
| | |____SpringbootJwtApplication.java
| | |____config
| | | |____.DS_Store
| | | |____SecurityConfig.java
| | | |____MyFilterSecurityInterceptor.java
| | | |____MyInvocationSecurityMetadataSourceService.java
| | | |____MyAccessDecisionManager.java
| | |____entity
| | | |____User.java
| | | |____RolePermisson.java
| | | |____Role.java
| | |____mapper
| | | |____PermissionMapper.java
| | | |____UserMapper.java
| | | |____RoleMapper.java
| | |____utils
| | | |____JwtTokenUtil.java
| | |____controller
| | | |____AuthController.java
| | |____filter
| | | |____JwtTokenFilter.java
| | |____service
| | | |____impl
| | | | |____AuthServiceImpl.java
| | | | |____UserDetailsServiceImpl.java
| | | |____AuthService.java
关键代码
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/spring-security-jwt?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
SecurityConfig
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
//校验用户
auth.userDetailsService( userDetailsService ).passwordEncoder( new PasswordEncoder() {
//对密码进行加密
@Override
public String encode(CharSequence charSequence) {
System.out.println(charSequence.toString());
return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
}
//对密码进行判断匹配
@Override
public boolean matches(CharSequence charSequence, String s) {
String encode = DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
boolean res = s.equals( encode );
return res;
}
} );
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
//因为使用JWT,所以不需要HttpSession
.sessionManagement().sessionCreationPolicy( SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
//OPTIONS请求全部放行
.antMatchers( HttpMethod.OPTIONS, "/**").permitAll()
//登录接口放行
.antMatchers("/auth/login").permitAll()
//其他接口全部接受验证
.anyRequest().authenticated();
//使用自定义的 Token过滤器 验证请求的Token是否合法
http.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
http.headers().cacheControl();
}
@Bean
public JwtTokenFilter authenticationTokenFilterBean() throws Exception {
return new JwtTokenFilter();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
JwtTokenUtil
/**
* JWT 工具类
*/
@Component
public class JwtTokenUtil implements Serializable {
private static final String CLAIM_KEY_USERNAME = "sub";
/**
* 5天(毫秒)
*/
private static final long EXPIRATION_TIME = 432000000;
/**
* JWT密码
*/
private static final String SECRET = "secret";
/**
* 签发JWT
*/
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>(16);
claims.put( CLAIM_KEY_USERNAME, userDetails.getUsername() );
return Jwts.builder()
.setClaims( claims )
.setExpiration( new Date( Instant.now().toEpochMilli() + EXPIRATION_TIME ) )
.signWith( SignatureAlgorithm.HS512, SECRET )
.compact();
}
/**
* 验证JWT
*/
public Boolean validateToken(String token, UserDetails userDetails) {
User user = (User) userDetails;
String username = getUsernameFromToken( token );
return (username.equals( user.getUsername() ) && !isTokenExpired( token ));
}
/**
* 获取token是否过期
*/
public Boolean isTokenExpired(String token) {
Date expiration = getExpirationDateFromToken( token );
return expiration.before( new Date() );
}
/**
* 根据token获取username
*/
public String getUsernameFromToken(String token) {
String username = getClaimsFromToken( token ).getSubject();
return username;
}
/**
* 获取token的过期时间
*/
public Date getExpirationDateFromToken(String token) {
Date expiration = getClaimsFromToken( token ).getExpiration();
return expiration;
}
/**
* 解析JWT
*/
private Claims getClaimsFromToken(String token) {
Claims claims = Jwts.parser()
.setSigningKey( SECRET )
.parseClaimsJws( token )
.getBody();
return claims;
}
}
JwtTokenFilter
@Component
public class JwtTokenFilter extends OncePerRequestFilter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
/**
* 存放Token的Header Key
*/
public static final String HEADER_STRING = "Authorization";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String token = request.getHeader( HEADER_STRING );
if (null != token) {
String username = jwtTokenUtil.getUsernameFromToken(token);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(token, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(
request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
chain.doFilter(request, response);
}
}
AuthServiceImpl
@Service
public class AuthServiceImpl implements AuthService {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
public String login(String username, String password) {
UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken( username, password );
Authentication authentication = authenticationManager.authenticate(upToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails userDetails = userDetailsService.loadUserByUsername( username );
String token = jwtTokenUtil.generateToken(userDetails);
return token;
}
}
关键代码就是这些,其他类代码参照后面提供的源码地址。
验证
登录,获取token
curl -X POST -d "username=admin&password=123456" http://127.0.0.1:8080/auth/login
返回
eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTU1NDQ1MzUwMX0.sglVeqnDGUL9pH1oP3Lh9XrdzJIS42VKBApd2nPJt7e1TKhCEY7AUfIXnzG9vc885_jTq4-h8R6YCtRRJzl8fQ
不带token访问资源
curl -X POST -d "name=zhangsan" http://127.0.0.1:8080/admin/hi
返回,拒绝访问
{
"timestamp": "2019-03-31T08:50:55.894+0000",
"status": 403,
"error": "Forbidden",
"message": "Access Denied",
"path": "/auth/login"
}
携带token访问资源
curl -X POST -H "Authorization: eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTU1NDQ1MzUwMX0.sglVeqnDGUL9pH1oP3Lh9XrdzJIS42VKBApd2nPJt7e1TKhCEY7AUfIXnzG9vc885_jTq4-h8R6YCtRRJzl8fQ" -d "name=zhangsan" http://127.0.0.1:8080/admin/hi
返回正确
hi zhangsan , you have 'admin' role
源码
https://github.com/gf-huanchupk/SpringBootLearning/tree/master/springboot-jwt
推荐阅读
Spring Boot 系列精选
Spring Boot 自定义 starter
Spring Boot 整合 mybatis-plus
Spring Boot 整合 spring cache
Spring Boot 整合 rabbitmq
Spring Boot 整合 elasticsearch
Spring Boot 整合 docker
Spring Boot 整合 elk
Spring Boot Admin 2.0 详解
Spring Boot 整合 apollo
Spring Boot Security 详解
Spring Boot Security 整合 OAuth2 设计安全API接口服务
Spring Boot Security 整合 JWT 实现 无状态的分布式API接口
SpringCloud 系列精选
Spring Cloud Gateway 入门
Spring Cloud Gateway 之 Predict
Spring Cloud Gateway 之 Filter
Spring Cloud Gateway 之 限流
Spring Cloud Gateway 之 服务注册与发现
Dubbo 系列精选
Dubbo 搭建管理控制台
Dubbo 创建 提供者 消费者
Dubbo 配置
Dubbo 高可用
Docker 系列精选
服务 Docker 化
Docker 私有仓库搭建
DockerSwarm 集群环境搭建
DockerSwarm 微服务部署
欢迎扫码或微信搜索公众号《程序员果果》关注我,关注有惊喜~

Spring Boot Security 整合 JWT 实现 无状态的分布式API接口的更多相关文章
- Spring Boot Security 整合 OAuth2 设计安全API接口服务
简介 OAuth是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版.本文重点讲解Spring Boot项目对OAuth2进行的实现,如果你对OAut ...
- Spring Boot Security OAuth2 实现支持JWT令牌的授权服务器
概要 之前的两篇文章,讲述了Spring Security 结合 OAuth2 .JWT 的使用,这一节要求对 OAuth2.JWT 有了解,若不清楚,先移步到下面两篇提前了解下. Spring Bo ...
- Spring Boot Security JWT 整合实现前后端分离认证示例
前面两章节我们介绍了 Spring Boot Security 快速入门 和 Spring Boot JWT 快速入门,本章节使用 JWT 和 Spring Boot Security 构件一个前后端 ...
- spring boot:spring security整合jwt实现登录和权限验证(spring boot 2.3.3)
一,为什么使用jwt? 1,什么是jwt? Json Web Token, 它是JSON风格的轻量级的授权和身份认证规范, 可以实现无状态.分布式的Web应用授权 2,jwt的官网: https:// ...
- Spring Boot:整合Spring Security
综合概述 Spring Security 是 Spring 社区的一个顶级项目,也是 Spring Boot 官方推荐使用的安全框架.除了常规的认证(Authentication)和授权(Author ...
- Spring Security整合JWT,实现单点登录,So Easy~!
前面整理过一篇 SpringBoot Security前后端分离,登录退出等返回json数据,也就是用Spring Security,基于SpringBoot2.1.4 RELEASE前后端分离的情况 ...
- 实战!spring Boot security+JWT 前后端分离架构认证登录!
大家好,我是不才陈某~ 认证.授权是实战项目中必不可少的部分,而Spring Security则将作为首选安全组件,因此陈某新开了 <Spring Security 进阶> 这个专栏,写一 ...
- Spring Boot Security Oauth2之客户端模式及密码模式实现
Spring Boot Security Oauth2之客户端模式及密码模式实现 示例主要内容 1.多认证模式(密码模式.客户端模式) 2.token存到redis支持 3.资源保护 4.密码模式用户 ...
- Spring Boot 应用系列 5 -- Spring Boot 2 整合logback
上一篇我们梳理了Spring Boot 2 整合log4j2的配置过程,其中讲到了Spring Boot 2原装适配logback,并且在非异步环境下logback和log4j2的性能差别不大,所以对 ...
随机推荐
- netty基本用法
1.客户端 ①HelloClient.java public class HelloClient { public static String host = "127.0.0.1" ...
- Android之微信朋友圈UI实现--ExpandableListView+GridView
PS:我们都知道微信,更是知道朋友圈,很多人在朋友圈里卖起了化妆品,打入广告等为自己做一下推广,里面会附带一写好看的图片,上面有标题,有描述,整体布局每场的美观,那么这是怎么实现的呢,有些人可能会单个 ...
- elasticsearch6.6.2在Centos6.9的安装
JDK8 做个记录,以防以后忘记能够查看. 1.elastic是java编写的,先搭建运行环境,6.6.2版本必须要jdk8以上版本才可运行,先官网下载jdk,上传服务器 https://www.or ...
- 深度揭秘腾讯云新一代企业级HTAP数据库TBase核心概念
腾讯云PostgreSQL-XZ(PGXZ)经过公司内部多年业务的打磨,在2017年改名为TBase后,正式对外推出,目前已在政务.医疗.公安.消防.电信.金融等行业等行业的解决方案中大量应用.TBa ...
- Vue 进阶之路(五)
之前的文章我们说了一下 vue 的样式绑定,这篇文章来介绍一下 vue 中的条件渲染,先看下面的代码: <!DOCTYPE html> <html lang="en&quo ...
- Redis的正确使用姿势
前言 说到分布式缓存,可能大多数人脑海浮现的就是redis了,为什么redis能够在竞争激烈的缓存大战中脱颖而出呢?原因无非有一下几点:性能好,丰富的特性跟数据结构,api操作简单.但是用的人多了,就 ...
- OAuth2.0记录
阮一峰老师讲解OAuth2.0 http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html 举例详解: https://www.cnblogs.com/ ...
- openlayers4 入门开发系列之小区信号扇形图篇
前言 openlayers4 官网的 api 文档介绍地址 openlayers4 api,里面详细的介绍 openlayers4 各个类的介绍,还有就是在线例子:openlayers4 官网在线例子 ...
- 开源ERP Odoo仓存功能模块深度应用(一)
基本功能 库位 库位是一个逻辑存货区,可以是一个物理库区,可以是一个货架.货架上的一个货位.库位可以有子库位 库位有虚拟库位和实际库位,实际库位是实际存放货物的库位,虚拟库位是因复式库存记账而虚构的库 ...
- 基于OpenStreetMap计算驾车距离(Java)
最近公司有个项目需要计算6000个点之间的驾车距离,第一时间想到的是利用Google的Distance Matrix API,但是免费Key每天只能计算2500个元素(元素 = 起点数量 * 终点数量 ...