前面整理过一篇 SpringBoot Security前后端分离,登录退出等返回json数据,也就是用Spring Security,基于SpringBoot2.1.4 RELEASE前后端分离的情况下,实现了登陆登出的功能,亮点就在于以JSON的形式接收返回参数。这个是针对单个后台服务的, 登录信息都存储在SecurityContextHolder缓存里。如果是两个或两个以上的应用呢,那该怎么办?Session是不能用了,Cookie自然也不能用,毕竟它俩是一对的。

曾想过用OAuth2来解决这个问题,但是OAuth2太复杂,首先理解概念就需要花费一些时间,而且里面的授权服务器、资源服务器、客户端等等让人傻傻分不清,还有四种授权模式,要反复衡量,到底要用哪一种,概念还没有扯清楚就开始纠结使用哪一个了。从概念入手不是个好主意,也不是个轻松的主意。在理解OAuth2的过程中,想到自己的项目是前后端分离的,离不开JSON,无意中遇见JWT。JWT是什么玩意,咦,难道是自己苦苦寻求的吗?!

那么,什么是JWT呢?看看专家介绍 阮一峰的网络日志,才知道,JWT 是JSON Web Token的简称,它解决的就是跨域问题。看来,要找的就是它,简单的,但也是管用的。

继续深究,JWT到底是怎样和SpringSecurity结合的呢。下面上代码,在上代码前先说明一下,在本次实例中,涉及到两个项目,一个项目是登录的项目A,另一个项目是根据token进行访问的项目B。其中B项目没有登录,也不会涉及登录,只要有Token就可以访问,Token失效了就访问不了了。

A项目是登录的项目,也是一个只能通过登录进行访问的后台服务。B项目就是一个服务,只要用户在A项目登录了,就可以访问。

设计图-1

A项目配置,代码如下

第一步,A项目 POM.xml 引入文件
 <!-- spring-security 和 jwt 引入 --><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> 
第二步,A项目SecurityConfig配置
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import com.example.demo.filter.JWTAuthenticationFilter; import com.example.demo.filter.JWTLoginFilter; /** * SpringSecurity的配置 * 参考网址:https://blog.csdn.net/sxdtzhaoxinguo/article/details/77965226 * @author 程就人生 * @date 2019年5月26日 */ @Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService myCustomUserService; @Autowired private MyPasswordEncoder myPasswordEncoder; @Override protected void configure(HttpSecurity http) throws Exception { http //关闭跨站请求防护 .cors().and().csrf().disable() //允许不登陆就可以访问的方法,多个用逗号分隔 .authorizeRequests().antMatchers("/test").permitAll() //其他的需要授权后访问 .anyRequest().authenticated() .and() //增加登录拦截 .addFilter(new JWTLoginFilter(authenticationManager())) //增加是否登陸过滤 .addFilter(new JWTAuthenticationFilter(authenticationManager())) // 前后端分离是无状态的,所以暫時不用session,將登陆信息保存在token中。 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); } @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { //覆盖UserDetailsService类 auth.userDetailsService(myCustomUserService) //覆盖默认的密码验证类 .passwordEncoder(myPasswordEncoder); } } 
第三步,实现配置文件中自定义的类
  1. MyPasswordEncoder类实现了默认的PasswordEncoder 接口,可以对密码加密和密码对比进行个性化定制
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;/*** 自定义的密码加密方法,实现了PasswordEncoder接口* @author 程就人生 * @date 2019年5月26日 */ @Component public class MyPasswordEncoder implements PasswordEncoder { @Override public String encode(CharSequence charSequence) { //加密方法可以根据自己的需要修改 return charSequence.toString(); } @Override public boolean matches(CharSequence charSequence, String s) { return encode(charSequence).equals(s); } } 
  1. MyCustomUserService 实现了框架默认的UserDetailsService,可以根据username从数据库获取用户,查看用户是否存在
/*** 登录专用类,用户登陆时,通过这里查询数据库* 自定义类,实现了UserDetailsService接口,用户登录时调用的第一类* @author 程就人生* @date 2019年5月26日*/
@Component public class MyCustomUserService implements UserDetailsService { /** * 登陆验证时,通过username获取用户的所有权限信息 * 并返回UserDetails放到spring的全局缓存SecurityContextHolder中,以供授权器使用 */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //在这里可以自己调用数据库,对username进行查询,看看在数据库中是否存在 MyUserDetails myUserDetail = new MyUserDetails(); myUserDetail.setUsername(username); myUserDetail.setPassword("123456"); return myUserDetail; } } 
  1. MyUserDetails 实现了框架的UserDetails接口,可以在该类中根据需要添加自己必需的属性
import java.util.Collection;import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;/** * 实现了UserDetails接口,只留必需的属性,也可添加自己需要的属性 * @author 程就人生 * @date 2019年5月26日 */ public class MyUserDetails implements UserDetails { private static final long serialVersionUID = 1L; //登录用户名 private String username; //登录密码 private String password; private Collection<? extends GrantedAuthority> authorities; public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public void setAuthorities(Collection<? extends GrantedAuthority> authorities) { this.authorities = authorities; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return this.authorities; } @Override public String getPassword() { return this.password; } @Override public String getUsername() { return this.username; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } } 
  1. JWTLoginFilter 实现了框架自带的UsernamePasswordAuthenticationFilter 接口,对拦截做处理,以便登录成功后,在头部设置token返回;不管登录成功还是失败,都有JSON数据返回
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap; import java.util.Map; import javax.servlet.FilterChain; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import com.example.demo.entity.User; import com.example.demo.security.MyUserDetails; import com.fasterxml.jackson.databind.ObjectMapper; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; /** * 验证用户名密码正确后,生成一个token,放在header里,返回给客户端 * @author 程就人生 * @date 2019年5月26日 */ public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter { private AuthenticationManager authenticationManager; public JWTLoginFilter(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } /** * 接收并解析用户凭证,出現错误时,返回json数据前端 */ @Override public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res){ try { User user =new ObjectMapper().readValue(req.getInputStream(), User.class); return authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( user.getUsername(), user.getPassword(), new ArrayList<>()) ); } catch (Exception e) { try { //未登錄出現賬號或密碼錯誤時,使用json進行提示 res.setContentType("application/json;charset=utf-8"); res.setStatus(HttpServletResponse.SC_UNAUTHORIZED); PrintWriter out = res.getWriter(); Map<String,Object> map = new HashMap<String,Object>(); map.put("code",HttpServletResponse.SC_UNAUTHORIZED); map.put("message","账号或密码错误!"); out.write(new ObjectMapper().writeValueAsString(map)); out.flush(); out.close(); } catch (Exception e1) { e1.printStackTrace(); } throw new RuntimeException(e); } } /** * 用户登录成功后,生成token,并且返回json数据给前端 */ @Override protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res,FilterChain chain, Authentication auth){ //json web token构建 String token = Jwts.builder() //此处为自定义的、实现org.springframework.security.core.userdetails.UserDetails的类,需要和配置中设置的保持一致 //此处的subject可以用一个用户名,也可以是多个信息的组合,根据需要来定 .setSubject(((MyUserDetails) auth.getPrincipal()).getUsername()) //设置token过期时间,24小時 .setExpiration(new Date(System.currentTimeMillis() + 60 * 60 * 24 * 1000)) //设置token签名、密钥 .signWith(SignatureAlgorithm.HS512, "MyJwtSecret") .compact(); //返回token res.addHeader("Authorization", "Bearer " + token); try { //登录成功時,返回json格式进行提示 res.setContentType("application/json;charset=utf-8"); res.setStatus(HttpServletResponse.SC_OK); PrintWriter out = res.getWriter(); Map<String,Object> map = new HashMap<String,Object>(); map.put("code",HttpServletResponse.SC_OK); map.put("message","登陆成功!"); out.write(new ObjectMapper().writeValueAsString(map)); out.flush(); out.close(); } catch (Exception e1) { e1.printStackTrace(); } } } 
  1. JWTAuthenticationFilter 类实现了BasicAuthenticationFilter 接口,对Controller中需要登录后才能访问的方法进行了拦截,没有登录,则不能访问,返回JSON信息进行提示
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map; import javax.servlet.FilterChain; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import com.fasterxml.jackson.databind.ObjectMapper; import io.jsonwebtoken.Jwts; /** * 是否登陆验证方法 * @author 程就人生 * @date 2019年5月26日 */ public class JWTAuthenticationFilter extends BasicAuthenticationFilter { public JWTAuthenticationFilter(AuthenticationManager authenticationManager) { super(authenticationManager); } /** * 對請求進行過濾 */ @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) { try { //请求体的头中是否包含Authorization String header = request.getHeader("Authorization"); //Authorization中是否包含Bearer,有一个不包含时直接返回 if (header == null || !header.startsWith("Bearer ")) { chain.doFilter(request, response); responseJson(response); return; } //获取权限失败,会抛出异常 UsernamePasswordAuthenticationToken authentication = getAuthentication(request); //获取后,将Authentication写入SecurityContextHolder中供后序使用 SecurityContextHolder.getContext().setAuthentication(authentication); chain.doFilter(request, response); } catch (Exception e) { responseJson(response); e.printStackTrace(); } } /** * 未登錄時的提示 * @param response */ private void responseJson(HttpServletResponse response){ try { //未登錄時,使用json進行提示 response.setContentType("application/json;charset=utf-8"); response.setStatus(HttpServletResponse.SC_FORBIDDEN); PrintWriter out = response.getWriter(); Map<String,Object> map = new HashMap<String,Object>(); map.put("code",HttpServletResponse.SC_FORBIDDEN); map.put("message","请登录!"); out.write(new ObjectMapper().writeValueAsString(map)); out.flush(); out.close(); } catch (Exception e1) { e1.printStackTrace(); } } /** * 通过token,获取用户信息 * @param request * @return */ private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) { String token = request.getHeader("Authorization"); if (token != null) { //通过token解析出用户信息 String user = Jwts.parser() //签名、密钥 .setSigningKey("MyJwtSecret") .parseClaimsJws(token.replace("Bearer ", "")) .getBody() .getSubject(); //不为null,返回 if (user != null) { return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>()); } return null; } return null; } } 
  1. 在登录过滤器中接收参数的实体类,也可以直接接收,这一个类不是必须的
public class User { private long id; private String username; private String password; public long getId() { return id; } 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; } } 

B项目的配置

第一步,在pom中引入必须的架包
<!-- spring-security 和 jwt 引入 --><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> 
第二步,增加SecurityConfig配置文件
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy; import com.example.demo.filter.JWTAuthenticationFilter; /** * SpringSecurity的配置 * 参考网址:https://blog.csdn.net/sxdtzhaoxinguo/article/details/77965226 * @author 程就人生 * @date 2019年5月26日 */ @Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http //关闭跨站请求防护 .cors().and().csrf().disable() //允许不登陆就可以访问的方法,多个用逗号分隔 .authorizeRequests() //其他的需要授权后访问 .anyRequest().authenticated() .and() //增加是否登陸过滤 .addFilter(new JWTAuthenticationFilter(authenticationManager())) // 前后端分离是无状态的,所以暫時不用session,將登陆信息保存在token中。 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); } } 
第三步,在增加对方法是否登录进行拦截的过滤器
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map; import javax.servlet.FilterChain; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import com.fasterxml.jackson.databind.ObjectMapper; import io.jsonwebtoken.Jwts; /** * 是否登陆验证方法 * @author 程就人生 * @date 2019年5月26日 */ public class JWTAuthenticationFilter extends BasicAuthenticationFilter { public JWTAuthenticationFilter(AuthenticationManager authenticationManager) { super(authenticationManager); } /** * 對請求進行過濾 */ @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) { try { //请求体的头中是否包含Authorization String header = request.getHeader("Authorization"); //Authorization中是否包含Bearer,有一个不包含时直接返回 if (header == null || !header.startsWith("Bearer ")) { chain.doFilter(request, response); responseJson(response); return; } //获取权限失败,会抛出异常 UsernamePasswordAuthenticationToken authentication = getAuthentication(request); //获取后,将Authentication写入SecurityContextHolder中供后序使用 SecurityContextHolder.getContext().setAuthentication(authentication); chain.doFilter(request, response); } catch (Exception e) { responseJson(response); e.printStackTrace(); } } /** * 未登錄時的提示 * @param response */ private void responseJson(HttpServletResponse response){ try { //未登錄時,使用json進行提示 response.setContentType("application/json;charset=utf-8"); response.setStatus(HttpServletResponse.SC_FORBIDDEN); PrintWriter out = response.getWriter(); Map<String,Object> map = new HashMap<String,Object>(); map.put("code",HttpServletResponse.SC_FORBIDDEN); map.put("message","请登录!"); out.write(new ObjectMapper().writeValueAsString(map)); out.flush(); out.close(); } catch (Exception e1) { e1.printStackTrace(); } } /** * 通过token,获取用户信息 * @param request * @return */ private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) { String token = request.getHeader("Authorization"); if (token != null) { //通过token解析出用户信息 String user = Jwts.parser() //签名盐 .setSigningKey("MyJwtSecret") .parseClaimsJws(token.replace("Bearer ", "")) .getBody() .getSubject(); //不为null,返回 if (user != null) { return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>()); } return null; } return null; } } 

从B项目的配置中,可以看出,B项目配置的太简洁了,只需要拦截一下没有登录的请求,连登录也都省了。

A和B项目中分别添加一个Controller,用于测试

import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * 测试用例 * @author 程就人生 * @date 2019年5月26日 */ @RestController public class IndexController { @GetMapping("/index") public Object index(){ return "index"; } } 

使用测试工具进行测试

第一步,测试A项目和B项目的index是否能访问,结果都不能访问,测试结果OK
测试结果-1

测试结果-2

第二步,通过登录获取token,登录成功后,返回了JSON格式的提示,返回的token在头部,点击响应头,获取token
测试结果-3

测试结果-4

第三步,将token拷贝至A项目index的头部,B项目index的头部,测试结果ok,都可以访问,也可以把token时间设置的短一些,测试一下token过期了,是否还能访问。
测试结果-5

测试结果-6

最后,感觉一下Token的结构,去掉前面固定的Bearer ,后面的分成三个部分,中间用点隔开,这个就简单了解下吧。

  • Header(头部)
  • Payload(负载)
  • Signature(签名)
Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJmZW5nIiwiZXhwIjoxNTU4OTUxMjM5fQ.X7lOhHJljxnVcNEckYSX22rgTDN0ToRJLaPb_1dAoPzx6q_eN5B5iOxO2GXoNUllIfQG6SrdJhgYzKZPTMsDIg 

Spring Security整合JWT,实现单点登录的功能,到此就告一段落了,看起来是不是很简单呢,那就动手试一试吧。

作者:程就人生
链接:https://www.jianshu.com/p/8bd4a6e27e7f
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

转载于:https://www.cnblogs.com/telwanggs/p/11162110.html

Spring Security整合JWT,实现单点登录,So Easy~!相关推荐

  1. 基于Spring Security与JWT实现单点登录

    基于RBAC的权限管理 RBAC(Role-Based Access Control):基于角色的访问控制 当前项目中,RBAC具体的表现为: 管理员表:ams_admin 角色表:ams_role ...

  2. java oauth sso 源码_基于Spring Security Oauth2的SSO单点登录+JWT权限控制实践

    概 述 在前文<基于Spring Security和 JWT的权限系统设计>之中已经讨论过基于 Spring Security和 JWT的权限系统用法和实践,本文则进一步实践一下基于 Sp ...

  3. 基于Spring Security + OAuth2 的SSO单点登录(服务端)

    相关技术 spring security: 用于安全控制的权限框架 OAuth2: 用于第三方登录认证授权的协议 JWT:客户端和服务端通信的数据载体 传统登录 登录web系统后将用户信息保存在ses ...

  4. spring boot整合Shiro实现单点登录

    默认情况下,Shiro已经为我们实现了和Cas的集成,我们加入集成的一些配置就ok了 1.加入shiro-cas包 <!-- shiro整合cas单点 --><dependency& ...

  5. jwt认证机制优势和原理_最详细的Spring Boot 使用JWT实现单点登录

    Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(S ...

  6. springBoot整合spring security+JWT实现单点登录与权限管理前后端分离

    在前一篇文章当中,我们介绍了springBoot整合spring security单体应用版,在这篇文章当中,我将介绍springBoot整合spring secury+JWT实现单点登录与权限管理. ...

  7. springBoot整合spring security+JWT实现单点登录与权限管理前后端分离--筑基中期

    写在前面 在前一篇文章当中,我们介绍了springBoot整合spring security单体应用版,在这篇文章当中,我将介绍springBoot整合spring secury+JWT实现单点登录与 ...

  8. Spring Cloud入门-Oauth2授权之基于JWT完成单点登录(Hoxton版本)

    文章目录 Spring Cloud入门系列汇总 摘要 单点登录简介 创建oauth2-client模块 修改授权服务器配置 网页单点登录演示 调用接口单点登录演示 oauth2-client添加权限校 ...

  9. Spring Security Oauth2 JWT 实现用户认证授权功能

    Spring Security Oauth2 JWT 一 用户认证授权 1. 需求分析 1.1 用户认证与授权 什么是用户身份认证? 用户身份认证即用户去访问系统资源时系统要求验证用户的身份信息,身份 ...

最新文章

  1. java基础编程题(2)
  2. 蓝牙模块hc05原理图_使用步进电机和Android App的蓝牙控制迷你升降机
  3. dex2oat 加载多次
  4. 不同层级的Java开发者的不同行为
  5. 2021年金三银四春招实习回顾
  6. Win11系统获取管理员权限的方法
  7. mysql 压缩备份_备份压缩mysql 数据库
  8. python webservices_python实现webservices接口并调用
  9. 应急响应的整体思路一
  10. nginx的工作原理及配置
  11. 思维导图之《机器视觉知识体系》
  12. ExtJs6学习(五)【Extjs MVC开发模式详解】
  13. oracle c# 插入中文乱码,C#写入Oracle 中文乱码问题
  14. 用matlab模拟等离子体论文,等离子体模型的建立
  15. word从第三页插入页码
  16. 暴躁是企业家的性格?
  17. android调试遇到ADB server didn't ACK以及蛋疼的sjk_daemon进程
  18. Dazdata BI之PDF魔幻输出
  19. Stimulsoft 报表工具单元格内换行
  20. QT——Qt QtCreator 官方下载地址

热门文章

  1. mysql 自然排序_如何在mysql中实现自然排序
  2. 物联网和工业互联网场景下的边缘计算
  3. Verilog HDL常用循环语句类型
  4. vc6开发一个抓包软件_开发一个软件要多少钱?app软件开发的费用
  5. 【蓝桥杯嵌入式】【STM32】8_USART之响应上位机指令发送实时时间
  6. 集群的可扩展性及其分布式体系结构(2)-下
  7. 今日初学C语言写的几个程序。
  8. 嵌入式Linux系统编程学习之十八进程间通信(IPC)简介
  9. led灯条串联图_液晶电视维修:LED灯光电路原理,电路图原理分析?
  10. springmvc的运行流程