简介

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接口的更多相关文章

  1. Spring Boot Security 整合 OAuth2 设计安全API接口服务

    简介 OAuth是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版.本文重点讲解Spring Boot项目对OAuth2进行的实现,如果你对OAut ...

  2. Spring Boot Security OAuth2 实现支持JWT令牌的授权服务器

    概要 之前的两篇文章,讲述了Spring Security 结合 OAuth2 .JWT 的使用,这一节要求对 OAuth2.JWT 有了解,若不清楚,先移步到下面两篇提前了解下. Spring Bo ...

  3. Spring Boot Security JWT 整合实现前后端分离认证示例

    前面两章节我们介绍了 Spring Boot Security 快速入门 和 Spring Boot JWT 快速入门,本章节使用 JWT 和 Spring Boot Security 构件一个前后端 ...

  4. spring boot:spring security整合jwt实现登录和权限验证(spring boot 2.3.3)

    一,为什么使用jwt? 1,什么是jwt? Json Web Token, 它是JSON风格的轻量级的授权和身份认证规范, 可以实现无状态.分布式的Web应用授权 2,jwt的官网: https:// ...

  5. Spring Boot:整合Spring Security

    综合概述 Spring Security 是 Spring 社区的一个顶级项目,也是 Spring Boot 官方推荐使用的安全框架.除了常规的认证(Authentication)和授权(Author ...

  6. Spring Security整合JWT,实现单点登录,So Easy~!

    前面整理过一篇 SpringBoot Security前后端分离,登录退出等返回json数据,也就是用Spring Security,基于SpringBoot2.1.4 RELEASE前后端分离的情况 ...

  7. 实战!spring Boot security+JWT 前后端分离架构认证登录!

    大家好,我是不才陈某~ 认证.授权是实战项目中必不可少的部分,而Spring Security则将作为首选安全组件,因此陈某新开了 <Spring Security 进阶> 这个专栏,写一 ...

  8. Spring Boot Security Oauth2之客户端模式及密码模式实现

    Spring Boot Security Oauth2之客户端模式及密码模式实现 示例主要内容 1.多认证模式(密码模式.客户端模式) 2.token存到redis支持 3.资源保护 4.密码模式用户 ...

  9. Spring Boot 应用系列 5 -- Spring Boot 2 整合logback

    上一篇我们梳理了Spring Boot 2 整合log4j2的配置过程,其中讲到了Spring Boot 2原装适配logback,并且在非异步环境下logback和log4j2的性能差别不大,所以对 ...

随机推荐

  1. 如何将markdown转换为wxml

    话说我要为技术博客写一个小程序版,我的博客解决方案是 hexo + github-page,格式当然是技术控们喜欢的 markdown 了 .但小程序使用的却是独有的模版语言 WXML.我总不能把之前 ...

  2. 1. CMake 系列 - 从零构建动态库和静态库

    目录 1. 文件目录结构 2. 库文件源代码 3. 编译生成库文件 1. 文件目录结构 首先创建如下目录结构: └── lib ├── build # ├── CMakeLists.txt └── s ...

  3. 《HelloGitHub》第 32 期

    公告 新加入了 2 位机器学期的小伙伴负责机器学习专栏.项目的首页增加合作组织一栏,如有开源组织有意合作可以点击联系我. 我们还在路上,不停地前行. <HelloGitHub>第 32 期 ...

  4. 我的秋招经验分享(已拿BAT头条网易滴滴)

    微信公众号[程序员江湖] 作者黄小斜,斜杠青年,某985硕士,阿里 Java 研发工程师,于 2018 年秋招拿到 BAT 头条.网易.滴滴等 8 个大厂 offer,目前致力于分享这几年的学习经验. ...

  5. python爬虫踩坑教程

    我们的目标是爬取下面这个个网址上的2010~2018年的数据 http://stockdata.stock.hexun.com/zrbg/Plate.aspx?date=2015-12-31 获取我们 ...

  6. PageHelper分页异常(java.base/java.util.ArrayList cannot be cast to com.github.pagehelper.Page)

    在SqlMapConfig.xml里面配置分页插件 applicationContext-service.xml里面的配置,我出现问题谁因为,在salSessionFactory里没注入全局配置文件

  7. vue中路由按需加载的几种方式

    使用vue-cli构建项目后,我们会在Router文件夹下面的index.js里面引入相关的路由组件,如: import Hello from '@/components/Hello' import ...

  8. NLP第八条

    今日!我虽啥也没干,但我还是有着学习的心的~          也许是“遵义会议”呢   那也说不定 留下这句话再说 哈哈哈哈哈哈 只能抄抄NLP第八条了  谁叫我选了个灰常有意义的通识课咧 八,每一 ...

  9. ubuntu安装rpm的方法

    Ubuntu的软件包格式是deb,如果要安装rpm的包,则要先用alien把rpm转换成deb. sudo apt-get install alien #alien默认没有安装,所以首先要安装它 su ...

  10. ASP.NET Core 中断请求了解一下(翻译)

    ASP.NET Core知多少系列:总体介绍及目录 本文所讲方式仅适用于托管在Kestrel Server中的应用.如果托管在IIS和IIS Express上时,ASP.NET Core Module ...